Python exec() Function Detection Vulnerability

Critical Risk Code Injection
PythonexecCode InjectionRemote Code ExecutionDynamic Code ExecutionInput Validation

What it is

Application uses the exec() function which can execute arbitrary Python statements, creating critical security risks including remote code execution when processing untrusted input.

from flask import Flask, request

@app.route('/execute_code')
def execute_code():
    # Vulnerable: exec() with user input allows arbitrary code execution
    user_code = request.form.get('code')
    try:
        # EXTREMELY DANGEROUS: Direct code execution
        exec(user_code)  # Complete RCE vulnerability
        return 'Code executed successfully'
    except Exception as e:
        return f'Execution error: {str(e)}', 400

@app.route('/dynamic_function')
def dynamic_function():
    # Vulnerable: Building and executing function definitions
    func_name = request.args.get('name')
    func_body = request.args.get('body')
    
    # Dangerous: Creating functions dynamically
    code = f'''def {func_name}():
    {func_body}

result = {func_name}()'''
    
    local_vars = {}
    exec(code, {}, local_vars)  # RCE through function creation
    return str(local_vars.get('result', 'No result'))

@app.route('/template_exec')
def template_exec():
    # Vulnerable: Template-like execution
    template = request.form.get('template')
    variables = {'user': 'admin', 'role': 'user'}
    
    # Dangerous: exec() for template processing
    try:
        exec(template, variables)  # Can execute arbitrary code
        return f'Template processed: {variables}'
    except:
        return 'Template error', 400

# Vulnerable: Dynamic module execution
def load_plugin(plugin_code):
    namespace = {}
    exec(plugin_code, namespace)  # Arbitrary code execution
    return namespace.get('plugin_function', lambda: None)
from flask import Flask, request
import importlib
import json
import re
from types import ModuleType

# Safe function registry
SAFE_FUNCTIONS = {
    'greet': lambda name: f'Hello, {name}!' if isinstance(name, str) else 'Hello!',
    'add': lambda a, b: a + b if isinstance(a, (int, float)) and isinstance(b, (int, float)) else 0,
    'multiply': lambda a, b: a * b if isinstance(a, (int, float)) and isinstance(b, (int, float)) else 0,
    'format_text': lambda text: text.strip().title() if isinstance(text, str) else ''
}

@app.route('/execute_code')
def execute_code():
    # Secure: Use predefined safe functions instead of exec()
    function_name = request.form.get('function', '')
    args_json = request.form.get('args', '[]')
    
    if function_name not in SAFE_FUNCTIONS:
        return 'Function not allowed', 400
    
    try:
        args = json.loads(args_json)
        if not isinstance(args, list) or len(args) > 5:
            return 'Invalid arguments', 400
        
        result = SAFE_FUNCTIONS[function_name](*args)
        return f'Result: {result}'
    except (json.JSONDecodeError, TypeError, ValueError) as e:
        return f'Error: {str(e)}', 400

@app.route('/dynamic_function')
def dynamic_function():
    # Secure: Use configuration-based approach
    operation = request.args.get('operation', '')
    operand1 = request.args.get('a', '0')
    operand2 = request.args.get('b', '0')
    
    # Validate operation
    allowed_operations = ['add', 'subtract', 'multiply', 'divide']
    if operation not in allowed_operations:
        return 'Operation not allowed', 400
    
    try:
        a = float(operand1)
        b = float(operand2)
        
        # Safe operation mapping
        operations = {
            'add': lambda x, y: x + y,
            'subtract': lambda x, y: x - y,
            'multiply': lambda x, y: x * y,
            'divide': lambda x, y: x / y if y != 0 else 'Division by zero'
        }
        
        result = operations[operation](a, b)
        return f'Result: {result}'
    except ValueError:
        return 'Invalid numeric input', 400
    except ZeroDivisionError:
        return 'Division by zero', 400

@app.route('/template_exec')
def template_exec():
    # Secure: Use safe template substitution
    template = request.form.get('template', '')
    
    # Validate template format (only allow simple variable substitution)
    if not re.match(r'^[a-zA-Z0-9 {}_.,-]+$', template):
        return 'Invalid template format', 400
    
    # Safe variable substitution
    safe_variables = {
        'user': 'admin',
        'role': 'user',
        'timestamp': '2024-01-01',
        'version': '1.0.0'
    }
    
    try:
        # Safe template formatting
        result = template.format(**safe_variables)
        return f'Template result: {result}'
    except (KeyError, ValueError) as e:
        return f'Template error: {str(e)}', 400

