Blog · C.12 · Web Application Security

Clickjacking attacks: how invisible iframes hijack user clicks - and how to stop them

Clickjacking (UI redressing) layers a transparent, attacker-controlled iframe over a legitimate page and harvests every click the user thinks they're making on the visible content. It requires no malware, no XSS, and no user error - just a browser rendering an iframe. Here's the full attack anatomy, real exploit variants, and the exact headers that shut it down.

ClickjackingUI RedressingX-Frame-OptionsCSP frame-ancestorsOWASPWeb Pen Test
CLICKJACKING: UI Redressing · Transparent Iframe · X-Frame-Options · CSP frame-ancestors · Frame Busting · Likejacking · Cursor Hijacking · SameSite Cookies · Opacity: 0 · z-index Stacking · Drag-and-Drop Hijack CLICKJACKING: UI Redressing · Transparent Iframe · X-Frame-Options · CSP frame-ancestors · Frame Busting · Likejacking · Cursor Hijacking · SameSite Cookies · Opacity: 0 · z-index Stacking · Drag-and-Drop Hijack
// TL;DR

Clickjacking embeds your target site inside an invisible iframe, overlays it on a decoy page, and tricks authenticated users into clicking buttons they can't see - transferring funds, granting OAuth permissions, posting to social feeds, or enabling webcam access. The fix is one HTTP response header: Content-Security-Policy: frame-ancestors 'none' (or 'self' if you need legitimate embedding). X-Frame-Options is the legacy equivalent. JavaScript frame busting and SameSite cookies add defence-in-depth but are not standalone fixes. Every web application should have this header. Most don't.

// 01 What is clickjacking?

Clickjacking - formally called UI redressing - is an interface-based client-side attack in which the victim is deceived into clicking on content belonging to a hidden website while believing they are interacting with a visible decoy page. The technique requires no vulnerability in the victim's browser beyond its standard, intentional ability to render iframes. The attacker constructs a page, hosts it anywhere, and tricks the victim into visiting it - typically via phishing, malicious advertising, or a compromised third-party widget.

The core mechanic is simple: load the target application inside an iframe, make the iframe transparent, position a visible decoy element underneath the invisible target button, and wait for the authenticated user to click the decoy. Their click lands on the hidden target application, performing whatever action the attacker positioned beneath the cursor. Because the victim is already authenticated to the target site - their session cookie is sent automatically - the action executes with their full privileges.

Clickjacking is listed in the OWASP Top 10 under A05: Security Misconfiguration and was a prominent entry in earlier editions specifically. It appears routinely in web application penetration tests because the fix (adding an HTTP header) is trivial and the oversight (omitting it) is extremely common.

No XSSClickjacking requires no code injection - only the ability to embed the target in an iframe
Auth bypassThe victim's existing session is exploited - attacker never needs credentials
1 headerA single CSP or X-Frame-Options response header is the primary fix
~70%Of web applications we test lack adequate framing protection on at least one sensitive endpoint

// 02 How a clickjacking attack is constructed

Clickjacking attacks are built using CSS to create precisely positioned, transparent layers. The attacker loads the target website as an iframe - positioned to fill the browser viewport - and sets its opacity to zero so it is completely invisible. A decoy page with enticing visible content sits beneath the iframe in the stacking order (z-index), at the exact pixel offset required to align a target button with whatever visible element the attacker wants the user to click.

HTML + CSS<!-- Basic clickjacking page structure -->
<html>
<head>
  <style>
    #target_website {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      border: none;
      opacity: 0.0;      /* invisible to the victim */
      z-index: 2;        /* sits on top - receives all clicks */
    }
    #decoy_website {
      position: absolute;
      z-index: 1;        /* visible decoy content sits below */
    }
  </style>
</head>
<body>
  <div id="decoy_website">
    <h1>You just won a $1,000 prize!</h1>
    <button>Click here to claim</button>  <!-- visible bait -->
  </div>
  <iframe id="target_website"
          src="https://bank.example.com/transfer?amount=5000&to=attacker">
  </iframe>               <!-- invisible - click lands here -->
</body>
</html>

/* The victim sees the prize button.
   They click it. Their click hits the bank transfer confirm button
   in the invisible iframe. The transfer executes with their session. */

