Python反序列化

Python反序列化漏洞

Pickle

  • 序列化:pickle.dumps() 将对象序列化为字符串、pickle.dump() 将对象序列化后的字符串存储为文件
  • 反序列化:pickle.loads() 将字符串反序列化为对象、pickle.load() 从文件中读取数据反序列化

使用dumps()loads() 时可以使用 protocol 参数指定协议版本

协议有0,1,2,3,4,5号版本,不同的 python 版本默认的协议版本不同。这些版本中,0号是最可读的,之后的版本为了优化加入了不可打印字符

协议是向下兼容的,0号版本也可以直接使用

可序列化的对象

  • NoneTrueFalse
  • 整数、浮点数、复数
  • str、byte、bytearray
  • 只包含可封存对象的集合,包括 tuple、list、set 和 dict
  • 定义在模块最外层的函数(使用 def 定义,lambda 函数则不可以)
  • 定义在模块最外层的内置函数
  • 定义在模块最外层的类
  • __dict__ 属性值或 __getstate__() 函数的返回值可以被序列化的类(详见官方文档的Pickling Class Instances)

反序列化流程

pickle.load()和pickle.loads()方法的底层实现是基于 _Unpickler()方法来反序列化

在反序列化过程中,_Unpickler(以下称为机器吧)维护了两个东西:栈区和存储区

为了研究它,需要利用一个调试器 pickletools

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wUDq6S9E-1642832623478)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220121114238511.png)]

从图中可以看出,序列化后的字符串实际上是一串 PVM(Pickle Virtual Machine) 指令码,指令码以栈的形式存储、解析

PVM指令集

完整PVM指令集可以在 pickletools.py 中查看,不同协议版本使用的指令集略有不同

上图中的指令码可以翻译成:

    0: \x80 PROTO      3  # 协议版本
    2: ]    EMPTY_LIST  # 将空列表推入栈
    3: (    MARK  # 将标志推入栈
    4: X        BINUNICODE 'a'  # unicode字符
   10: X        BINUNICODE 'b'
   16: X        BINUNICODE 'c'
   22: e        APPENDS    (MARK at 3)  # 将3号标准之后的数据推入列表
   23: .    STOP  # 弹出栈中数据,结束
highest protocol among opcodes = 2

指令集中有几个重要的指令码:

  • GLOBAL = b’c’ # 将两个以换行为结尾的字符串推入栈,第一个是模块名,第二个是类名,即可以调用全局变量 xxx.xxx 的值
  • REDUCE = b’R’ # 将可调用元组和参数元组生成的对象推进栈,即__reduce()返回的第一个值作为可执行函数,第二个值为参数,执行函数
  • BUILD = b’b’ # 通过__setstate__或更新__dict__完成构建对象,如果对象具有__setstate__方法,则调用anyobject .__setstate__(参数);如果无__setstate__方法,则通过anyobject.__dict__.update(argument)更新值(更新可能会产生变量覆盖)
  • STOP = b’.’ # 结束

一个更复杂的例子:

import pickle
import pickletools

class a_class():
    def __init__(self):
        self.age = 24
        self.status = 'student'
        self.list = ['a', 'b', 'c']
a_class_new = a_class()
a_class_pickle = pickle.dumps(a_class_new,protocol=3)
print(a_class_pickle)
# 优化一个已经被打包的字符串
a_list_pickle = pickletools.optimize(a_class_pickle)
print(a_class_pickle)
# 反汇编一个已经被打包的字符串
pickletools.dis(a_class_pickle)
    0: \x80 PROTO      3
    2: c    GLOBAL     '__main__ a_class'
   20: )    EMPTY_TUPLE  # 将空元组推入栈
   21: \x81 NEWOBJ  # 表示前面的栈的内容为一个类(__main__ a_class),之后为一个元组(20行推入的元组),调用cls.__new__(cls, *args)(即用元组中的参数创建一个实例,这里元组实际为空)
   22: }    EMPTY_DICT  # 将空字典推入栈
   23: (    MARK
   24: X        BINUNICODE 'age'
   32: K        BININT1    24
   34: X        BINUNICODE 'status'
   45: X        BINUNICODE 'student'
   57: X        BINUNICODE 'list'
   66: ]        EMPT
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值