# Injection Prevention ## Rule Never concatenate untrusted input into commands, queries, or templates. Use parameterized APIs. **Source:** [OWASP Injection](https://owasp.org/Top10/A03_2021-Injection/) ## SQL Injection ### Correct Pattern ```python # Parameterized query — safe def get_user(user_id: int): cursor.execute( "SELECT * FROM users WHERE id = %s", (user_id,) ) return cursor.fetchone() # ORM — safe def get_user(user_id: int): return User.query.filter_by(id=user_id).first() ``` ### Incorrect Pattern ```python # Wrong: string concatenation def get_user(user_id): cursor.execute(f"SELECT * FROM users WHERE id = {user_id}") # Input: "1; DROP TABLE users; --" # Wrong: string formatting query = "SELECT * FROM users WHERE name = '%s'" % name ``` ## Command Injection ### Correct Pattern ```python import subprocess import shlex # Use list form — shell=False prevents injection def run_command(filename: str): result = subprocess.run( ["ls", "-la", filename], capture_output=True, shell=False # Critical! ) return result.stdout # If you must use shell, validate strictly VALID_FILENAME = re.compile(r'^[a-zA-Z0-9._-]+$') def safe_filename(name: str) -> str: if not VALID_FILENAME.match(name): raise ValueError("Invalid filename") return name ``` ### Incorrect Pattern ```python # Wrong: shell=True with user input subprocess.run(f"ls -la {filename}", shell=True) # Input: "file.txt; rm -rf /" # Wrong: os.system os.system(f"convert {input_file} {output_file}") ``` ## Template Injection ### Correct Pattern ```python # Use auto-escaping templates from jinja2 import Environment, select_autoescape env = Environment(autoescape=select_autoescape(['html', 'xml'])) template = env.get_template("page.html") output = template.render(user_name=user_input) # Auto-escaped ``` ### Incorrect Pattern ```python # Wrong: rendering user input as template template = Template(user_input) # SSTI vulnerability # Wrong: disabling auto-escape template.render(content=Markup(user_input)) ``` ## Path Traversal ### Correct Pattern ```python import os from pathlib import Path UPLOAD_DIR = Path("/app/uploads").resolve() def safe_path(filename: str) -> Path: """Ensure path stays within allowed directory.""" # Resolve to absolute, normalized path requested = (UPLOAD_DIR / filename).resolve() # Verify it's still under UPLOAD_DIR if not requested.is_relative_to(UPLOAD_DIR): raise ValueError("Path traversal detected") return requested ``` ### Incorrect Pattern ```python # Wrong: direct concatenation path = f"/app/uploads/{filename}" # Input: "../../../etc/passwd" # Wrong: checking for ".." without resolving if ".." not in filename: # Can bypass with encoding open(f"/uploads/{filename}") ``` ## Edge Cases - Second-order injection (stored, then executed later) - Polyglot payloads (valid in multiple contexts) - Encoding bypasses (URL, Unicode, hex) - Blind injection (no visible output)