Position values are tuned with absolute and relative offsets so the target action overlaps the decoy precisely regardless of screen resolution. Attackers often use a near-zero opacity (e.g., 0.001) rather than 0.0 to evade threshold-based transparency detection present in some browsers - a sufficient effect without triggering heuristics that block fully transparent iframes.

"The attack doesn't exploit a bug in the browser. It exploits the browser doing exactly what it was designed to do: render an iframe and process a click. That's what makes it persistent - there's no vendor patch coming."

CyberFortify Web Application Security Practice

// 03 Clickjacking attack variants in the wild

The basic clickjacking template spawns several distinct attack variants depending on what the attacker wants to achieve. Understanding these variants is essential for comprehensive testing - a generic iframe check catches the basic case but misses cursor hijacking and drag-and-drop attacks entirely.

Variant 01

Likejacking

The original mass-scale clickjacking variant. An invisible Facebook, LinkedIn, or Twitter Like/Follow button is overlaid on compelling content - news articles, viral videos, prize pages. Victims click the decoy and unknowingly endorse attacker-controlled content to their entire social network, amplifying phishing or disinformation campaigns.

Variant 02

Multi-step clickjacking

Sequences multiple decoy interactions across a workflow that requires several user clicks to complete - such as OAuth application authorization dialogs or two-step wire transfer confirmations. Each visible decoy click advances the hidden application through one step of its confirmation flow.

Variant 03

Drag-and-drop hijacking

Exploits the HTML5 drag-and-drop API to exfiltrate data. The attacker positions a hidden input field from the target application under a visible drag target. When the user drags an attractive element (e.g., a "move this file" widget), the drag action deposits content - including clipboard data or file paths - directly into the hidden form field.

Variant 04

Cursor hijacking

Uses CSS cursor: url() to replace the system cursor with a custom image offset by a fixed number of pixels. The victim perceives their cursor to be at one position while actual click events register elsewhere - effectively applying a persistent spatial offset to every click on the page without requiring an iframe layer.

Variant 05

File upload hijacking

Positions a transparent file input element from a target site over an innocuous-looking "upload your avatar" widget. The victim selects and submits a file through what appears to be a normal upload dialog; instead, the file is uploaded to the attacker's target site under the victim's authenticated session.

Variant 06

Permission hijacking

Overlays browser permission grant dialogs - camera, microphone, geolocation, notification - on decoy pages. Targets users into granting device permissions to attacker-controlled origins while believing they are approving something unrelated. Particularly effective on mobile browsers where permission dialogs occupy a smaller portion of the viewport.

// 04 What an attacker can actually achieve

The impact of a successful clickjacking attack is bounded by what authenticated actions the target application exposes to a single click. In practice, that scope is broader than most developers assume:

// 05 How to test for clickjacking vulnerabilities

During a web application penetration test, clickjacking is assessed on every sensitive endpoint - not just the homepage. A page that lacks framing controls only on its /account/delete endpoint is just as exploitable as one that lacks them globally. The test methodology is:

Step 01

Check response headers on each sensitive endpoint

For every endpoint that performs a state-changing action (transfers, deletions, permission changes, profile updates), inspect the HTTP response headers for X-Frame-Options and Content-Security-Policy: frame-ancestors. Missing on any one endpoint is a finding - even if all others are protected.

Step 02

Attempt iframe embedding

Create a local HTML file that loads the target URL inside an iframe. Open it in a browser. If the page renders inside the iframe, framing protection is absent. Use browser DevTools to inspect whether any Content-Security-Policy violations appear in the console - their presence confirms a header exists but may indicate misconfiguration.

Step 03

Test the overlap scenario

For a confirmed finding, build a proof-of-concept that demonstrates impact: position the iframe so a specific target button (e.g., "Confirm Transfer", "Delete Account") overlaps a visible decoy button. Screenshot the overlay. This is what goes into the report - a theoretical finding needs a working PoC to be actionable.

Step 04

Check frame-busting bypasses

If JavaScript frame busting is the only protection, test whether it can be bypassed via the sandbox attribute on the iframe element: <iframe sandbox="allow-forms allow-scripts" ...>. The sandbox attribute prevents the framed page's JavaScript from executing, neutralising frame busting without affecting the iframe render.

Step 05

Assess severity by action

Rate severity based on what the clickjackable action does. Account deletion or financial transfer → Critical. Privilege escalation → Critical. Profile update or social share → Medium. Static read-only page → Informational (framing a page that performs no state-changing action has no direct impact).