# Secure: Safe plugin loading using importlib
def load_safe_plugin(plugin_name):
    """Safely load plugins from a trusted directory."""
    # Validate plugin name
    if not re.match(r'^[a-zA-Z0-9_]+$', plugin_name):
        raise ValueError('Invalid plugin name')
    
    try:
        # Safe import from trusted location
        module_name = f'plugins.{plugin_name}'
        module = importlib.import_module(module_name)
        
        # Validate required interface
        if not hasattr(module, 'plugin_function'):
            raise AttributeError('Plugin missing required interface')
        
        return getattr(module, 'plugin_function')
    except ImportError:
        raise ImportError(f'Plugin {plugin_name} not found')

@app.route('/load_plugin')
def load_plugin_endpoint():
    # Secure: Controlled plugin loading
    plugin_name = request.args.get('plugin', '')
    
    # Allowlist of permitted plugins
    allowed_plugins = ['math_utils', 'text_processor', 'data_validator']
    
    if plugin_name not in allowed_plugins:
        return 'Plugin not allowed', 400
    
    try:
        plugin_func = load_safe_plugin(plugin_name)
        # Call plugin with safe parameters
        result = plugin_func({'safe': True, 'version': '1.0'})
        return f'Plugin result: {result}'
    except (ImportError, AttributeError, ValueError) as e:
        return f'Plugin error: {str(e)}', 400

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Code executes user input: exec(request.data.decode()). exec() runs arbitrary Python statements. Attackers submit malicious code accessing files, network, or executing commands. Unlike eval(), exec() supports full statement syntax including imports, loops, function definitions. Complete code execution vector.

Root causes

Using exec() to Execute User-Supplied Code Strings

Code executes user input: exec(request.data.decode()). exec() runs arbitrary Python statements. Attackers submit malicious code accessing files, network, or executing commands. Unlike eval(), exec() supports full statement syntax including imports, loops, function definitions. Complete code execution vector.

Using exec() for Dynamic Code Generation from Templates

Generating code from templates with user data: code = template.format(**user_vars); exec(code). Template injection enables code execution. Even with validation, complex templates hard to secure. User data in code strings creates injection. Dynamic code generation requires proper escaping or safer alternatives.

Loading and Executing Plugin Code with exec()

Plugin systems using exec(): exec(open(plugin_file).read()). Executing scripts from user-uploaded files or external sources. No sandboxing or isolation. Plugins have full application access. exec() without restrictions gives plugins complete control. Plugin architectures require proper security boundaries.

Using exec() with Insufficient Namespace Isolation

Attempting isolation with globals/locals: exec(code, {'__builtins__': {}}). Namespace restrictions bypassable through object introspection: ().__class__.__bases__[0].__subclasses__(). Accessing built-ins through inheritance chain. Dictionary-based isolation insufficient. exec() namespace restrictions provide weak security.

Using exec() for Configuration or Dynamic Imports

Config files as Python code: exec(open('config.py').read()). Dynamic imports: exec(f'from {module_name} import *'). Config should use data formats (JSON, YAML), not code. Dynamic imports better handled with importlib. exec() for configuration enables code injection through config file manipulation.

Fixes

1

Never Use exec() on Untrusted Input Under Any Circumstances

Completely avoid exec() with user data. No validation makes exec() safe for untrusted input. Remove exec() from code paths handling user input. If code execution required, use sandboxed environments, separate processes, or domain-specific languages. exec() inherently insecure with untrusted data.

2

Use importlib for Dynamic Module Loading

Replace exec imports with importlib: import importlib; module = importlib.import_module(module_name). Safe dynamic imports. Validate module names against allowlist. Use __import__() only for trusted module names. importlib provides structured, safer dynamic imports than exec().

3

Use Configuration File Formats, Not Python Code

Replace Python config files with JSON, YAML, or TOML: import json; config = json.load(open('config.json')). Data-only formats prevent code execution. Use configparser for INI files. Pydantic or dataclasses for typed configuration. Config should be data, not executable code.

4

Implement Plugin Systems with Process Isolation

Run plugins in separate processes: subprocess.run(['python', plugin_script], timeout=30). Use containers for isolation. Implement RPC for plugin communication. RestrictedPython for in-process sandboxing as last resort. Proper plugin architecture prevents plugins from accessing main application. Isolation critical for plugin security.

5

Use AST Manipulation Instead of exec() for Code Generation

Generate code with AST: import ast; tree = ast.parse(template); modified = ast.NodeTransformer().visit(tree). Programmatic code construction safer than string templates. Compile AST: compile(tree, '<generated>', 'exec'). AST manipulation provides structure, type safety. Still requires care, but better than string-based code generation.

6

Scan for exec() Usage with Static Analysis

Use bandit to detect exec(): bandit -r . finds exec() usage. Add security linters to CI/CD. Fail builds with exec() in user-facing code. Use pre-commit hooks. Code review for any exec() additions. Static analysis prevents accidental introduction of exec() vulnerabilities.

Detect This Vulnerability in Your Code

Sourcery automatically identifies python exec() function detection vulnerability and many other security issues in your codebase.