# 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