// 06 Prevention: X-Frame-Options header

The X-Frame-Options response header is the original server-side mechanism for preventing clickjacking. It instructs the browser not to render the response inside a <frame>, <iframe>, <embed>, or <object> element. It was introduced by Internet Explorer 8 in 2009 and is now universally supported across modern browsers.

HTTP HEADERS# DENY - page cannot be embedded in any iframe, anywhere
X-Frame-Options: DENY

# SAMEORIGIN - page can only be embedded by pages on the same origin
X-Frame-Options: SAMEORIGIN

# ALLOW-FROM (deprecated - not supported in Chrome or Firefox)
# Do not use - falls back to no protection in modern browsers
X-Frame-Options: ALLOW-FROM https://trusted-partner.com

Set this header at the web server or reverse proxy level so it applies to all responses, not just those generated by application code. Missing it on even one route can be exploited.

SERVER CONFIG# Nginx - add to server {} or location {} block
add_header X-Frame-Options "DENY" always;

# Apache - add to httpd.conf or .htaccess
Header always set X-Frame-Options "DENY"

# Express.js (Node) - use helmet middleware
const helmet = require('helmet');
app.use(helmet.frameguard({ action: 'deny' }));

# Django - set in settings.py
X_FRAME_OPTIONS = 'DENY'

# ASP.NET Core - add in Program.cs
app.Use(async (ctx, next) => {
    ctx.Response.Headers.Add("X-Frame-Options", "DENY");
    await next();
});

// 07 Prevention: CSP frame-ancestors directive

Content-Security-Policy: frame-ancestors is the modern, OWASP-recommended replacement for X-Frame-Options. It is more powerful: it supports multiple allowed origins, wildcards at the scheme or host level, and is processed before X-Frame-Options in browsers that support both. Where X-Frame-Options: ALLOW-FROM is deprecated and ignored by Chrome and Firefox, frame-ancestors handles the multi-origin use case correctly.

HTTP HEADERS - CSP frame-ancestors# Block all framing - equivalent to X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none';

# Allow framing only by same origin - equivalent to SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self';

# Allow framing by self and specific trusted partner domains
Content-Security-Policy: frame-ancestors 'self' https://partner.example.com https://embed.trusted.io;

# Combine with other CSP directives in one header
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'; script-src 'self';

For applications that legitimately need to be embedded - such as a widget served to third-party sites - use frame-ancestors with an explicit allowlist of trusted origins. Never use a wildcard (*) as the frame-ancestors value; it provides no protection against clickjacking.

"X-Frame-Options tells the browser what it can't do. CSP frame-ancestors tells the browser exactly what it can do, and refuses everything else. The second posture is always stronger."

CyberFortify Web Application Security Practice

// 08 Prevention: Frame busting, SameSite cookies, and confirmation dialogs

These three controls are defence-in-depth measures - they reduce clickjacking risk but are not reliable standalone fixes. Deploy them alongside the header-based controls above.

JavaScript frame busting

Frame busting scripts detect when the page is loaded inside a frame and redirect the top-level window to the page's own URL, breaking out of the iframe. The limitation is critical: they can be neutralised by the attacker using the sandbox attribute on the iframe, which blocks JavaScript execution in the framed context.

JAVASCRIPT// Basic frame busting - breaks out of any iframe
if (window.top !== window.self) {
    window.top.location = window.self.location;
}

// Bypass used by attackers - sandbox prevents the JS from running:
// <iframe sandbox="allow-forms allow-scripts" src="target.html"></iframe>
// sandbox="allow-forms" alone lets forms submit but blocks top-frame navigation
// Result: frame buster silently fails, iframe renders normally

// Conclusion: frame busting is NOT a substitute for X-Frame-Options / CSP

SameSite cookies

The SameSite cookie attribute limits when session cookies are sent on cross-site requests. With SameSite=Strict, the session cookie is not sent when the page is loaded inside a cross-site iframe - meaning the attacker's decoy page cannot leverage an authenticated session from a different site. This reduces the impact of many clickjacking attacks but does not prevent all variants (same-site attackers, or scenarios where the victim navigates directly).

HTTP - SET-COOKIE# Strict - session cookie never sent cross-site (strongest)
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure; HttpOnly

