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.