关于 python 序列化与反序列化的一些基础知识,有待补充
python 序列化与反序列化
python 提供两个模块来实现反序列化,即
cPickle
和pickle
,这两个模块功能是相同的,区别在于cPickle
是 C 语言写的,pickle
是纯 Python 写的写一个
Person
类并生成一个admin
的实例化对象测试# python 3.7.3 class Person(): def __init__(self, username, password): self.username = username self.password = password admin = Person('admin','admin123')
pickle.dumps()
方法将对象序列化为byte
对象s = pickle.dumps(admin) # b'\x80\x03c__main__\nPerson\nq\x00)\x81q\x01}q\x02(X\x08\x00\x00\x00usernameq\x03X\x05\x00\x00\x00adminq\x04X\x08\x00\x00\x00passwordq\x05X\x08\x00\x00\x00admin123q\x06ub.'
pickle.loads()
将byte
对象反序列化出对象,这个函数有个特性,就是对于未导入的库会尝试自动import
uns = pickle.loads(s) # <__main__.Person object at 0x00000229997E5780> dir(uns) # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slotnames__', '__str__', '__subclasshook__', '__weakref__', 'password', 'username'] uns.username # 'admin' uns.password # 'admin123'
当一个类的实例对象被反序列化时,它的
__init__()
方法不会被调用,默认的方式是创建一个没有初始化的对象然后恢复它的属性。
RestrictedUnpickler 类优化反序列化
在 pickle 文档开头有如下一句话
Never unpickle data received from an untrusted
or unauthenticated source.考虑如下代码,这会导致系统命令
echo
的执行import pickle pickle.loads(b"cos\nsystem\n(S'echo peri0d'\ntR.") # peri0d # 0
文档给出的解决方案是在
Unpickler.find_class()
里面设置黑白名单检测,只能执行内置函数并且函数不在黑名单中,示例如下import builtins import io import pickle black_builtins = { 'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit', } class ResRestrictedUnpickler(pickle.Unpickler): def find_class(self, moudle, name): if moudle == "builtins" and name not in black_builtins: # getattr 用于获取某个对象的属性值 return getattr(builtins, name) raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (moudle, name)) def restricted_loads(s): """Helper function analogous to pickle.loads().""" # BytesIO 用于在内存中读写数据,操作的对象是 byte 类型,对应了 pickle 序列化之后的类型 return ResRestrictedUnpickler(io.BytesIO(s)).load()
做个测试,
print(restricted_loads(b'\x80\x03]q\x00(K\x01K\x02K\x03K\x04X\x04\x00\x00\x000x10q\x01e.'))
第二个,
print(restricted_loads(b"cos\nsystem\n(S'echo hello world'\ntR."))
第三个,
print(restricted_loads((b'cbuiltins\neval\n'b'(S\'getattr(__import__("os"), "system")'b'("echo hello world")\'\ntR.')))
python 反序列化漏洞
所谓反序列化漏洞,就是在序列化的过程中,类的一些魔术方法一起被序列化,而在反序列化时,这些函数就会执行,通过构造相应的 POP 链实现不同的攻击
示例如下,关键点在于
__reduce__()
方法,当对象被序列化时就会被调用,它返回一个代表全局名称的字符串或者一个元组import pickle import os class Person(): def __init__(self, username, password): self.username = username self.password = password def __reduce__(self): return (os.system,('whoami',)) admin = Person('admin','admin123') s = b'\x80\x03cnt\nsystem\nq\x00X\x06\x00\x00\x00whoamiq\x01\x85q\x02Rq\x03.' uns = pickle.loads(s)
返回字符串 : 查找字符串对应名字的对象,将其序列化之后返回
返回元组 : 元组中第一个参数为可调用的对象,第二个元素是该对象所需要的参数元组