Python Dangerous System Call with Tainted Environment Arguments

Critical Risk Command Injection
PythonSystem CallsEnvironment VariablesCommand InjectionTainted Inputos.system

What it is

Application uses system calls (os.system, subprocess, etc.) with environment variables or arguments derived from user input, potentially allowing command injection or privilege escalation attacks.

import os
from flask import request

@app.route('/system_call')
def system_call():
    # Vulnerable: User input in environment affects system call
    user_path = request.args.get('path')
    os.environ['CUSTOM_PATH'] = user_path  # Tainted environment
    
    # Dangerous: System call with tainted environment
    result = os.system('echo $CUSTOM_PATH')  # Command injection risk
    return f'Result: {result}'

@app.route('/execute_with_env')
def execute_with_env():
    # Vulnerable: Environment variables from user input
    env_vars = request.get_json() or {}
    for key, value in env_vars.items():
        os.environ[key] = str(value)  # Dangerous assignment
    
    # System call inherits tainted environment
    os.system('printenv | grep USER_')  # Can execute malicious commands
    return 'Environment updated'

@app.route('/run_script')
def run_script():
    # Vulnerable: Script execution with user-controlled environment
    script_env = request.args.get('env')
    os.environ['SCRIPT_CONFIG'] = script_env
    
    # Dangerous: Shell command with environment dependency
    return os.system('bash -c "echo Processing: $SCRIPT_CONFIG"')
import subprocess
import re
from flask import request

# Safe environment allowlist
SAFE_ENV_PATTERN = re.compile(r'^[a-zA-Z0-9_\-\.]+$')
ALLOWED_ENV_VARS = ['LANG', 'LC_ALL', 'TZ', 'USER']

@app.route('/system_call')
def system_call():
    # Secure: Validate and sanitize user input
    user_path = request.args.get('path', '')
    
    # Validate path format
    if not re.match(r'^[a-zA-Z0-9/_\-\.]+$', user_path):
        return 'Invalid path format', 400
    
    # Use safe subprocess instead of os.system
    try:
        result = subprocess.run(
            ['echo', user_path],  # Safe argument list
            capture_output=True,
            text=True,
            timeout=5,
            env={'PATH': '/usr/bin:/bin'}  # Fixed safe environment
        )
        return f'Result: {result.stdout}'
    except subprocess.TimeoutExpired:
        return 'Command timeout', 408

@app.route('/execute_with_env')
def execute_with_env():
    # Secure: Validate environment variables
    env_vars = request.get_json() or {}
    safe_env = {'PATH': '/usr/bin:/bin'}  # Start with safe base
    
    for key, value in env_vars.items():
        # Validate environment variable name and value
        if (key in ALLOWED_ENV_VARS and 
            isinstance(value, str) and 
            len(value) <= 100 and 
            SAFE_ENV_PATTERN.match(value)):
            safe_env[key] = value
        else:
            return f'Invalid environment variable: {key}', 400
    
    # Safe subprocess with validated environment
    try:
        result = subprocess.run(
            ['printenv'],  # Safe command
            capture_output=True,
            text=True,
            env=safe_env,
            timeout=5
        )
        return f'Environment: {result.stdout}'
    except subprocess.TimeoutExpired:
        return 'Command timeout', 408

@app.route('/run_script')
def run_script():
    # Secure: Use predefined safe configuration
    script_config = request.args.get('config', '')
    
    # Validate configuration value
    allowed_configs = ['production', 'staging', 'development']
    if script_config not in allowed_configs:
        return 'Invalid configuration', 400
    
    # Safe execution with fixed environment
    safe_env = {
        'PATH': '/usr/bin:/bin',
        'SCRIPT_CONFIG': script_config,  # Validated value
        'HOME': '/tmp'
    }
    
    try:
        result = subprocess.run(
            ['/opt/scripts/safe_processor.sh'],  # Fixed safe script
            capture_output=True,
            text=True,
            env=safe_env,
            timeout=30
        )
        return f'Processing result: {result.stdout}'
    except subprocess.TimeoutExpired:
        return 'Script timeout', 408

