对python沙盒的编写以及现有几种常见的沙盒绕过策略的一点总结
基础知识
内建名称空间 __builtins__
在启动Python解释器之后,即使没有创建任何的变量或者函数,还是会有许多函数可以使用,这些函数就是内建函数,并不需要我们自己做定义,而是在启动python解释器的时候,就已经导入到内存中供我们使用,想要了解这里面的工作原理,我们可以从名称空间开始
名称空间在python是个非常重要的概念,只得是从名称到对象的映射,而在python程序的执行过程中,至少会存在两个名称空间
- 内建名称空间
- 全局名称空间
这里我们主要关注的是内建名称空间,是名字到内建对象的映射,,在python中,初始的__builtins__
模块提供内建名称空间到内建对象的映射
dir()函数用于向我们展示一个对象的属性有哪些,在没有提供对象的时候,将会提供当前环境所导入的所有模块,我们可以看到初始模块有哪些
|
|
这里面,我们可以看到__builtins__
是做为默认初始模块出现的,那么用dir()命令看看__builtins__
的成分
|
|
从里面我们可以看到一些我们经常用到的函数:open(),eval(),len(),__import__
,以及我们刚才用的dir()函数,还有我们要用的一些对象诸如list,dict,tuple,int,float这些,然后还有一些异常啥的。当然,这里面最关键的就是__import__
了,可以使用import函数的话,就可以导入任意模块了。
python中的类继承,两个魔术方法,全局变量
类继承
python中对一个变量应用__class__
方法从一个变量实例转到对应的对象类型后,类有以下三种关于继承关系的方法
__base__
对象的一个基类,一般情况下是object,有时不是,这时需要使用下一个方法__mro__
同样可以获取对象的基类,只是这时会显示出整个继承链的关系,是一个列表,object在最底层故在列表中的最后,通过__mro__[-1]
可以获取到__subclasses__()
继承此对象的子类,返回一个列表
有这些类继承的方法,我们就可以从任何一个变量,顺藤摸瓜到基类中去,再获得到此基类所有实现的类,就可以获得到很多的类啦,当然,这些类还只是直接继承object的,如果我们顺着子类往下摸说不定还能找到更多
两个魔术方法
第一个是类具有的——__dict__
魔术方法
|
|
第二个是实例、类、函数都具有的——__getattribute__
魔术方法
|
|
了解上面这些是有些用处的,沙盒逃逸中有个很重要的方法就是:从变量->对象->基类->子类遍历->全局变量 这个流程中,找到我们想要的模块或者函数,而上面的叙述一个是为了讲清为什么要用这个流程,另一方面就是介绍了两种魔术方法,能够以字符串的形式调用属性,提供了一些字符绕过的可能
从沙盒编写中看沙盒逃逸思路
通过删除内建空间中的成员来限制
比较典型的删除名单列表
|
|
__import__
被删掉了,限制了对敏感模块的导入
reload
被删掉了,限制了对已有模块的重新导入,reload(__builtins__)
open
被删掉了,限制文件读写
eval
被删掉了,限制字符代码执行
如果没有限制reload的话,就可以将__builtins__
重新导入,但是在这种经典配置下,似乎我们目前只有下面这一种姿势去搞:
其实思路之前已经介绍过了,就是变量->对象->基类->子类遍历->全局变量 ,在这个流程中找到我们想要的模块或者函数。介绍一个简易的寻找代码
|
|
直接从Ben师傅那里搬运几个python2,3中常用的payload来
|
|
通过对参数的匹配来进行过滤
通过字符匹配去做过滤,python写法那么多,我们采用不同的写法就好了,所以应对这种姿势的方法也是多种多样
一般都会限制os,import
在上面提到过了两种魔术方法,可以通过字符的形式调用里面的属性,一旦能够通过字符去调用就很容易绕过了,比如最简单的’so’[::-1]就可以
对于import关键字过滤的话,可以考虑以下几个思路,
__builtins__
里面包含着import方法,借助之前提到的__dict__
属性可以通过字符串编码绕过__builtins__.__dict__['__impo'+'rt__']('o'+'s')
限制
__ [] '等各种字符
本来我以为也能利用
__dict__
的方法,将payload中的属性转变为字符串来表示的,但一来__dict__
自身就带有__
,二来__globals__ __subclasses__ __base__
这些并不是属性,无法使用__dict__
,所以非模板注入的情况下,暂时没有找到绕过的方法但是在Jinja2模板注入中,绕过是可行的:
可以用
[].__class__['__base__']
来表示[].__class__.__base__
,为了免去从变量到对象这一步则使用flask中的request对象,也就是request['_'+'_base_'+'_']
如果不允许用[],jinja2中的函数|attr()也可以代表属性,更推荐用这种
[]|attr('__class__')|attr('__base__')
如果禁止使用引号,可以考虑使用request.args.para1参数来代表
url?mobancanshu=[]|attr(request.args.para1)¶1=__class__
当然,如果
__
的检测是对全部传入参数的,还可以使用Jinja2中自带的链接函数('a','b')|join
,所以最后的payload就是这样mobancanshu=[]|attr((request.args.para1*2, request.args.para2, request.args.para1*2)|join)|attr((request.args.para1*2, request.args.para3, request.args.para1*2)|join)¶1=_¶2=class¶3=base
再加一句,如果
|join
被过滤,其实还有个替代函数mobancanshu=(request.args.para1)|format(request.args.pata2, request.args.pata2, request.args.pata2, request.args.pata2)¶1=%s%sbase%s%s¶2=_
在hctf中又看到了一些绕过的姿势
1234过滤了:空格(%20),回车(%0a),'__','[',']','os','"',"|[a-z]"绕过方法:空格可以用tab(%09)绕过,|后不允许接a-z可以用%0c即|%0cattr,tab等绕过
接着上面提到的非模板注入下场景如果过滤了
__
暂时没有绕过方法这个话题,08067的比赛中有个有趣的场景一个在线代码练习的网站,首先肯定不能禁用掉import,否则用户体验极差。。所以主要的过滤在基于字符的上面,过滤掉了
__
等字符,此时没必要执着在那个长长的魔术方法流程上了,用不了啦,__builtins__
所提供的思路也用不了啦,既然是字符过滤,那么就考虑怎样使用字符形式的调用方法来绕过字符检测就好了。最开始自然想到eval,但是被删掉了。解决方案是在import上下手,python中有很多内置模块可以import进去,我们可以在里面找到一些和eval类似的。
123# 用于测试代码的运行时间,可以将运行语句以字符串形式写出来,类似execimport timeittimeit.timeit("__import__('os').system('ls')", number=1)123# 一般用于显示各种配置信息,可以直接写命令import platformprint platform.popen('ls', mode='r').read()
Android中类似的绕过方式
相信大家对Android中的Webview漏洞都不陌生,其中通过Js代码来调用Java代码的addJavascriptInterface 接口引起远程代码执行漏洞的实现原理也类似于上面我们所说的Python沙盒绕过
首先通过Android中的addJavascriptInterface函数可以为要执行的js代码绑定一个Java对象,之后js代码就可以直接调用这个Java对象的所有公有接口,如下所示:
|
|
|
|
那么类似于上面python的绕过方式,在Java中我们可以通过Java的反射机制实现调用到其他的类:
- Android中的对象有一公共的方法:getClass() ,该方法可以获取到当前类,类型为Class
- Class类有一关键的方法: Class.forName,可以用来加载一个指定的类
- 加载java.lang.Runtime 类,该类是可以执行本地命令的
js代码可以如下书写:
|
|