Fickling Blocklist Bypass: cProfile.run() · CVE-2026-22607
漏洞详情
包: pip fickling (pip) 受影响版本: <= 0.1.6 已修复版本: 0.1.7
描述
Fickling 的评估
cProfile 已被添加到不安全导入列表 (trailofbits/fickling@dc8ae12)。
原始报告
描述
摘要 Fickling 0.1.6 及更早版本未将 Python 的 cProfile 模块视为不安全。因此,使用 cProfile.run() 的恶意 pickle 文件被归类为“可疑”(SUSPICIOUS)而非“明显恶意”(OVERTLY_MALICIOUS)。如果用户依赖 Fickling 的输出来决定 pickle 文件是否安全可反序列化,这种误分类可能导致他们在系统上执行攻击者控制的代码。这会影响任何使用 Fickling 作为 pickle 反序列化安全网关的工作流或产品。
详细信息 cProfile 模块缺失于 fickling/analysis.py 中 fickling 的不安全模块导入阻止列表。这与 CVE-2025-67748 (pty) 和 CVE-2025-67747 (marshal/types) 的根本原因相同。
问题源代码:
- 文件: fickling/analysis.py
- 类: UnsafeImports
- 问题: 阻止列表未包含 cProfile、cProfile.run 或 cProfile.runctx
参考类似修复:
- PR #187 将 pty 添加到阻止列表以修复 CVE-2025-67748
- PR #108 记录了阻止列表方法
- 应对 cProfile 应用相同的修复模式
绕过原理:
- 攻击者使用
__reduce__中的 cProfile.run() 创建 pickle - cProfile.run() 接受 Python 代码字符串并直接执行(profile.run 的 C 加速版本)
- Fickling 的 UnsafeImports 分析未将 cProfile 标记为危险
- 只有 UnusedVariables 启发式规则被触发,导致结果为 SUSPICIOUS 严重等级
- 该 pickle 应像 os.system、eval 和 exec 一样被评级为 OVERTLY_MALICIOUS
测试行为 (fickling 0.1.6):
| 函数 | Fickling 严重等级 | 能否导致 RCE |
|---|---|---|
| os.system | LIKELY_OVERTLY_MALICIOUS | 是 |
| eval | OVERTLY_MALICIOUS | 是 |
| exec | OVERTLY_MALICIOUS | 是 |
| cProfile.run | SUSPICIOUS | 是 ← 绕过 |
| cProfile.runctx | SUSPICIOUS | 是 ← 绕过 |
建议的修复: 在 fickling/analysis.py 的不安全导入阻止列表中添加:
- cProfile
- cProfile.run
- cProfile.runctx
- _lsprof (底层 C 模块)
概念验证
完整的环境和配置复现步骤。
环境:
- Python 3.13.2
- fickling 0.1.6 (最新版本,通过 pip 安装)
第 1 步:创建恶意 pickle
|
|
第 2 步:使用 fickling 分析
|
|
预期输出 (如果被正确检测到):
|
|
实际输出 (绕过被确认):
|
|
第 3 步:通过加载 pickle 证明 RCE
|
|
输出
|
|
检查: 代码被执行,证明 RCE。
Pickle 反汇编 (证据):
|
|
影响
漏洞类型:
- 不完整的阻止列表导致安全检查绕过 (CWE-184)
- 通过不安全的反序列化实现任意代码执行 (CWE-502)
受影响对象: 任何依赖 fickling 在加载前审查 pickle 文件安全性的用户或系统。包括:
- ML 模型验证流水线
- 模型托管平台 (Hugging Face, MLflow 等)
- 使用 fickling 的安全扫描工具
- 验证 pickle 制品的 CI/CD 流水线
攻击场景: 攻击者将恶意的 ML 模型或 pickle 文件上传到模型仓库。受害者的流水线使用 fickling 扫描上传文件。Fickling 将文件评级为“可疑”(SUSPICIOUS)(而非“明显恶意”(OVERTLY_MALICOUS)),因此文件未被拒绝。当受害者加载模型时,任意代码在其系统上执行。
为何 cProfile.run() 是危险的: 与需要磁盘上文件的 runpy.run_path() 不同,cProfile.run() 直接接受代码字符串。这意味着整个攻击都包含在 pickle 中,无需外部文件。Python 文档明确指出 cProfile.run() 接受“可以传递给 exec() 函数的单个参数”。cProfile 是 C 加速版本,比 profile 更常用。根据 Python 文档,它也是推荐的性能分析器(“cProfile 推荐给大多数用户”),因此几乎存在于所有 Python 安装中。
严重程度:高
- 攻击者实现了任意代码执行
- 安全控制 (fickling) 专门设计用于防止这种情况
- 绕过除了用 cProfile 制作 pickle 外无需特殊条件
- 攻击完全自包含(无需外部文件)
- cProfile 比 profile 更常用,增加了攻击面
参考
- GHSA-565g-hwwr-4pp3
- GHSA-p523-jq9w-64x9
- GHSA-r7v6-mfhq-g3m2
- trailofbits/fickling#108
- trailofbits/fickling#187
- trailofbits/fickling#195
- trailofbits/fickling@dc8ae12
- https://github.com/trailofbits/fickling/blob/977b0769c13537cd96549c12bb537f05464cf09c/test/test_bypasses.py#L116