diff --git a/README.md b/README.md index 75bb994..164a503 100644 --- a/README.md +++ b/README.md @@ -29,18 +29,37 @@ Based on OWASP Top 10:2025 and recent security research. | [jwt-security.md](jwt-security.md) | Algorithm confusion, weak secrets, expiration | A07 | | [session-management.md](session-management.md) | Session fixation, hijacking, secure cookies | A07 | -### Attack Prevention +### Injection & Request Attacks | File | Topic | OWASP 2025 | |------|-------|------------| | [injection-prevention.md](injection-prevention.md) | SQL, command, template, path traversal | A05 | | [ssrf.md](ssrf.md) | Server-side request forgery, metadata endpoints | A10 | | [xxe.md](xxe.md) | XML external entities, DTD attacks | A05 | -| [dos-prevention.md](dos-prevention.md) | Rate limiting, resource bounds, algorithmic complexity | — | -| [prompt-injection.md](prompt-injection.md) | LLM security, data/instruction separation | — | | [deserialization.md](deserialization.md) | Untrusted data deserialization, pickle, yaml | A08 | -| [race-conditions.md](race-conditions.md) | TOCTOU, atomic check-and-act, database locks | — | +| [open-redirect.md](open-redirect.md) | URL validation, OAuth redirect URI | A01 | + +### Client-Side Security + +| File | Topic | OWASP 2025 | +|------|-------|------------| +| [csp.md](csp.md) | Content Security Policy, nonces, hashes | A05 | | [cors.md](cors.md) | Origin validation, credential handling | A01 | +| [clickjacking.md](clickjacking.md) | X-Frame-Options, frame-ancestors | A01 | + +### Application Logic + +| File | Topic | OWASP 2025 | +|------|-------|------------| +| [race-conditions.md](race-conditions.md) | TOCTOU, atomic check-and-act, database locks | — | +| [dos-prevention.md](dos-prevention.md) | Rate limiting, resource bounds, algorithmic complexity | — | +| [file-upload.md](file-upload.md) | Content validation, safe storage, malware scanning | A04 | + +### AI/LLM Security + +| File | Topic | OWASP 2025 | +|------|-------|------------| +| [prompt-injection.md](prompt-injection.md) | LLM security, data/instruction separation | — | ### Infrastructure @@ -51,18 +70,18 @@ Based on OWASP Top 10:2025 and recent security research. ## OWASP Top 10:2025 Coverage -| # | Category | Pattern | -|---|----------|---------| -| A01 | Broken Access Control | authorization.md, cors.md | -| A02 | Security Misconfiguration | secure-defaults.md | -| A03 | Software Supply Chain Failures | supply-chain.md | -| A04 | Cryptographic Failures | cryptography.md | -| A05 | Injection | injection-prevention.md, xxe.md | -| A06 | Insecure Design | secure-defaults.md | -| A07 | Authentication Failures | authentication.md, jwt-security.md, session-management.md | -| A08 | Software or Data Integrity Failures | deserialization.md | -| A09 | Security Logging and Alerting Failures | audit-logging.md | -| A10 | Mishandling of Exceptional Conditions | error-handling.md, ssrf.md | +| # | Category | Patterns | +|---|----------|----------| +| A01 | Broken Access Control | authorization, cors, clickjacking, open-redirect | +| A02 | Security Misconfiguration | secure-defaults | +| A03 | Software Supply Chain Failures | supply-chain | +| A04 | Cryptographic Failures | cryptography, file-upload | +| A05 | Injection | injection-prevention, xxe, csp | +| A06 | Insecure Design | secure-defaults | +| A07 | Authentication Failures | authentication, jwt-security, session-management | +| A08 | Software or Data Integrity Failures | deserialization | +| A09 | Security Logging and Alerting Failures | audit-logging | +| A10 | Mishandling of Exceptional Conditions | error-handling, ssrf | ## Sources diff --git a/clickjacking.md b/clickjacking.md new file mode 100644 index 0000000..ab6ba5f --- /dev/null +++ b/clickjacking.md @@ -0,0 +1,174 @@ +# Clickjacking + +## Rule + +Set X-Frame-Options or frame-ancestors CSP. Prevent your site from being embedded in attacker frames. + +**Source:** [OWASP Clickjacking Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html) + +## How Clickjacking Works + +1. Attacker creates page with invisible iframe containing your site +2. Attacker overlays convincing UI elements +3. User thinks they're clicking attacker's button +4. Actually clicking your site's button (delete, transfer, etc.) + +```html + + +
+ +``` + +## Correct Pattern + +```python +# Option 1: X-Frame-Options header (legacy, still works) +@app.after_request +def add_frame_options(response): + response.headers["X-Frame-Options"] = "DENY" + # Or "SAMEORIGIN" to allow same-origin framing + return response + +# Option 2: CSP frame-ancestors (modern, more flexible) +@app.after_request +def add_csp(response): + response.headers["Content-Security-Policy"] = "frame-ancestors 'none'" + # Or "frame-ancestors 'self'" for same-origin + # Or "frame-ancestors 'self' https://trusted.com" for specific sites + return response + +# Option 3: Both (for browser compatibility) +@app.after_request +def add_framing_protection(response): + response.headers["X-Frame-Options"] = "DENY" + response.headers["Content-Security-Policy"] = "frame-ancestors 'none'" + return response +``` + +## Incorrect Pattern + +```python +# Wrong: no framing protection at all +# (missing headers) + +# Wrong: JavaScript frame-busting only +# Can be bypassed with sandbox attribute +""" + +""" +# Bypassed by: + +# Wrong: ALLOWALL (defeats the purpose) +response.headers["X-Frame-Options"] = "ALLOWALL" + +# Wrong: checking via JavaScript after load +# Attacker can disable JS or race the check +``` + +## When Framing IS Needed + +```python +# If you need to allow specific partners to embed: + +ALLOWED_FRAME_ANCESTORS = ["https://partner1.com", "https://partner2.com"] + +@app.after_request +def conditional_framing(response): + # Pages that should never be framed + if request.path.startswith("/admin") or request.path.startswith("/settings"): + response.headers["Content-Security-Policy"] = "frame-ancestors 'none'" + + # Embeddable widgets + elif request.path.startswith("/embed/"): + ancestors = " ".join(ALLOWED_FRAME_ANCESTORS) + response.headers["Content-Security-Policy"] = f"frame-ancestors {ancestors}" + + # Default: same-origin only + else: + response.headers["Content-Security-Policy"] = "frame-ancestors 'self'" + + return response +``` + +## Double-Framing Defense + +```python +# Attacker might try: evil.com -> trusted.com -> your-site.com +# frame-ancestors 'self' https://trusted.com would allow this! + +# Defense: Only allow direct framing +@app.after_request +def strict_framing(response): + # Check if request came from an allowed embedder + # Note: Referer can be spoofed, this is defense-in-depth + referer = request.headers.get("Referer", "") + + if is_embed_request(request): + if not any(referer.startswith(a) for a in ALLOWED_FRAME_ANCESTORS): + response.headers["Content-Security-Policy"] = "frame-ancestors 'none'" + return response + + # Also set on response so browsers enforce + ancestors = " ".join(ALLOWED_FRAME_ANCESTORS) + response.headers["Content-Security-Policy"] = f"frame-ancestors {ancestors}" + + return response +``` + +## Sensitive Actions + +```python +# Clickjacking is most dangerous for state-changing actions +# Add extra protection for these: + +def require_confirmation(f): + """Require explicit confirmation for sensitive actions.""" + @wraps(f) + def decorated(*args, **kwargs): + # Require POST with CSRF token + if request.method != "POST": + abort(405) + + # Verify CSRF + if not validate_csrf_token(request.form.get("csrf_token")): + abort(403) + + # Optional: require re-authentication for very sensitive actions + # Optional: add CAPTCHA + + return f(*args, **kwargs) + return decorated + +@app.route("/account/delete", methods=["POST"]) +@require_confirmation +def delete_account(): + # Clickjacking can't easily bypass POST + CSRF + pass +``` + +## Edge Cases + +- Mobile apps using WebViews may legitimately embed your site +- PDF embedding (`