应使用 subprocess.run() 替代 os.system() 和 os.popen(),传入参数列表而非拼接字符串,禁用 shell=True,显式控制 env 和 cwd,并警惕 SUID 二进制与环境变量劫持风险。

使用 subprocess.run() 替代 os.system() 和 os.popen()
os.system() 和 os.popen() 会直接将字符串交给 shell 解析,一旦参数含用户输入,极易触发命令注入。比如 os.system(f"ls {user_input}") 遇到 user_input = "; rm -rf /" 就会执行恶意命令。
正确做法是用 subprocess.run() 并传入参数列表(而非拼接字符串),让 Python 绕过 shell,直接调用程序:
subprocess.run(["ls", "-l", user_input]) # 安全:user_input 被当作文本参数,不会被 shell 解析
关键点:
- 始终用
list形式传参,避免shell=True - 若必须用 shell 功能(如管道、通配符),则需严格过滤或转义用户输入,不推荐
-
subprocess.run(..., capture_output=True, text=True)比check_output()更清晰可控
警惕 shell=True 的隐式风险
即使你没显式写 shell=True,某些封装函数(如旧版 commands.getoutput(),或第三方库的 exec 函数)内部可能默认启用 shell,导致参数被重新解析。
立即学习“Python免费学习笔记(深入)”;
常见踩坑场景:
- 调用
subprocess.run("ls *.py", shell=True)——*由 shell 展开,但若*.py来自用户,就等于放行任意文件名匹配逻辑 - 用
shlex.split()处理用户输入再传给subprocess,却仍设shell=True,等于白做 - 在 Windows 上误以为
shell=False就绝对安全,其实cmd.exe的某些内置命令(如dir)仍依赖 shell 行为
建议:除非明确需要 shell 特性(且已评估输入完全可信),否则一律禁用 shell=True。
小麦企业网站展示系统介绍:一、安装使用将xiaomai.sql导入数据库二、后台登录后台帐号,密码默认都是admin,config.php 配置文件可根据自行需要修改,IP地址,数据库用户名,密码,及表名后台目录默认admin,支持自行任意修改目录名三、注意事项1 本源码完全免费,采用伪静态,减少不必要的源码重复,速度更快,支持二次开发。2、注明本程序编码为UTF8,如发生乱码,请注意修改编码3、
环境变量与工作目录的污染风险
子进程会继承父进程的 os.environ,其中可能含敏感信息(如 HOME、PATH、SSH_AUTH_SOCK),或被恶意篡改影响命令行为。
实操建议:
- 显式传入干净的
env参数,例如env=dict(os.environ, PATH="/usr/bin:/bin") - 用
cwd明确指定工作目录,避免子进程在意外路径下读写文件 - 对敏感命令(如涉及密钥操作的
gpg或ssh),考虑用env={}清空环境后按需注入必要变量
权限提升与进程逃逸的实际约束
Python 进程本身权限决定子进程上限:普通用户启动的脚本,无论怎么调用 subprocess,都无法直接执行 sudo 或写入 /etc——除非提前配置了免密 sudo 规则,而这属于系统层配置问题,不是 Python 能绕过的。
但仍有两类易被忽略的越权路径:
- 子进程调用的二进制本身有 SUID 位(如
/usr/bin/passwd),此时它以文件所有者身份运行,可能提权 - 通过
LD_PRELOAD或pythonpath等环境变量劫持子进程加载的库或模块,间接控制行为
所以不要只盯着“命令怎么拼”,还得看“谁在跑、在哪跑、带了什么环境”。尤其在容器或共享服务器中,这些边界比语法更关键。









