The Clickjacking Attack Β· Astro Tech Blog

The Clickjacking Attack

Clickjacking tricks users into clicking something they didn’t intend to. An attacker overlays an invisible iframe on top of a decoy page β€” the user thinks they’re clicking a button, but actually clicks something on the hidden page.

How It Works

Visible page:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Click here to win!     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ [CLAIM PRIZE]     β”‚  β”‚  ← User thinks they click this
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Invisible iframe (opacity:0):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         β”‚
β”‚  Twitter.com (hidden)   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ [TWEET]           β”‚  β”‚  ← Actually clicks this
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The Attack HTML

<style>
  iframe {
    width: 500px;
    height: 500px;
    position: absolute;
    top: 0;
    left: 0;
    opacity: 0;
    z-index: 100;
  }
  .decoy-button {
    position: absolute;
    top: 200px;
    left: 100px;
    z-index: 10;
  }
</style>

<div class="decoy-button">Click me for a prize!</div>
<iframe src="https://victim-site.com/tweet"></iframe>

The user clicks the visible button, but actually clicks the invisible iframe’s β€œTweet” button.

Defending Against Clickjacking

1. X-Frame-Options Header

The simplest defense. Send this HTTP header:

X-Frame-Options: DENY           // never allow in iframe
X-Frame-Options: SAMEORIGIN     // allow if same origin

Set it on the server:

// Express.js
res.setHeader('X-Frame-Options', 'DENY');

// Nginx
add_header X-Frame-Options "SAMEORIGIN" always;

// Apache
Header always set X-Frame-Options "DENY"

2. Content-Security-Policy frame-ancestors

The modern approach β€” more flexible:

Content-Security-Policy: frame-ancestors 'none';
Content-Security-Policy: frame-ancestors 'self';
Content-Security-Policy: frame-ancestors https://trusted-site.com;

3. Frame Busting with JavaScript

A client-side defense (works if JS is enabled):

if (window.top !== window.self) {
  window.top.location = window.self.location;
}

But this can be bypassed. The sandbox attribute on the attacker’s iframe can prevent it:

<iframe sandbox="allow-scripts" src="..."></iframe>

Without allow-top-navigation, the frame-busting JS can’t redirect the top window.

Generate a random token, store it in a cookie and session, and verify on sensitive actions:

// On sensitive form submission
if (req.cookies.csrf_token !== req.session.csrf_token) {
  // reject β€” likely clickjacking or CSRF
}

Detecting Iframe Embedding

Demo: Frame Detection
HTML
<div id='frame-detect' style='padding:16px;background:#f1f5f9;border-radius:8px;'>
<p id='detect-status'>Checking if we're in an iframe...</p>
<pre id='detect-log' style='background:white;padding:8px;border-radius:4px;'></pre>
</div>
JavaScript
const log = document.getElementById('detect-log');
const status = document.getElementById('detect-status');

try {
if (window.top !== window.self) {
status.textContent = '⚠️ I am embedded in an iframe!';
status.style.color = '#ef4444';
log.textContent =
'window.self: ' + window.self.location.href + '\\n' +
'window.top: ' + window.top.location.href + '\\n' +
'These are DIFFERENT β€” we are framed!';
} else {
status.textContent = 'βœ… I am the top-level window. Not framed.';
status.style.color = '#22c55e';
log.textContent =
'window.self === window.top: true' + '\\n' +
'This page is not embedded in any iframe.';
}
} catch (e) {
// Cross-origin access to top.location throws
status.textContent = '⚠️ Cannot access top β€” likely cross-origin iframe';
status.style.color = '#eab308';
log.textContent = 'SecurityError: ' + e.message + '\\n' + 'This means we\\'re likely in a cross-origin iframe.';
}
Live Output Window

window.self vs window.top

PropertyWhat it is
window.selfThe current window/iframe
window.topThe topmost browser window
window.parentThe direct parent (might be an iframe too)

When window.self !== window.top, you’re inside an iframe.

Best Practices

  1. Always set X-Frame-Options: DENY on auth pages (login, payment)
  2. Use Content-Security-Policy: frame-ancestors for fine-grained control
  3. Don’t rely solely on JavaScript frame busting β€” headers are the primary defense
  4. For pages that legitimately need framing (widgets, embeds), restrict to specific origins:
    Content-Security-Policy: frame-ancestors https://partner.com;
    
  5. Consider adding SameSite=Strict cookies for sensitive operations

Key Takeaways

  • Clickjacking hides a victim site in an invisible iframe over a decoy page
  • X-Frame-Options: DENY is the simplest server-side defense
  • CSP frame-ancestors is the modern, more flexible approach
  • Frame-busting JS (if (top !== self)) is a secondary defense
  • Always protect login, payment, and social-action pages from framing
  • The sandbox attribute can be used to restrict what framed content can do