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.