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.
This commit is contained in:
+174
@@ -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
|
||||
<!-- Attacker's page -->
|
||||
<style>
|
||||
iframe {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
.fake-button {
|
||||
position: absolute;
|
||||
top: 200px; left: 300px; /* Aligned with real button */
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
<div class="fake-button">Click to win a prize!</div>
|
||||
<iframe src="https://bank.com/transfer?to=attacker&amount=10000"></iframe>
|
||||
```
|
||||
|
||||
## 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
|
||||
"""
|
||||
<script>
|
||||
if (top !== self) {
|
||||
top.location = self.location;
|
||||
}
|
||||
</script>
|
||||
"""
|
||||
# Bypassed by: <iframe src="bank.com" sandbox="allow-forms"></iframe>
|
||||
|
||||
# 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 (`<embed>`, `<object>`) 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 `<meta>` tag
|
||||
- Cursorjacking: manipulating cursor position (similar attack)
|
||||
- Likejacking: clicking social media Like buttons
|
||||
Reference in New Issue
Block a user