pickle 是 Python 中用于序列化和反序列化对象的模块。序列化是将对象转换为字节流的过程,反序列化是将字节流还原为对象的过程。它常被用来:
- 存储 Python 对象(变量、列表、字典、类实例等)
- 用来存储模型参数(但不推荐!LLM 现在更常用
safetensors)
import pickle
import os
import torch.nn as nn
class Demo(nn.Module):
def __init__(self):
super(Demo, self).__init__()
self.linear1 = nn.Linear(128, 256)
self.linear2 = nn.Linear(128, 256)
def test():
pickle.dump(Demo(), open('demo.bin', 'wb'))
demo = pickle.load(open('demo.bin', 'rb'))
if __name__ == '__main__':
test()
但是,使用 Pickle 存在是一个严重的安全风险。在反序列化时,Pickle 可以执行存储在对象中的任意 Python 代码。 这意味着攻击者可以构造恶意的 .pkl 或 .bin 文件,如果你的模型文件是从陌生网站来源下载的,攻击者可以植入恶意代码。
1. 漏洞描述
在 Python 中,pickle 模块用于序列化和反序列化对象,通常使用 pickle.dump 和 pickle.load 进行操作。然而,这个过程中存在一个安全隐患,主要源自 __reduce__ 魔术方法。
接下来,先了解下__reduce__ 魔术方法的作用,它主要是用于在反序列化恢复对象的时候调用(默认不需要手动添加,只有当特殊情况下才会手动编写)。
import pickle
class Demo:
def __init__(self, a, b):
self.a = a
self.b = b
def __reduce__(self):
return (self.__class__, (self.a, self.b))
def test():
# 将 __class__ 对应的类名及其所在模块路径存储到文件中
# 将 self.a 和 self.b 对应的值存储到文件中
pickle.dump(Demo(10, 20), open('demo.bin', 'wb'))
# 从文件中加载数据,并根据存储的模块路径找到名字为 Demo 的类
# 使用该类和存储的参数值创建对象
demo = pickle.load(open('demo.bin', 'rb'))
if __name__ == '__main__':
test()
当一个类实现了 __reduce__ 魔术方法时,pickle 在序列化该对象时会调用 __reduce__,该方法应返回一个元组,其中第一个元素是一个可调用对象,第二个元素是传递给该对象的参数元组。在反序列化时,pickle 会调用这个可调用对象,并传入提供的参数来重建对象。
那么,如果别有用心者在 __reduce__ 返回值上做手脚,即:返回一个具有恶意行为的可调用对象。一旦不知情的用户加载这个 pickle 文件,恶意代码便会执行,从而造成安全威胁。
2. 场景复现
再次明确:Pickle 的安全漏洞主要是由于别有用心者滥用了 __reduce__ 函数,并将返回的正常的可调用对象替换为具有恶意行为的可调用对象。
import pickle
import os
import torch.nn as nn
# 恶意代码的字符串表示
malicious_code_str = """
def malicious_code():
print("no safe code!")
malicious_code()
"""
class Demo:
def __reduce__(self):
return (exec, (malicious_code_str,))
# return (os.system, ('echo no safe command!',))
def test():
demo = Demo()
# 序列化包含恶意程序的对象
with open('no-safe.pkl', 'wb') as f:
pickle.dump(demo, f)
if __name__ == '__main__':
test()
这段代码已经生成了 no-safe.pkl 文件,接下来反序列化该文件:
import pickle
def test():
demo = pickle.load(open('no-safe.pkl', 'rb'))
if __name__ == '__main__':
test()
no safe code!
最后总结下,pickle 是一个强大的序列化模块,但在反序列化时存在安全风险。始终确保反序列化的数据来源可靠,避免反序列化用户输入的数据。如果安全性是关键需求,建议使用更安全的序列化格式(如 json、safetensors)。如果必须使用 pickle,可以通过自定义反序列化逻辑来限制可反序列化的类。
import pickle
# 恶意代码的字符串表示
malicious_code_str = """
def malicious_code():
print("no safe code!")
malicious_code()
"""
class Demo:
def __reduce__(self):
return (exec, (malicious_code_str,))
# return (self.__class__, ())
# 自定义反序列化类
class SafeUnpickler(pickle.Unpickler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 定义允许反序列化的类
self.allowed_classes = {'__main__.Demo': Demo}
# 重写 find_class 函数,该函数在反序列化时被调用,用于根据模块名和类名找到对应的类
def find_class(self, module, name):
# module 和 name 是从序列化文件中读取的模块和类型
# 检查模块和类名是否允许反序列化该类
allowed_class = f'{module}.{name}'
if allowed_class not in self.allowed_classes:
raise pickle.UnpicklingError(f'尝试去反序列化不支持的类型 {allowed_class}')
return self.allowed_classes[allowed_class]
def test():
demo = Demo()
pickle.dump(demo, open('no-safe.pkl', 'wb'))
# 使用自定义反序列化
with open('no-safe.pkl', 'rb') as f:
demo = SafeUnpickler(f).load()
print(demo)
if __name__ == '__main__':
test()


冀公网安备13050302001966号