python: 序列化/反序列化,以及漏洞思考
一、序列化/反序列化
python中内置了很多序列化/反序列化的方式,最常用的有json、pickle、marshal这三种,示例用法如下:
import json
import pickle
import marshal
author1 = {"name": "Shanshanyuan", "blog": "https://editor.youkuaiyun.com/md?articleId=104850849", "title": "架构师", "pets": ["dog", "cat"]}
# json序列化
json_str = json.dumps(author1)
print("json=>\n", json_str)
# json字符串反序列化
author2 = json.loads(json_str)
# pickle序列化
pickle_str = pickle.dumps(author1)
print("pickle=>\n", pickle_str)
# pickle字符串反序列化
author3 = pickle.loads(pickle_str)
# marshal序列化
marshal_str = marshal.dumps(author1)
print("marshal=>\n", marshal_str)
# marshal反序列化
author4 = marshal.loads(marshal_str)
print("\n",
id(author1), "\n",
id(author2), "\n",
id(author3), "\n",
id(author4), "\n",
author1, "\n",
author2, "\n",
author3, "\n",
author4)
with open("json.txt", "w") as file1:
json.dump(author1, file1)
with open("pickle.txt", "wb") as file2:
pickle.dump(author1, file2)
with open("marshal.txt", "wb") as file3:
marshal.dump(author1, file3)
输出:
json=>
{"name": "\u83e9\u63d0\u6811\u4e0b\u7684\u6768\u8fc7", "blog": "http://yjmyzz.cnblogs.com/", "title": "\u67b6\u6784\u5e08", "pets": ["dog", "cat"]}
pickle=>
b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x15\x00\x00\x00\xe8\x8f\xa9\xe6\x8f\x90\xe6\xa0\x91\xe4\xb8\x8b\xe7\x9a\x84\xe6\x9d\xa8\xe8\xbf\x87q\x02X\x04\x00\x00\x00blogq\x03X\x1a\x00\x00\x00http://yjmyzz.cnblogs.com/q\x04X\x05\x00\x00\x00titleq\x05X\t\x00\x00\x00\xe6\x9e\xb6\xe6\x9e\x84\xe5\xb8\x88q\x06X\x04\x00\x00\x00petsq\x07]q\x08(X\x03\x00\x00\x00dogq\tX\x03\x00\x00\x00catq\neu.'
marshal=>
b'\xfb\xda\x04name\xf5\x15\x00\x00\x00\xe8\x8f\xa9\xe6\x8f\x90\xe6\xa0\x91\xe4\xb8\x8b\xe7\x9a\x84\xe6\x9d\xa8\xe8\xbf\x87\xda\x04blog\xfa\x1ahttp://yjmyzz.cnblogs.com/\xda\x05title\xf5\t\x00\x00\x00\xe6\x9e\xb6\xe6\x9e\x84\xe5\xb8\x88\xda\x04pets[\x02\x00\x00\x00\xda\x03dog\xda\x03cat0'
4307564944
4309277360
4307565016
4309277432
{'name': 'Shanshanyuan', 'blog': 'https://editor.youkuaiyun.com/md?articleId=104850849', 'title': '架构师', 'pets': ['dog', 'cat']}
{'name': 'Shanshanyuan', 'blog': 'https://editor.youkuaiyun.com/md?articleId=104850849', 'title': '架构师', 'pets': ['dog', 'cat']}
{'name': 'Shanshanyuan', 'blog': 'https://editor.youkuaiyun.com/md?articleId=104850849', 'title': '架构师', 'pets': ['dog', 'cat']}
{'name': 'Shanshanyuan', 'blog': 'https://editor.youkuaiyun.com/md?articleId=104850849', 'title': '架构师', 'pets': ['dog', 'cat']}
注:api的方法名还是很好记的,dump/dumps意为“倒垃圾”,把对象向xxx里一倒,就算序列化完成了。反之load/loads即从字符串或文件中装载(还原)对象。特别要值得一提的是:pickle、marshal存在安全问题,如果装载的字符串或文件里,包含有精心设计的恶意代码,会让恶意代码执行(关于反序列化的漏洞,大家可以上网查一下,有很多类似的介绍)。另外从序列化后的字符串大小来看,默认情况下,就本示例而言,json序列化后的字符串长度最小,so,综合来看,推荐同学们使用json序列化/反序列化。
二、The difference of pickle and marshal
1.jsonpickle和marshal之间有什么区别
- pickle会跟踪已经序列化的对象,如果有引用相同的对象,那么它将不会被再次序列化。但marshal依然会。
- marshal不能序列化用户自定义的对象。
- marshal不能保证在多个Python版本间传递。
2.pickle和json方式之间有什么区别:
- json的序列化输出是文本对象(unicode),而pickle序列化的输出是二进制字节(bytes),如果不明白这两者的区别可以看我的上一篇博客:Python2和Python3之间关于字符串编码处理的差别。
- json可读性更好。
json拥有广泛的兼容性,能够在Python以外的很多地方使用。而pickle只针对Python。 - json只能表示一部分Python内置的类型,不能表示用户自定义的类对象。当你尝试序列化一个自定义的类对象时,会抛出一个 TypeError 。而pickle则能表示大多数对象,包括用户自定义的类(绝大部分)。
三、 什么类型可以序列化和反序列化(Python3)
- None, True, 和 False
- 整形、浮点、复数
- strings, bytes, bytearrays
- 元组, 列表, 集合, 和 只包含可序列化对象的字典
- 定义在模块顶层的函数(lambda表达式不可以
- 定义在模块顶层的内建函数
- 定义在模块顶层类
- 最后一个本人翻译的不好,直接给英文原文:instances of such classes whose dict or the result of calling getstate() is picklable.
四、漏洞分析
漏洞产生的原因在于其可以将自定义的类进行序列化和反序列化。反序列化后产生的对象会在结束时触发__reduce__()函数从而触发恶意代码。
序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。
简单说明一下__reduce__()函数:将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。
补充:
import pickle
a = 123 #pickle.dumps(a)=b'\x80\x03K{.'
print(id(a), pickle.dumps(a))
b = b'\x80\x03K{.'
print(id(pickle.loads(b)),pickle.loads(b))
输出结果如下:
140708742856768 b'\x80\x03K{.'
140708742856768 123
证明:
首先a被赋值为123,然后将a的值进行序列化再反序列化,那么经过反序列化后的值仍然是123,也就是说pickle.load(f)的返回值是123,那么d=pickle.load(f)就相当于是d=123,虽然是一个新的对象,但程序发现内存中已经有个123了,所以就直接把指针指向了已经存在的123的地址,这也是python和其它语言不同的地方。所以最后看到的地址是一样的