# Lax - sent on top-level navigations only, not subframe loads (default in modern browsers)
Set-Cookie: sessionId=abc123; SameSite=Lax; Secure; HttpOnly

# None - sent in all cross-site contexts (dangerous - requires Secure)
Set-Cookie: sessionId=abc123; SameSite=None; Secure; HttpOnly
# SameSite=None provides zero clickjacking protection

Confirmation dialogs on critical actions

Requiring explicit typed confirmation (not just a single click) on destructive or high-value actions raises the bar for clickjacking exploitability. An attacker cannot easily sequence multi-step dialog interactions through a single click. This is a useful UX control for actions like account deletion, wire transfers, and permission grants - even when header-based framing controls are in place.

HTML + JS<!-- Typed confirmation for destructive actions -->
<button onclick="confirmDelete()">Delete Account</button>

<script>
function confirmDelete() {
  const typed = prompt('Type DELETE to confirm account deletion:');
  if (typed === 'DELETE') {
    // proceed with deletion
  }
}
// Clickjacking can fire a click but cannot simulate a keyboard entry
// in an isolated iframe - making multi-step attacks significantly harder
</script>

// 09 Comparing clickjacking defences

Defence Browser support Stops basic clickjacking Sandbox iframe bypass Notes
X-Frame-Options: DENY Universal Yes Not bypassed Simple, widely supported - use as fallback
CSP frame-ancestors 'none' All modern Yes Not bypassed OWASP recommended primary control - use this
JavaScript frame busting Universal Yes Bypassed by sandbox Supplement only - not reliable as primary
SameSite=Strict cookies All modern Reduces impact Not bypassed Limits session availability in iframe - defence-in-depth
Confirmation dialogs N/A Raises bar only Mitigated UX control - use on Critical actions regardless
X-Frame-Options: ALLOW-FROM Deprecated No (ignored by Chrome/Firefox) N/A Do not use - provides false sense of security

// 10 Clickjacking prevention checklist

Use this checklist as a remediation reference after a pen test finding, or as a pre-launch hardening step for any web application that handles authenticated actions. This checklist aligns with OWASP Top 10 A05 (Security Misconfiguration) guidance and covers the controls expected in compliance frameworks including PCI DSS and ISO 27001.

Check 01

CSP frame-ancestors on all responses

Set Content-Security-Policy: frame-ancestors 'none' (or 'self' if self-embedding is needed) on every HTTP response - not just HTML pages. Apply globally at the web server or API gateway level, not in application middleware that might be skipped on error paths.

Check 02

X-Frame-Options as legacy fallback

Send X-Frame-Options: DENY alongside the CSP header. Older browsers that don't support frame-ancestors will honour this header instead. Two lines of server config, zero application code changes.

Check 03

SameSite=Strict on session cookies

Set SameSite=Strict on all session and authentication cookies. This prevents the cookie from being sent when the target page is embedded in a cross-site iframe, removing the authenticated session from the clickjacking scenario entirely.

Check 04

Remove any ALLOW-FROM directives

Audit existing configurations for X-Frame-Options: ALLOW-FROM. This directive is unsupported in Chrome and Firefox and provides no protection in those browsers. Replace with CSP frame-ancestors with an explicit origin allowlist.

Check 05

Require explicit confirmation on critical actions

Identify your highest-impact single-click actions - fund transfers, account deletion, admin grants, OAuth approvals. Add a confirmation step that requires typed input or a separate verification code. This is the last line of defence if framing headers are accidentally removed in a deployment.

Check 06

Include clickjacking in regression testing

Add a header assertion to your CI pipeline or post-deployment smoke test: verify that X-Frame-Options and Content-Security-Policy are present in the response headers of every sensitive endpoint. A missing header after a framework upgrade or nginx config change should fail the build, not a pen test.

CY

CyberFortify Research

Web Application Security Practice

Field notes from web application penetration testing engagements. All findings are drawn from real authorised assessments. Methodology aligned with OWASP, OSSTMM, and the Penetration Testing Execution Standard.

Is your application protected against clickjacking?

A missing response header is all it takes - and it appears on roughly 70% of the web applications we test. CyberFortify's web application penetration testing service covers clickjacking, injection, broken authentication, SSRF, and the full OWASP Top 10 with a manual, context-aware methodology that automated scanners cannot replicate.

Schedule scoping call → Web app pen testing →