What is Clickjacking?
Clickjacking (UI Redress Attack) is a technique where an attacker embeds a target website in a transparent or invisible <iframe> overlaid on a decoy page. The victim sees the attacker’s page and clicks on what appears to be a harmless button, but their click is actually registered by the hidden target frame below — performing an action on the target site using their authenticated session.
Common clickjacking attacks include: tricking users into clicking “Allow” on camera/microphone permission prompts, submitting forms (account deletion, fund transfers, social media likes), enabling attacker-controlled settings, or approving OAuth permissions.
How exploitation works
<!-- Attacker's decoy page -->
<html>
<body>
<style>
iframe {
position: absolute; top: 0; left: 0;
width: 100%; height: 100%;
opacity: 0.0; /* Invisible overlay */
z-index: 999;
}
#decoy-button {
position: absolute; top: 300px; left: 200px;
z-index: 1;
}
</style>
<!-- Target site loaded invisibly on top -->
<iframe src="https://target.com/settings/delete-account"></iframe>
<!-- Decoy button positioned under the real "Confirm" button -->
<button id="decoy-button">Click here to claim your prize!</button>
</body>
</html>
The victim clicks the decoy button; the click falls through to the invisible iframe, hitting the “Delete Account” button on the target site.
Vulnerable code examples
ASP.NET Core — no frame options set
// VULNERABLE: No X-Frame-Options or CSP frame-ancestors configured
// Default ASP.NET Core configuration does not include framing protection
app.UseRouting();
app.UseAuthorization();
// Missing: response headers that prevent iframe embedding
Express / Node.js — no helmet configuration
// VULNERABLE: No framing protection
const app = express();
// Missing: helmet with frameguard, or explicit X-Frame-Options header
app.use(express.json());
Secure code examples
ASP.NET Core — security headers middleware
// SECURE: Set X-Frame-Options and CSP frame-ancestors
app.Use(async (context, next) => {
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("Content-Security-Policy",
"frame-ancestors 'none'"); // Modern equivalent — takes precedence
await next();
});
Express / Node.js — helmet
// SECURE: helmet's frameguard sets X-Frame-Options automatically
const helmet = require('helmet');
app.use(helmet({
frameguard: { action: 'deny' },
contentSecurityPolicy: {
directives: {
frameAncestors: ["'none'"], // Disallow all framing
}
}
}));
Python / Django
# SECURE: Enable clickjacking middleware (enabled by default in Django)
# settings.py
MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', # Adds X-Frame-Options: DENY
...
]
X_FRAME_OPTIONS = 'DENY' # or 'SAMEORIGIN' if you need same-domain iframes
What Offensive360 detects
- Missing X-Frame-Options header — HTTP responses without
X-Frame-Options: DENYorSAMEORIGIN - Missing CSP
frame-ancestorsdirective — Absence of the modernContent-Security-Policy: frame-ancestorsrestriction - Permissive SAMEORIGIN without need — Applications that allow same-origin framing when there is no legitimate use case
- Disabled Django XFrame middleware —
XFrameOptionsMiddlewareremoved from the middleware stack
Remediation guidance
-
Set
Content-Security-Policy: frame-ancestors 'none'— This is the modern standard and takes precedence overX-Frame-Optionsin browsers that support CSP Level 2+. -
Also set
X-Frame-Options: DENY— For backward compatibility with older browsers that do not support CSP. -
Use
SAMEORIGINonly when required — Only allow same-origin framing if your application deliberately uses iframes for own-domain content. -
Apply headers globally — Configure framing headers as middleware that applies to all responses, not per-endpoint.
-
Avoid JavaScript frame-busting — JavaScript-based frame-busting (
if (top !== self) top.location = self.location) can be bypassed withsandboxiframe attributes. Use HTTP headers instead.