subprocess.run() 更安全,因其默认不调用 shell,将命令与参数以列表形式直接传给操作系统,避免 shell 解析导致的命令注入;而 os.system() 将字符串全交由 shell 处理,用户输入含 ;、&& 等即可执行任意命令。

subprocess.run() 为什么比 os.system() 安全?
因为 os.system() 把整个字符串交给 shell 解析,攻击者只要在参数里塞 ;、&& 或 $() 就能执行任意命令;而 subprocess.run() 默认不调用 shell,它把命令和参数拆成列表直接传给操作系统,中间没有 shell 解释层,自然就切掉了命令注入的路径。
常见错误现象:用户输入 "; rm -rf /" 作为文件名,用 os.system(f"cat {filename}") 一跑,系统就没了。
- 必须把命令和每个参数分开写成列表,例如
["ls", "-l", "/tmp"],不能拼成字符串"ls -l /tmp" - 如果硬要走 shell(比如需要管道、通配符),必须显式加
shell=True,但此时你得自己确保所有变量都经过shlex.quote()处理 - 不要用
subprocess.Popen(..., shell=True)配合字符串格式化,这是高危组合
如何安全传入用户输入的参数?
核心原则:参数永远走 args 列表,不用字符串插值。哪怕参数里带空格、引号、$ 符号,只要进列表,subprocess 就会原样传递给目标程序,不会被 shell 二次解析。
使用场景:比如调用 ffmpeg 转码,用户指定输入文件路径,路径可能含空格或括号。
- ✅ 正确:
subprocess.run(["ffmpeg", "-i", user_input_path, "out.mp4"]) - ❌ 错误:
subprocess.run(f'ffmpeg -i "{user_input_path}" out.mp4', shell=True) - ⚠️ 特殊情况:如果必须用
shell=True(如需要重定向或|),则对每个动态部分用shlex.quote(user_input)包裹,例如f"grep {shlex.quote(pattern)} file.txt | wc -l"
哪些参数配置能进一步降低风险?
光靠参数拆分还不够,几个关键配置能堵住其他逃逸口。
-
executable:显式指定解释器路径(如/bin/bash),避免环境变量被污染导致调用到恶意 shell -
cwd:限定工作目录,防止相对路径跳转到敏感位置 -
timeout:强制超时,避免恶意命令卡死进程(比如sleep 999999) -
capture_output=True和text=True:方便捕获输出,也避免二进制乱码干扰判断 - 别设
env,除非真有必要;若必须传,用os.environ.copy()基础上增删,而不是全量覆盖
遇到复杂命令链怎么办?
比如要实现 find /tmp -name "*.log" | xargs grep "ERROR",直接拆成一个列表做不到,因为管道是 shell 功能。这时候有两个选择:
- ✅ 推荐:用 Python 原生逻辑替代——先
subprocess.run(["find", ...], capture_output=True, text=True)拿到结果,再用str.splitlines()+subprocess.run(["grep", ...])分步处理 - ⚠️ 次选:启用
shell=True,但必须对每个外部输入做shlex.quote(),且禁用用户控制整个命令结构(比如不允许用户填整个find命令,只允许填-name后的模式) - ❌ 绝对避免:让用户输入整条 shell 命令字符串然后直接
run(cmd, shell=True)
真正难的不是写对一条命令,而是守住「用户输入不参与命令结构决策」这条线——一旦放开命令模板自由拼接,shlex.quote() 也救不了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











