From 1eac5d3bcc0b6fe3831d6505f51106a327ad84f3 Mon Sep 17 00:00:00 2001 From: Rodin Date: Sun, 10 May 2026 23:24:52 -0700 Subject: [PATCH] Add CSP, file upload, open redirect, clickjacking patterns Complete security patterns collection (23 total): - csp.md: nonces, hashes, strict-dynamic, reporting - file-upload.md: content validation, path traversal, malware scanning - open-redirect.md: URL validation, OAuth redirect URI, bypass techniques - clickjacking.md: X-Frame-Options, frame-ancestors CSP Comprehensive coverage for web application security review. --- README.md | 51 ++++++++---- clickjacking.md | 174 ++++++++++++++++++++++++++++++++++++++++ csp.md | 166 ++++++++++++++++++++++++++++++++++++++ file-upload.md | 205 +++++++++++++++++++++++++++++++++++++++++++++++ open-redirect.md | 188 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 768 insertions(+), 16 deletions(-) create mode 100644 clickjacking.md create mode 100644 csp.md create mode 100644 file-upload.md create mode 100644 open-redirect.md 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 + + +
Click to win a prize!
+ +``` + +## 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 (``, ``) not covered by frame-ancestors +- Legacy IE doesn't support CSP frame-ancestors, needs X-Frame-Options +- frame-ancestors must be in HTTP header, not `` tag +- Cursorjacking: manipulating cursor position (similar attack) +- Likejacking: clicking social media Like buttons diff --git a/csp.md b/csp.md new file mode 100644 index 0000000..b8d5908 --- /dev/null +++ b/csp.md @@ -0,0 +1,166 @@ +# Content Security Policy (CSP) + +## Rule + +Define strict CSP to prevent XSS. Start restrictive, loosen only as needed. Never use `unsafe-inline` for scripts. + +**Source:** [MDN Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) + +## CSP Directives + +| Directive | Controls | +|-----------|----------| +| `default-src` | Fallback for all resource types | +| `script-src` | JavaScript sources | +| `style-src` | CSS sources | +| `img-src` | Image sources | +| `connect-src` | XHR, fetch, WebSocket | +| `frame-src` | iframe sources | +| `frame-ancestors` | Who can embed this page | +| `form-action` | Form submission targets | +| `base-uri` | `` tag restrictions | + +## Correct Pattern + +```python +# Strict CSP with nonces (recommended) +import secrets + +def generate_csp_nonce() -> str: + return secrets.token_urlsafe(16) + +def get_csp_header(nonce: str) -> str: + """Generate strict CSP header.""" + return "; ".join([ + "default-src 'self'", + f"script-src 'nonce-{nonce}' 'strict-dynamic'", + "style-src 'self' 'nonce-{nonce}'", + "img-src 'self' data: https:", + "font-src 'self'", + "connect-src 'self' https://api.example.com", + "frame-ancestors 'none'", + "form-action 'self'", + "base-uri 'self'", + "upgrade-insecure-requests", + ]) + +@app.after_request +def add_security_headers(response): + nonce = generate_csp_nonce() + g.csp_nonce = nonce # Make available to templates + response.headers["Content-Security-Policy"] = get_csp_header(nonce) + return response + +# In template: +# +``` + +## Incorrect Pattern + +```python +# Wrong: unsafe-inline allows XSS +csp = "script-src 'self' 'unsafe-inline'" + +# Wrong: unsafe-eval allows eval() +csp = "script-src 'self' 'unsafe-eval'" + +# Wrong: wildcard allows any source +csp = "script-src *" + +# Wrong: no CSP at all +# (missing header) + +# Wrong: report-only without enforcement +# Use for testing, but deploy with enforcement +response.headers["Content-Security-Policy-Report-Only"] = csp +# ^ Only reports, doesn't block! + +# Wrong: data: in script-src +csp = "script-src 'self' data:" +# Attacker can inject: