647928a0a1
Fundamentals: secure-defaults, input-validation, credential-handling, audit-logging Identity: authentication, authorization Attack Prevention: injection-prevention, dos-prevention, prompt-injection
139 lines
3.0 KiB
Markdown
139 lines
3.0 KiB
Markdown
# 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)
|