Clickjacking Prevention: X-Frame-Options, CSP & UI Redressing
Clickjacking prevention is a critical yet often overlooked aspect of web application security. A clickjacking attack — also called a UI redressing attack — tricks users into clicking on something different from what they perceive, by overlaying a transparent iframe over a legitimate-looking UI. One click can unknowingly authorize a payment, change account settings, or enable a webcam.
Find out if your site is vulnerable to clickjacking in seconds — [scan it free with ZeriFlow](https://zeriflow.com).
How Clickjacking Attacks Work
The mechanics are deceptively simple. An attacker creates a malicious page that loads your web application inside an invisible or transparent <iframe>. They then overlay decoy content — a fake button, a game element, a social share prompt — precisely over a sensitive action on your framed page.
<!-- Attacker's malicious page -->
<style>
iframe {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
opacity: 0.0001; /* invisible but clickable */
z-index: 2;
}
.decoy-button {
position: absolute;
top: 200px; left: 300px;
z-index: 1;
}
</style>
<div class='decoy-button'>Click here to claim your prize!</div>
<iframe src='https://victim-bank.com/transfer?to=attacker&amount=1000'></iframe>When the victim clicks the "prize" button, they're actually clicking the bank's transfer confirmation button on the transparent iframe.
Real-World Clickjacking Scenarios
- Like farming: Framing a Facebook Like button to drive fake engagement.
- Webcam/microphone activation: Clickjacking Adobe Flash permission dialogs (historically common).
- Account deletion: Tricking users into clicking "delete my account" confirmations.
- OAuth authorization: Framing an OAuth consent screen to silently grant app permissions.
- Form submissions: Pre-filling and framing forms to extract submitted data.
X-Frame-Options: The Original Defense
X-Frame-Options is an HTTP response header that controls whether a browser is allowed to render a page inside a <frame>, <iframe>, <embed>, or <object>.
X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
X-Frame-Options: ALLOW-FROM https://trusted.comDirective Reference
| Directive | Meaning |
|---|---|
DENY | Page cannot be framed by anyone |
SAMEORIGIN | Page can only be framed by pages on the same origin |
ALLOW-FROM uri | Page can be framed only by the specified URI |
Recommendation: Use DENY for all pages that don't need to be embedded. Use SAMEORIGIN if your own app embeds its pages. Avoid ALLOW-FROM — it's deprecated and not supported in Chrome or Firefox.
Setting X-Frame-Options
Nginx:
add_header X-Frame-Options 'DENY' always;Apache:
Header always set X-Frame-Options "DENY"Express.js (Node.js):
const helmet = require('helmet');
app.use(helmet.frameguard({ action: 'deny' }));Django:
# settings.py
X_FRAME_OPTIONS = 'DENY'
MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# ...
]CSP frame-ancestors: The Modern Standard
While X-Frame-Options is widely supported and still effective, Content Security Policy (CSP) `frame-ancestors` is the modern, more flexible replacement. It's part of the CSP Level 2 specification and takes precedence over X-Frame-Options in browsers that support it.
Content-Security-Policy: frame-ancestors 'none';
Content-Security-Policy: frame-ancestors 'self';
Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.com;Why CSP frame-ancestors Is Superior
- 1Multiple allowed origins:
ALLOW-FROMinX-Frame-Optionsonly supports one URI. CSPframe-ancestorssupports multiple. - 2Wildcard support:
frame-ancestors 'self' https://*.trusted.comallows all subdomains. - 3More granular control: Combine with port and scheme specifications.
- 4CSP violation reporting: Get reports when framing is attempted.
Content-Security-Policy: frame-ancestors 'self' https://app.partner.com; report-uri /csp-violationsDeploying Both Headers
For maximum compatibility (older browsers, IE), deploy both headers:
add_header X-Frame-Options 'DENY' always;
add_header Content-Security-Policy "frame-ancestors 'none'" always;CSP frame-ancestors takes precedence in modern browsers; X-Frame-Options covers the rest.
ZeriFlow Checks Both Headers
ZeriFlow's security scanner automatically checks for:
- Presence of
X-Frame-Optionsheader - Presence and correctness of
frame-ancestorsin your CSP header - Conflicting configurations between the two
Run a free clickjacking scan on your site at ZeriFlow — results in under 60 seconds, no signup required.
JavaScript Frame-Busting: An Unreliable Fallback
Before X-Frame-Options existed, developers used JavaScript "frame-busting" scripts to detect when their page was framed and redirect to the top-level window:
// Old-school frame-busting (DO NOT rely on this)
if (top !== self) {
top.location = self.location;
}This is easily defeated. Attackers can sandbox the iframe to disable JavaScript:
<iframe src='victim.com' sandbox='allow-forms allow-scripts'></iframe>Or use onBeforeUnload tricks. Never rely on JavaScript frame-busting as your primary defense. Use HTTP headers.
Advanced Clickjacking Techniques
Drag-and-Drop Clickjacking
Some attacks use HTML5 drag-and-drop events rather than clicks. The user drags an element on the attacker's page but is actually interacting with a framed target. This can be used to extract text content from framed cross-origin pages.
Mitigation: Same X-Frame-Options/frame-ancestors headers prevent the frame from loading at all.
Multi-Step Clickjacking
Instead of a single click, attackers guide users through multiple interactions — each one triggering a step in a multi-page workflow (like a multi-step bank transfer confirmation). The attacker needs to precisely align each step.
Mitigation: CSRF tokens on each step make it much harder to craft a working multi-step attack even if framing succeeds.
Cursor Jacking
Attackers can manipulate the apparent cursor position using CSS, making users think their cursor is in one location while it's actually elsewhere. Combined with clickjacking, this is particularly deceptive.
Common Clickjacking Mistakes
1. Only protecting login pages Sensitive actions exist throughout your app — password changes, payment confirmations, permission grants. Apply framing protection to all pages, not just login.
2. Using X-Frame-Options on CDN assets Static assets (images, scripts) don't need framing protection. Apply headers selectively to HTML responses.
3. Setting SAMEORIGIN when DENY is appropriate
If your app doesn't use iframes internally, use DENY. SAMEORIGIN only makes sense if you deliberately embed your own pages.
4. Forgetting the `always` flag in Nginx/Apache
Without always, the header isn't sent on error responses (4xx, 5xx). Attackers can sometimes frame error pages to extract information.
5. Not testing after deployment Header misconfiguration is common. Tools like ZeriFlow can catch this automatically.
FAQ
Q: Does HTTPS prevent clickjacking?
A: No. HTTPS secures the transport layer but has no bearing on whether your page can be framed. Clickjacking attacks work perfectly over HTTPS. You need X-Frame-Options or CSP frame-ancestors headers regardless of your SSL configuration.
Q: Will blocking iframes break my legitimate embed functionality?
A: If you need your site to be embeddable (a widget, a map, a video player), use CSP frame-ancestors with specific allowed origins rather than DENY. This gives you fine-grained control — blocking unauthorized framing while allowing trusted partners.
Q: Can mobile apps be clickjacked?
A: Native mobile apps aren't affected since they don't use browser iframes. However, mobile web apps (running in a browser or WebView) are vulnerable to the same clickjacking attacks as desktop web apps. Apply the same header-based protections.
Q: Is clickjacking in the OWASP Top 10?
A: Clickjacking falls under OWASP A05:2021 (Security Misconfiguration) in the current Top 10. It's also historically noted as a significant vulnerability in the OWASP Testing Guide. While not always ranked #1, it's consistently considered a high-priority misconfiguration to fix.
Q: How do I test if my site is vulnerable to clickjacking?
A: The simplest test is to create an HTML file with an iframe pointing to your site and open it in a browser. If the page loads in the iframe, you're vulnerable. Automated tools like ZeriFlow check this instantly, along with 80+ other security issues.
Conclusion
Clickjacking prevention is one of the highest-ROI security fixes you can make — two HTTP headers eliminate the attack surface entirely. X-Frame-Options: DENY and Content-Security-Policy: frame-ancestors 'none' should be in your default server configuration for every web application.
The gap between knowing about clickjacking and actually being protected is often just a missing header. [Scan your site with ZeriFlow right now](https://zeriflow.com) to see if you're protected — it takes less than a minute and covers 80+ security checks beyond just clickjacking.
Don't let a two-line fix be the reason a user's account gets compromised.