命令注入高发于os.system和subprocess.run(..., shell=true),应禁用shell模式、拆分参数列表,必要时严格校验/白名单过滤用户输入,避免字符串拼接与shlex.quote误用。

看到 os.system 或 subprocess.run(..., shell=True) 就该停一下
这两处是 Python 命令注入最常藏身的位置。只要用户输入(比如表单、文件名、URL 参数)被拼进命令字符串再交给 shell 执行,风险就已成立。os.system 默认走 shell,subprocess.run 加了 shell=True 也一样——它们都会把整个字符串丢给 /bin/sh 解析,而 shell 会执行其中的分号、管道、重定向、变量替换等。
实操建议:
- 优先用
subprocess.run且**不加**shell=True,把命令和参数拆成列表:subprocess.run(["ls", "-l", user_input])—— 这样user_input只会被当做一个普通参数传给ls,不会触发 shell 解析 - 如果必须用 shell 功能(比如通配符、管道),就彻底剥离用户输入:先校验/清洗,再白名单过滤,或改用 Python 原生实现(如
glob替代*,shutil.copy替代cp) - 别信“我只用了
int()转一下”——数字也能拼出恶意命令,比如1; rm -rf /在int()下会报错,但若你用了str()回填或容错逻辑,就又绕回去了
shlex.quote 不是万能解药,用错反而更危险
shlex.quote 的作用是把一个字符串转成 shell 安全的单引号包裹形式,比如 shlex.quote("a'b c") → 'a'"'"'b c'。但它只适用于「你确定要走 shell」且「只有一段不可信输入」的场景。
常见错误现象:对多个参数分别 shlex.quote 后再拼接字符串,比如 f"{shlex.quote(a)} {shlex.quote(b)}" —— 看似安全,实则破坏了参数边界,尤其当 b 是空字符串或含换行时,shell 仍可能误解析。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 只在明确需要
shell=True且仅有一个用户可控字段时使用shlex.quote - 绝对不要把它和字符串拼接混用;更别用它处理路径、选项、值混合的复杂命令
- 遇到类似
grep -r "pattern" /path,其中pattern和/path都来自用户?直接放弃 shell,改用subprocess.run(["grep", "-r", pattern, path], ...)
Django/Flask 里用 os.popen 或 commands.getoutput(Python 2)等于主动开门
这类封装函数本质仍是调用 shell,且通常不暴露 shell 控制开关,隐蔽性更强。Django 模板里写 {{ some_var|safe }} 再配合后端执行命令,更是双重危险。
使用场景多见于旧项目迁移、运维脚本嵌入 Web 接口、或“临时查个日志”的快速实现——但正是这些地方最容易漏掉输入校验。
实操建议:
- 立即替换
os.popen为subprocess.run(Python 3.5+)或subprocess.check_output,并确认shell=False(默认值) - Flask 路由中避免任何
subprocess调用裸露在 request 参数之后;Django 视图同理,尤其警惕request.GET.get("cmd")这类写法 - 如果业务真需要执行系统命令,把合法命令集做成白名单字典,比如
{"log_tail": ["tail", "-n", "100", "/var/log/app.log"]},再根据用户请求 key 查表执行
CI/CD 脚本、Dockerfile RUN 行里的 $INPUT 也是注入高发区
GitHub Actions 的 ${{ inputs.cmd }}、GitLab CI 的 $CI_COMMIT_TAG、Dockerfile 中的 RUN echo $VERSION —— 这些变量若来自外部(PR 标题、tag 名、环境注入),同样会触发 shell 注入。不是只有 Python 代码才管这个事。
性能影响不大,但一旦被利用,攻击者可能窃取 CI token、污染镜像、甚至反向连接构建机。
实操建议:
- GitHub Actions 中,避免在
run:下直接插值命令;改用env:传参 + 脚本内校验,或用steps:分离可信命令与数据 - Dockerfile 中禁用未声明的
ARG或ENV变量参与RUN;必须用时,先sed或awk做格式校验(如只允许字母数字和下划线) - GitLab CI 的
variables:若引用外部值,务必配合rules:的正则匹配做准入控制,而不是靠注释写“请勿输入特殊字符”
命令注入真正的麻烦点不在技术多难,而在于它常常跨层存在:前端传参、Web 框架接收、业务逻辑拼接、子进程执行、甚至容器构建阶段——每个环节都可能成为漏网之鱼。最易被忽略的是那些“只读操作”,比如 cat /etc/passwd | grep $USER,人总觉得没写权限就没事,但攻击者早就能用 $USER 把命令拖进后台执行了。










