You click a button on a webpage. The browser registers the click and delivers it to the element at those coordinates. That's the end of it.
Unless the element at those coordinates isn't the element you thought you were clicking.
What the Browser Is Actually Doing
When you interact with a webpage, the browser takes your cursor position, determines what's rendered at that position, and delivers the interaction to that element. The browser doesn't know what you intended to click. It knows where you clicked, and it gives the event to whatever is there.
Clickjacking exploits this. The attack uses visual deception to cause your click to land on a UI element you didn't intend to interact with, typically one belonging to a different website embedded inside the page you're looking at.
To understand how, you need to understand iframes.
Why Browsers Allow Embedding
An <iframe> element lets one webpage embed another. It's a normal browser feature with legitimate uses: embedding a map, a payment form, a video player, a third-party widget. When you load a page with an iframe pointing at another site, your browser fetches that other site and renders it inside the iframe. The framed page runs in its own browsing context.
By default, browsers allow any site to frame any other site. That's been the default for most of the web's history.
This is where the problem starts.
The Deceptive Interface
In a clickjacking scenario, an attacker builds a page that embeds a victim website in an iframe. The iframe is positioned over the attacker's page so that a sensitive element inside the victim site, a button, a settings toggle, a confirmation prompt, sits directly beneath something the attacker renders on top.
The attacker controls what the victim sees: a game, a form, a video, a button that says "Play" or "Continue" or "Claim your reward." Underneath that visible layer, the iframe is invisible or transparent, positioned so that the sensitive element on the victim site is exactly where the user will click.
What the victim sees:
┌────────────────────────────────┐
│ Click here to continue! │
│ [ Continue ] │
└────────────────────────────────┘
What's actually at those coordinates:
┌────────────────────────────────┐
│ victim-app.example │
│ [ Confirm account deletion ]│
└────────────────────────────────┘
When the user clicks "Continue," the browser delivers the click to the element at those screen coordinates. That element is on the victim site, inside the iframe. The browser interacts with the victim site's button, not the attacker's button.
Why Authentication Matters
The victim's browser already has a session with the victim site. If the user is logged into their banking app, social network, or cloud storage provider, that session is present in the browser. When the browser delivers a click to an element inside the framed victim page, that interaction happens in the context of the user's existing authenticated session.
This is the reason clickjacking can cause real harm. The attacker doesn't steal credentials. The attacker doesn't read the victim site's response inside the iframe (the Same-Origin Policy prevents cross-origin JavaScript from reading iframe contents). The attacker causes the victim's own authenticated browser to perform an action on a site the victim is already logged into.
The attacker sees nothing. But the action happens.
Impact
What that action can do depends on what the victim application exposes through user interaction. If the framed action changes account settings, authorizes a transaction, sends a message, deletes data, or follows an account, those consequences happen with the victim's full session privileges.
Impact is bounded by the application's UI. An action that requires multiple steps, CAPTCHA, or typed input is harder to exploit through clickjacking than a single-click confirmation. But any application that allows significant state-changing actions through simple interactions is potentially relevant.
How This Differs From CSRF and XSS
These three vulnerabilities are often mentioned together, but the mechanisms are different.
CSRF causes a victim's authenticated browser to send a forged HTTP request. The attacker crafts a request that the browser sends, typically by getting the victim to load an attacker-controlled page. The victim doesn't interact with anything that looks like the target site.
Clickjacking requires actual user interaction. The victim has to click something. The deception is visual: the user interacts with what they think is the attacker's interface, but the browser delivers that interaction to the victim site.
XSS injects attacker-controlled JavaScript into the victim site's origin, giving it the ability to act as that origin, read its contents, and interact with it. Clickjacking doesn't involve script execution in the victim's origin at all.
Modern applications often implement CSRF protections like CSRF tokens. These can make CSRF harder, but they don't prevent clickjacking, because in a clickjacking attack the user is performing the interaction themselves, and the CSRF token is handled normally by the browser as part of the legitimate page flow.
Defenses
The most direct defense is for websites to control whether and where they can be framed.
X-Frame-Options is an HTTP response header that tells browsers whether to allow the page to be embedded:
X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
DENY prevents framing entirely. SAMEORIGIN allows framing only from the same origin. This header is widely supported and remains in use for compatibility reasons.
Content-Security-Policy's frame-ancestors directive is the more flexible modern approach:
Content-Security-Policy: frame-ancestors 'none';
Content-Security-Policy: frame-ancestors 'self';
Content-Security-Policy: frame-ancestors https://trusted.example.com;
frame-ancestors supersedes X-Frame-Options in browsers that support it and allows finer-grained control, including specifying which specific origins are permitted to embed the page. For new deployments, frame-ancestors is the preferred mechanism.
Both controls work at the browser level: they tell the browser not to render the page inside a frame in unauthorized contexts. If the browser refuses to embed the page, the entire clickjacking attack collapses because there's nothing to click under the attacker's interface.
Frame-busting scripts are JavaScript-based techniques where a page detects that it's inside a frame and breaks out or redirects. These have historically been the primary client-side defense. They're fragile. Various techniques have existed to suppress frame-busting behavior, and they depend on JavaScript executing correctly. Server-enforced framing policies are more reliable because they don't depend on the page's own scripts running successfully.
A Caveat Worth Noting
Clickjacking doesn't require an invisible iframe. The underlying problem is visual misalignment between what the user believes they're interacting with and what the browser is actually delivering the interaction to. This can involve transparent overlays, precisely positioned elements, scroll manipulation, or other techniques that cause a similar mismatch. The iframe is the common implementation, not the definition of the vulnerability.
Clickjacking isn't fundamentally about hidden buttons or invisible layers. It's about the gap between what a user perceives as the target of their interaction and what the browser treats as the actual target.
The browser's job is to deliver events to elements. It does this correctly. The attack works by controlling what's visually presented to the user while placing a different element at the interaction coordinates. The browser does what it's told. The user does what they intended to do. They just didn't know they were intending different things.
Closing that gap is what frame-ancestors is for.
Top comments (2)
Good explanation of why the attack works at the coordinate level rather than the credential level — that framing is what makes it click for people who keep expecting auth to save them. One addition from the practical side: modern defense really is CSP
frame-ancestors(X-Frame-Options is deprecated and, in our experience, the header that stays wrong on staging environments long after production got fixed — the login-less admin dashboards are the juicy targets).The part I'd push further is the action side, not the embedding side: for destructive actions, requiring an interaction that can't be performed by one blind coordinate click (type-to-confirm, drag slider, re-auth) removes the whole class even if framing slips through. Do you cover the sandbox attribute traps in a follow-up?
allow-top-navigationon an iframe you don't control deserves the same length as this post.Thank You! and you have suggested a good addition to the article.