Initial commit: 9 security patterns for code review
Fundamentals: secure-defaults, input-validation, credential-handling, audit-logging Identity: authentication, authorization Attack Prevention: injection-prevention, dos-prevention, prompt-injection
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user