当前位置: 首页 > news >正文

Python 反序列化安全问题(二)


python pickle允许类定义__reduce__方法来声明如何进行序列化。其返回字符串或者tuple,前者可能代表着一个python的全局变量的名称,后者则是描述在反序列化过程中如何进行重构。安全问题也是主要出在后者,本文主要针对于该情况进行pickle模块源码分析。

一、源码分析

代码结构可以分为:基础变量、自定义异常类、操作变量、序列化以及反序列化类以及普通函数。

1.1 基础变量

代码(28-57行)最先定义了部分变量,如最高协议号还有代码中使用了struct.pack()以及marshal.loads()进行序列化和反序列化,并且解释了为何用这两个函数。

1.2 自定义异常类

代码(59-85行)中自定义了4个异常类,分别为PickleError、PicklingError、UnpicklingError以及_Stop.

  1. PickleError:PickingError和UnpicklingError的基类
  2. PicklingError:序列化过程中异常
  3. UnpicklingError:反序列化过程中异常
  4. _Stop:在反序列化过程中结尾处触发该异常

1.3 操作变量

代码(99-126行)定义了操作变量,我们可以理解为操作指令,每一个变量都对应着相关操作,这些指令在序列化的过程中写入,然后在反序列化过程中读取进行对应操作;我们主要理解如下操作指令。

  1. c:读取新的一行作为模块名module,读取下一行作为对象名object,然后将module.object压入到堆栈中。
  2. p:将堆栈中索引为-1的对应存储入内存。
  3. (:将一个标记对象插入到堆栈中。
  4. t:构建元组压入堆栈。
  5. S:读取字符串进行处理之后压入堆栈。
  6. R:将一个元组和一个可调用对象弹出堆栈,然后以该元组作为参数调用该可调用的对象,最后将结果压入到堆栈中。
  7. .:调用_Stop结束反序列化。

1.4 序列化以及反序列化类

代码定义了Pickler和Unpickler类,这两个类是pickle模块进行序列化反序列化的核心,下面看其实现过程:

1.4.1 序列化过程

  1. dumps函数接收参数后首先进行Pickler类的初始化,然后调用类中的dump函数进行序列化。
  2. dump()函数首先调用save函数,save函数可以看做字典类型调度器,key为需要进行序列化的对象的type,value为对应type的存储函数名。例如如果序列化对象为[1, 2, 3],也就是list类型,save函数判断完类型之后,在调度器内查找对应的方法save_list,然后调用结束后将结果写入内存中,最后dump函数写入结束符号完成整个序列化过程。
  3. 如果上一步查询调度器并没有查询到对应的方法,即对象的type不在NoneType/bool/builtin/classobj/dict/float/function/instance/int/list/long/str/tuple/type/unicode这些类型中的时候,首先查看是否存在__reduce_ex__,如果存在则不再查找__reduce__,不存在的话则继续查找__reduce__;进而判断该函数返回值是string还是tuple,前者进入save_global;后者进入危险开始的save_reduce函数。
  4. save_reduce会将__reduce__返回的tuple结果,调用save_tuple方法进行序列化存储

序列化流程图
测试代码

import os
class A():
    def __reduce__(self):
        a = 'whoami'
        return (os.system, (a,))
print type(A)
print dumps(A)
print type(A())
print  dumps(A())
class B(object):
    def __reduce__(self):
        a = 'whoami'
        return (os.system, (a,))
print type(B)
print  dumps(B)
print type(B())
print dumps(B())

测试结果:

<type 'classobj'>
c__main__
A
p0
.
<type 'instance'>
(i__main__
A
p0
(dp1
b.
<type 'type'>
c__main__
B
p0
.
<class '__main__.B'>
cnt
system
p0
(S'whoami'
p1
tp2
Rp3
.

上述结果可以看出如果我们要达到执行任意代码的目的,需要使用的是第四种即dumps(B())才能进入到save_reduce方法,前三种只能调用save_global方法,只是对于命名引用进行序列化,所以也只能使用于相同环境中,否则在反序列化的过程中会报错。

1.4.2 反序列化过程

反序列化和序列化的过程挺相似,按字节读取然后在调度器中查找对应的处理函数;上面我们提到过一些操作指令,反序列化的调度器中将上述的操作指令作为key,处理函数作为value,此处主要分析上述最后一个实例的反序列化过程。

序列化的结果:

cnt
system
p0
(S'whoami'
p1
tp2
Rp3
.

反序列化过程:

  1. 读取第一个字符c,查询调度器,对应的方法为load_global;
  2. 调用load_global,读取该行将该行后面nt作为模块名,下一行system作为方法名,两者作为参数进入find_class;
  3. find_class的目的就是返回nt.system方法,然后将返回结果压入堆栈中;该方法的代码如下所示:
    def find_class(self, module, name):
        __import__(module)
        mod = sys.modules[module]
        klass = getattr(mod, name)
        return klass
  1. 继续读取字节p,调用load_put,load_put从堆栈中获取最后一个对象,放入内存中,p后面的数字,为key;
  2. 继续读取字节(,调用load_mark,将object()压入堆栈;
  3. 继续读取字节S,调用load_string,将'whoami'去除" '压入堆栈;
  4. 到目前堆栈中有3个对象,分别为nt.system、object()、whoami;继续读取p,将whoami存储到内存中,key为1;
  5. 读取字节t,调用load_tuple,其首先调用marker获取object()的索引号,此处为1,然后将stack[1:]变为('whoami',),也就是说执行完这一步操作之后,堆栈中只有nt.system和('whoami',);
  6. 读取字节p,调用load_put,将('whoami',)存储如内存,key为2,;
  7. 读取字节R,调用load_reduce,运行nt.system('whoami'),得出结果之后赋值给堆栈索引为-1;
  8. 读取p,调用load_put,将结果存储到内存;
  9. 读取.,即结束符号,清空堆栈,结束反序列化。

总结

序列化以及反序列化其实是给每种能够识别出来的类型的对象一个既定的方式去进行序列化或者反序列化,如果碰到不认识的,那就去查找__reduce__,将其序列化,然后在根据它去进行反序列化过程中的重构;
从上面的分析过程中可以看出,如果我们要在反序列化的过程中去执行命令,就要满足在序列化的时候能执行save_reduce,然后在反序列化的过程中才能执行load_reduce,进而执行命令;

相关文章:

  • collection和collections的区别
  • Eclipse自动补全增强
  • H264 RTP封包原理(转载)
  • (转)Groupon前传:从10个月的失败作品修改,1个月找到成功
  • Intellij IDEA 热部署处理
  • Angular Material 攻略 03 angular Material Design 安装
  • urllib2 的使用细节(转)
  • 半理解系列--Promise的进化史
  • 游戏引擎大全
  • 【Lv1-Lesson008】A Guide to Birthdays
  • ARM9 S3C2440 定时器中断
  • 可复制的领导力(来自樊登读书会)
  • C++中的const成员函数(函数声明后加const,或称常量成员函数)用法详解
  • 孩子做错事不认错怎么办
  • iOS app被审核拒绝后如何打开解决方案中心的记录
  • 30天自制操作系统-2
  • iOS仿今日头条、壁纸应用、筛选分类、三方微博、颜色填充等源码
  • JAVA并发编程--1.基础概念
  • magento 货币换算
  • node入门
  • rabbitmq延迟消息示例
  • Webpack 4x 之路 ( 四 )
  • 阿里云前端周刊 - 第 26 期
  • 安卓应用性能调试和优化经验分享
  • 算法系列——算法入门之递归分而治之思想的实现
  • (C语言)编写程序将一个4×4的数组进行顺时针旋转90度后输出。
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (附源码)spring boot建达集团公司平台 毕业设计 141538
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (完整代码)R语言中利用SVM-RFE机器学习算法筛选关键因子
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .NET国产化改造探索(一)、VMware安装银河麒麟
  • .NET开源快速、强大、免费的电子表格组件
  • .NET设计模式(2):单件模式(Singleton Pattern)
  • .Net中的设计模式——Factory Method模式
  • /etc/apt/sources.list 和 /etc/apt/sources.list.d
  • [2]十道算法题【Java实现】
  • [Android]竖直滑动选择器WheelView的实现
  • [Android]通过PhoneLookup读取所有电话号码
  • [Avalon] Avalon中的Conditional Formatting.
  • [BIZ] - 1.金融交易系统特点
  • [BZOJ 1032][JSOI2007]祖码Zuma(区间Dp)
  • [GN] Vue3.2 快速上手 ---- 核心语法2
  • [i.MX]飞思卡尔IMX6处理器的GPIO-IOMUX_PAD说明
  • [IE编程] IE中对网页进行截图的编程接口
  • [JS]JavaScript 注释 输入输出语句
  • [LeetCode] 196. 删除重复的电子邮箱
  • [NOIP2007 普及组] 纪念品分组--贪心算法
  • [OGRE]看备注学编程(02):打地鼠01-布置场地九只地鼠
  • [one_demo_7]求走到第50个台阶的走法多少种
  • [Perl] Find Shell on your Wordpress site