码农高天
pickle底层反序列化会无条件执行由魔术方法指定的内置可调用对象,加载不可信数据将导致系统直接执行任意Shell命令并彻底沦陷。
AI-assisted summary of 【python】你知道pickle不安全么?你知道利用pickle进行攻击多简单么? — original source. Check the original for full context.
Source published:
Key ideas
- Python 官方在文档中对 pickle 模块发出了严重安全警告,明确指出对不可信数据执行反序列化可能导致任意代码执行并彻底攻破系统安全防线。 (2:30)
- 利用 pickle 的安全漏洞门槛极低,攻击者只需利用 Python 原生的 __reduce__ 魔术方法即可在十几行代码内构造恶意 Payload。 (2:30)
- pickle 反序列化时会自动动态解析并导入 subprocess 或 os 等标准内置库,即便受害者自身代码中未导入相关库,也会被直接调用执行系统 Shell 命令。 (5:00)
- 跨信任域的数据传输与持久化应优先选用 JSON 等仅表达数据的文本格式,pickle 只能在开发者完全可控且受信任的内部环境中使用。 (5:00)
Detailed notes
## Background 该视频由 YouTube 频道“码农高天”于 2024 年 5 月 22 日发布。视频简介指出,本期内容深入探讨 Python 内置模块 pickle 的安全性,解释为何 Python 官方会对该模块提出安全警告,并直观揭示其潜在风险究竟有多大。
## TL;DR 视频首先对比了通用纯文本格式 JSON 与 Python 专用的 pickle 在序列化及反序列化复杂原生对象时的机制差异。随后,作者结合 Python 官方文档中的高危警告,现场演示了受害者仅调用 `pickle.load()` 就会被恶意二进制文件悄然触发系统 Shell 并执行命令的严重漏洞。最后,视频深入剖析了利用 `__reduce__` 魔术方法注入 `subprocess.Popen` 的攻击原理,强调由于 Python 会自动动态加载内置标准库且缺乏拦截机制,开发者必须严格遵循官方建议,绝不能反序列化任何不受信任的外部数据。
## Atmosphere & style 视频整体呈现出清晰透彻且带有警示意味的硬核技术讲解风格。主讲人以日常编码习惯为切入点循循善诱,但在展示攻击代码执行的瞬间节奏骤然收紧,极简代码与强大破坏力的反差带来了强烈的震撼感。整场叙述理性务实、逻辑环环相扣,既是一堂生动的 Python 机制拆解课,也给每一位观看的开发者敲响了关于代码安全的警钟。
## Bulletin - [02:30](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=150s) Python 官方在文档中对 pickle 模块发出了严重安全警告,明确指出对不可信数据执行反序列化可能导致任意代码执行并彻底攻破系统安全防线。 - [02:30](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=150s) 利用 pickle 的安全漏洞门槛极低,攻击者只需利用 Python 原生的 __reduce__ 魔术方法即可在十几行代码内构造恶意 Payload。 - [05:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=300s) pickle 反序列化时会自动动态解析并导入 subprocess 或 os 等标准内置库,即便受害者自身代码中未导入相关库,也会被直接调用执行系统 Shell 命令。 - [05:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=300s) 跨信任域的数据传输与持久化应优先选用 JSON 等仅表达数据的文本格式,pickle 只能在开发者完全可控且受信任的内部环境中使用。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=0s) 序列化是将复杂数据结构转化为连续字节流以便持久化存储或网络传输的过程,反序列化则是其逆向还原过程。 - [00:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=0s) JSON 作为通用文本序列化标准,具有极高的跨语言支持度与良好的可读性,但仅支持基础数据类型,无法直接序列化 Python 自定义类对象。 - [00:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=0s) pickle 是 Python 专用的序列化模块,旨在支持绝大多数复杂的 Python 原生对象与自定义类实例,其输出为必须以二进制模式读写的字节流。 - [02:30](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=150s) JSON 仅表达纯数据因而天然安全,而 pickle 因支持对象的代码逻辑还原,在解析不可信数据时存在致命漏洞隐患。 - [02:30](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=150s) Python 官方文档在 pickle 页面顶部以醒目警告强调该模块绝不安全,反序列化外部文件本质上等同于允许在本地执行任意代码。 - [02:30](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=150s) 演示证明仅需一行 pickle.load() 调用,恶意 pickle 文件即可在受害者机器上启动系统 Shell 并无阻碍执行 ls 等系统命令。 - [02:30](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=150s) 攻击的核心机制依赖 Python 预留的 __reduce__ 魔术方法,该方法返回的元组可分别指定反序列化时调用的可调用对象及其参数列表。 - [05:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=300s) 攻击者将 subprocess.Popen 及执行指令打包至 __reduce__ 后,pickle.dump() 会将触发命令执行的指示完整写入二进制数据流中。 - [05:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=300s) 反序列化阶段无需受害者代码提前导入 subprocess 或 os 库,pickle 底层机制会在解析时根据路径自动动态查找并完成导入。 - [05:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=300s) 由于利用手段所依赖的库均为 Python 标准内置库,攻击 Payload 对任意标准 Python 运行环境均具备普遍生效的破坏力。 - [05:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=300s) pickle 协议底层对利用 __reduce__ 触发系统调用的方式缺乏内建的防御与拦截机制,攻击成本极低。 - [05:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=300s) 开发者必须严格遵守安全准则:绝不反序列化任何超出掌控范围或来源不可信的 pickle 文件,防范运行环境遭遇不可逆破坏。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=0s) 序列化背景与 pickle 的诞生初衷
在日常编程中,开发者会频繁处理各种各样的数据结构。以 Python 为例,常见的类型包括整型、字符串、列表、字典,以及它们之间极其复杂的相互嵌套。在实际应用场景里,开发者往往需要将这些复杂的数据结构保存到本地硬盘中,或者将其转化为字符串或二进制字节流通过网络进行远程传输。把一个类型和结构都比较复杂的数据结构转换成一串连续字节的过程被称为“序列化”(Serialization);反过来,将这串连续字节重新还原为内存中数据结构的过程则被称为“反序列化”(Deserialization)。
日常开发中最常见的序列化方式非 JSON 莫属。例如构建一个简单的 Python 字典 `{"data": "hello"}`,可以直接调用 Python 内置的 `json` 模块将其序列化并保存到 `data.json` 文件中,随后再读取并反序列化打印出来。JSON 作为通用格式,几乎得到了所有主流编程语言的良好支持,而且它本身是纯文本格式,极易于人类阅读。然而,JSON 也有明显的局限性:由于它是跨语言的通用标准,支持的原生数据类型非常有限。如果在 Python 中定义一个自定义类 `class A` 并实例化一个对象,JSON 就无法对其进行直接序列化。
为了解决 Python 原生对象的持久化与传输问题,Python 官方设计了内置库 `pickle`。`pickle` 与 JSON 在本质目标上完全一致,都是完成序列化与反序列化,但关键不同在于:`pickle` 是专门为 Python 量身定制的,能够天然支持绝大多数 Python 原生复杂对象。例如定义类 `class A` 并创建实例 `d`,后续代码与 JSON 操作几乎如出一辙,只需将 `json` 替换为 `pickle`。需要特别注意的是,与人类可读的 JSON 文本不同,`pickle` 输出的是二进制文件,人眼很难直接看懂,因此在以文件模式读写时必须显式加上二进制模式标志 `b`(如 `wb`、`rb`)。通过 `pickle`,开发者可以轻松实现自定义对象的保存与还原,反序列化后访问其属性(例如 `self.data`)依然保留着原来的值。
```mermaid flowchart TD accTitle: 序列化与反序列化流程对比 accDescr: 对比展示通用格式JSON与Python专用格式pickle在处理不同数据类型时的机制差异。
subgraph Generic["通用文本序列化: JSON"] JObj["内置基础类型<br/>(字典 / 列表 / 字符 / 数值)"] -->|"json.dump()"| JFile["data.json<br/>(人类可读的纯文本)"] JFile -->|"json.load()"| JRestored["还原后的原生基础类型"] end
subgraph PythonOnly["Python专用二进制: pickle"] PObj["Python 自定义对象<br/>(如 class A 实例)"] -->|"pickle.dump(..., 'wb')"| PFile["data.pkl<br/>(不可读的二进制字节流)"] PFile -->|"pickle.load(..., 'rb')"| PRestored["还原后的 class A 实例"] end ```
- **JSON 的优势与短板**:格式通用,所有主流语言均支持,文本格式易于人类直接阅读与调试;但仅支持基础数据类型,无法直接序列化 Python 自定义 class 对象。 - **pickle 的定位**:专为 Python 设计的序列化机制,基于私有协议实现,全面支持复杂的 Python 原生对象;代价是产物为二进制数据,不具备可读性,文件读写必须使用二进制模式。
---
[02:30](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=150s) pickle 的致命安全隐患与任意代码执行漏洞演示
`pickle` 底层设计了一套专门的操作码协议,其具体的实现细节虽然复杂,但核心问题在于其安全性。JSON 格式因其结构简单、只表达纯数据,常年被用作客户端与服务端之间不可信数据的通信协议,无论在服务器端还是本地电脑上直接打开一个陌生的 JSON 文件通常都是安全的。然而,如果查阅 Python 官方文档中的 `pickle` 页面,会发现页面最顶部醒目地悬挂着一个巨大的警告信息(Warning),明确指出该模块绝对是不安全的,开发者应当只对完全受信任的数据执行反序列化操作;官方文档进一步强调,在对一个文件进行“unpickle”(反序列化)时,本质上可能会在系统上执行任意代码。
为了证明这一警告绝非危言耸听,码农高天展示了一段极简的受害者代码:程序仅仅是用 `pickle.load()` 读取并反序列化了一个外部二进制文件。当代码运行的瞬间,控制台立刻打印出了当前工作目录下的所有文件和文件夹列表。实际上,在程序尝试反序列化这个恶意 `pickle` 文件的过程中,底层悄悄启动了一个系统 Shell,并自动执行了 `ls` 命令。正如码农高天所强调的那样:“如果我们能通过注入代码让你的程序打开一个shell并且执行任意指令的话,基本上你的安全防线就被攻破了。”
构建这样一个能夺取系统控制权的二进制 Payload 实际上极其轻巧,甚至不需要什么高深的黑客技巧,仅仅十几行代码即可搞定。核心的“黑魔法”全部隐藏在 Python 为 `pickle` 预留的特殊魔术方法 `__reduce__` 中。该方法的本意是为复杂对象的序列化提供一个自定义扩展入口,允许开发者手动指定对象该如何复原。当 `__reduce__` 被定义为一个返回包含两个元素的元组(tuple)时: 1. **第一个元素**:用于指定反序列化时应调用的可调用对象或类型(Callable / Type)。 2. **第二个元素**:一个传给该类型的参数元组(Arguments),用于指示初始化对象时需要传入的具体参数。
```mermaid flowchart LR accTitle: pickle 漏洞利用机制 accDescr: 展示如何通过定义对象的 reduce 魔术方法构造恶意 payload,并在受害者机器上反序列化触发任意代码执行。
subgraph Attack["攻击端: 构造恶意 Payload"] ClassDef["定义包含 __reduce__ 的类"] TuplePayload["__reduce__ 返回元组:<br/>1. subprocess.Popen<br/>2. ('ls', shell=True) 参数"] Dump["pickle.dump() 生成二进制文件"] ClassDef --> TuplePayload --> Dump end
subgraph Victim["受害者端: 盲目加载不可信文件"] Load["victim.py: pickle.load(file)"] AutoImport["底层自动定位并动态解析<br/>内置库 subprocess"] ExecShell["隐式调用 subprocess.Popen<br/>弹出 Shell 执行系统指令"] Load --> AutoImport --> ExecShell end
Dump -.->|"分发恶意文件"| Load ```
---
[05:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=300s) 攻击原理解析与官方安全建议
在上述攻击示例中,攻击者在元组的第一个元素填入了 `subprocess.Popen`,第二个元素填入了启动系统 Shell 运行 `ls` 命令的入参列表。尽管示例中仅仅使用了无害的 `ls` 指令来避免弄乱本地环境,但攻击者完全可以在这里传入任何具有毁灭性的恶意 Shell 命令(如清理磁盘或建立反弹 Shell)。当攻击者调用 `pickle.dump()` 时,`pickle` 就会根据 `__reduce__` 返回的内容,将“使用 `subprocess.Popen` 配合这串参数来复原对象”的指令原原本本地写入到二进制文件中。
该攻击最危险的地方在于反序列化的执行环境: 1. **无需预先导入依赖**:受害者编写的业务代码中,哪怕压根没有写过 `import subprocess`,`pickle.load()` 也会自动根据序列化数据中的路径自己去动态查找并加载 `subprocess`。 2. **内置库防不胜防**:由于 `subprocess` 以及同样具备系统破坏力的 `os` 库都是 Python 随附的标准内置库,任何一台安装了标准 Python 环境的机器都能百分之百成功加载它们。 3. **缺乏拦截机制**:对于 `pickle` 协议而言,这种极低门槛的利用方式在底层几乎没有任何内建的拦截防御手段。
正如码农高天在视频末尾所告诫的那样:“所以Python官方才会告诉你,只去unpickle那些你相信的、那些你信任的文件,任何超出你掌控范围能力之外的文件都不要去unpickle,他们都有可能对你的运行环境带来不可逆的伤害。”尽管 `pickle` 在 Python 内部跨进程通信或特定开发场景下非常灵活高效,但只要面对来源不可信的数据,就必须时刻警惕其背后的安全深渊,绝不能盲目对其进行反序列化。
- **不可信数据的绝对禁区**:禁止使用 `pickle` 接收来自网络用户、公开上传或未经签名的外部数据;如需跨信任域通信,应优先选择 JSON 等纯数据文本协议。 - **pickle 的合理适用场景**:仅在高度受控的环境中使用,例如由同一开发者完全掌控的本地多进程(IPC)之间传递特定 Python 复杂对象。
## Notable quotes > [05:00](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=300s) 只去unpickle那些你相信的、那些你信任的文件,任何超出你掌控范围能力之外的文件都不要去unpickle,他们都有可能对你的运行环境带来不可逆的伤害。 — 码农高天
> [02:30](https://www.youtube.com/watch?v=ZSCCMknUiDc&t=150s) 如果我们能通过注入代码让你的程序打开一个shell并且执行任意指令的话,基本上你的安全防线就被攻破了。 — 码农高天
## Mentioned 码农高天, Python, pickle, JSON, subprocess, os
## Video description 这期视频聊一下pickle的安全性,为什么python官方会警告这个module不安全?它到底有多不安全?
[Source](https://www.youtube.com/watch?v=ZSCCMknUiDc)