# Secure helper function for environment validation
def create_safe_system_environment(user_vars=None):
    """Create a safe environment for system calls."""
    base_env = {
        'PATH': '/usr/bin:/bin',
        'HOME': '/tmp',
        'SHELL': '/bin/bash',
        'LANG': 'en_US.UTF-8'
    }
    
    if user_vars:
        for key, value in user_vars.items():
            if (key in ALLOWED_ENV_VARS and 
                isinstance(value, str) and 
                len(value) <= 100 and 
                SAFE_ENV_PATTERN.match(value)):
                base_env[key] = value
    
    return base_env

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Code calls os.system() while environment contains user data: os.environ['PATH'] = user_path; os.system('command'). os.system() inherits environment, executes through shell. User-controlled PATH loads malicious binaries. LD_PRELOAD, IFS, or other variables modify command behavior enabling code execution.

Root causes

Using os.system() with Commands Affected by Environment Variables

Code calls os.system() while environment contains user data: os.environ['PATH'] = user_path; os.system('command'). os.system() inherits environment, executes through shell. User-controlled PATH loads malicious binaries. LD_PRELOAD, IFS, or other variables modify command behavior enabling code execution.

Modifying os.environ with User Input Before System Calls

Setting environment from user data: os.environ['CONFIG'] = request.args['config']; os.system('tool'). Global environment affects all subsequent system calls. User input propagates to shell commands. Race conditions in concurrent environments. Tainted environment enables persistent injection across multiple operations.

Using os.popen() or os.spawn*() with User-Controlled Environment

Legacy functions inherit environment: os.popen('command') after os.environ modified. os.spawnlpe() explicitly takes environment: os.spawnlpe(os.P_WAIT, 'cmd', user_env). User-controlled environment affects command execution. These functions execute shell or load binaries from PATH, vulnerable to environment manipulation.

Not Isolating Environment for Privileged Operations

Executing privileged commands with inherited environment: os.setuid(0); os.system('admin_tool'). Elevated privileges with user-influenced environment compounds risk. Privilege escalation through environment injection. Privileged operations require clean controlled environment, not inherited state from user requests.

Using Shell Metacharacters with Tainted Environment Variables

Environment variables expanded in shell commands: os.system('echo $USER_CONFIG'). If USER_CONFIG set from user input earlier, shell expands malicious content. Command substitution, wildcards, or special characters in environment values execute during shell parsing. Combination of user environment and shell creates injection.

Fixes

1

Never Use os.system(), os.popen(), or os.spawn*() Functions

Replace with subprocess: subprocess.run(['command'], env={'PATH': '/usr/bin'}, shell=False). Avoid os.system() entirely. subprocess.run() with explicit environment and shell=False prevents environment injection. Legacy os functions lack security controls. Modern subprocess module provides safe alternatives.

2

Never Modify os.environ with User-Controlled Data

Keep os.environ untainted: never os.environ[key] = user_value. Use local environment dicts for subprocess: my_env = {'PATH': '/usr/bin'}; subprocess.run(cmd, env=my_env). Isolate user data from global environment. Prevents persistent pollution affecting multiple operations.

3

Provide Explicit Clean Environment to All Subprocess Calls

Set minimal hardcoded environment: clean_env = {'PATH': '/usr/bin:/bin', 'HOME': '/tmp', 'LANG': 'C'}; subprocess.run(cmd, env=clean_env, shell=False). Don't inherit parent environment. Include only necessary variables with static values. Explicit environment prevents tainted variable inheritance.

4

Validate and Sanitize External Environment Sources

If environment from config, validate strictly: SAFE_VARS = {'LANG', 'TZ'}; safe_env = {k: v for k, v in config_env.items() if k in SAFE_VARS and is_safe_value(v)}. Allowlist variable names. Validate values. Reject dangerous variables like PATH, LD_*. Schema validation for configuration.

5

Isolate Privileged Operations with Clean Environment

Before privilege escalation, set clean environment: clean_env = {'PATH': '/usr/bin'}; subprocess.run(['privileged_cmd'], env=clean_env, user='root'). Never inherit environment for privileged operations. Drop all user-influenced variables. Use principle of least privilege with minimal environment.

6

Use Container or Sandbox Isolation for Untrusted Code

Run commands in containers: subprocess.run(['docker', 'run', '--env', 'VAR=value', 'image', 'command']). Container provides clean environment, isolated filesystem. Use seccomp, AppArmor for system call filtering. Sandbox libraries like pysandbox. Isolation prevents environment injection from affecting host system.

Detect This Vulnerability in Your Code

Sourcery automatically identifies python dangerous system call with tainted environment arguments and many other security issues in your codebase.