首页/安全/漏洞与攻击面速查/安全调用外部命令的工程实践
Web 漏洞原理与防护55 条

安全调用外部命令的工程实践

运维工具类系统常需调用外部命令(图像处理、格式转换、备份脚本);应建立统一封装:固定命令与参数白名单、超时、资源限制与输出净化。

漏洞与攻击面速查安全更新于 2026-10-13

麻烦出在哪里

运维工具类系统常需要调用外部命令:图片处理、格式转换、日志打包、备份脚本。风险不在于『调用命令』本身,而在于参数被拼接进命令行字符串——一旦用户输入混进命令串,就能改变命令语义。稳妥做法是建立统一封装:命令白名单、参数以数组形式传递、超时与输出限制、执行账号降权。

基本信息

常见语言Python subprocess、Java ProcessBuilder、Node child_process
核心原则参数以数组传值,不经 shell 解释
禁止用法shell=True、字符串拼接命令、eval 式执行
资源限制超时、输出大小上限、CPU 与内存约束
权限要求专用低权限账号运行,限定工作目录

典型使用场景

  • 图像处理:生成缩略图、格式转换、水印叠加
  • 文档转换:Office 转 PDF、PDF 合并与拆分
  • 备份与打包:调用 tar、rsync、mysqldump 等工具
  • 音视频处理:调用 ffmpeg 转码或截帧
  • 运维脚本:调用系统工具采集信息,如容量与状态检查

统一封装示例

统一封装示例
import subprocess

ALLOWED = {'convert', 'ffmpeg', 'tar'}          # 允许调用的命令白名单

def run_tool(tool, args, timeout=30, max_out=1_000_000):
    if tool not in ALLOWED:
        raise ValueError('tool not allowed')      # 不在白名单直接拒绝
    proc = subprocess.run([tool, *args],          # 数组传参,不经 shell
                          capture_output=True, timeout=timeout, check=False,
                          cwd='/srv/tmp')         # 固定工作目录
    if len(proc.stdout) > max_out:
        raise RuntimeError('output too large')
    return proc.returncode, proc.stdout

写法与风险对照

写法风险替代做法
shell 字符串拼接输入可改变命令语义数组传参,参数作为独立元素
使用 shell=True管道与重定向符号被解释关闭 shell,改用语言内置能力
命令名来自用户输入任意命令执行命令名固定,仅参数可控
无超时无输出限制进程挂起、磁盘被写满设超时与输出上限
以 root 执行一旦被利用即最高权限专用低权限账号,限定目录
把命令执行集中到单一封装函数,便于审计与静态检查:检索代码中的进程调用点,确认全部走了封装,而不是散落在业务逻辑里。
即使参数来自内部系统,也要按不可信输入对待:上游接口被污染时,内部参数照样会带上攻击载荷,白名单与超时是最后一道防线。

易错点

  • 统一了封装函数但参数仍做字符串拼接:函数化不等于安全,参数必须保持值的语义
  • 用黑名单过滤分号与管道符:编码与字符集变体可绕过,应改为白名单加数组传参
  • 忘记设置超时:被调命令卡住时会占用工作进程,最终拖垮整个应用
  • 封装函数用管理员权限运行:一旦被绕过,影响范围从业务扩展到整台主机

数据来源与参考

  • OWASP OS Command Injection Defense Cheat Sheet
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