Safeguard
Vulnerability Analysis

Clickjacking vulnerabilities explained

A clickjacking vulnerability hides real buttons under a decoy iframe to hijack clicks. Here's how the attack works, real incidents, and fixes.

Priya Mehta
DevSecOps Engineer
7 min read

A clickjacking vulnerability lets an attacker trick a victim into clicking something other than what they perceive, by rendering a legitimate page inside an invisible or disguised iframe and positioning a fake UI element directly over a real button. The victim thinks they're clicking "Play video" or "Claim reward"; they're actually clicking "Transfer funds" or "Grant camera access" on the page underneath. The technique was named in 2008 by researchers Jeremiah Grossman and Robert Hansen after they demonstrated it against Adobe's Flash Player Settings Manager, and it has resurfaced in Twitter worms, Facebook "Likejacking" campaigns, and webmail account takeovers ever since. It's tracked as CWE-1021 (Improper Restriction of Rendered UI Layers or Frames) and remains a standard test case in the OWASP Web Security Testing Guide (WSTG-CLNT-09). The fix is well understood — frame-control headers — yet security teams still find it on production apps because header enforcement is inconsistent across services, load balancers, and CDNs.

What is a clickjacking vulnerability?

A clickjacking vulnerability is any page that can be loaded inside an <iframe> on an attacker-controlled domain without the framed page detecting or blocking it. If a banking, admin, or account-settings page renders normally inside a third-party iframe, an attacker can set that iframe's CSS opacity to near-zero, stack a decoy UI on top with z-index, and align the invisible frame's real buttons under the decoy's clickable regions using pixel-precise positioning. The victim interacts with what looks like a game, survey, or "I'm not a robot" checkbox, but the click lands on the transparent, authenticated page underneath — often executing a state-changing action like enabling a webcam, following an account, or submitting a form the victim never saw. The vulnerability isn't in a specific line of application code; it's the absence of a server-side or header-based instruction telling browsers not to allow framing at all.

How does a clickjacking attack actually work?

The attack works by layering two documents in the DOM and exploiting the gap between what the browser renders and what the user perceives. First, the attacker builds a malicious HTML page containing an iframe that points to the target — say, https://victim-app.com/account/delete. Second, CSS sets the iframe to opacity: 0.001 (fully invisible but still receiving clicks in most browsers) or clips it with clip-path so only the target button's exact coordinates are exposed. Third, the attacker overlays a decoy element, commonly a "Click to continue" button or an embedded video's play icon, positioned so its clickable area matches the hidden button beneath it pixel-for-pixel. Fourth, the victim, already authenticated to victim-app.com in another tab or via a persistent session cookie, clicks the decoy and the browser delivers that click, along with the session cookie, to the real button. Variants extend this further: "cursorjacking" redraws the visible cursor icon offset from the actual pointer position so victims click somewhere other than where they think they're pointing, and "double-click jacking" swaps the target between the first and second click of a double-click sequence to defeat single-click confirmation dialogs.

What real-world incidents show clickjacking's impact?

Clickjacking has caused real account takeovers and worm-style propagation, not just theoretical demos. In February 2009, a clickjacking-style self-XSS worm on Twitter used a disguised link labeled "Don't Click This" that, once clicked, posted the same link to the victim's own feed, spreading it to followers within hours. In 2010, Facebook "Likejacking" campaigns overlaid invisible Like buttons on pages promising salacious videos or free gift cards; clicking anywhere on the page silently posted a Like to the victim's timeline, which then advertised the scam to their friends, and Sophos and other researchers tracked dozens of distinct campaign waves through 2011. In 2018, a bug bounty report against ProtonMail's webmail client showed that a crafted page could frame the compose or settings view and trick an authenticated user into deleting messages or changing account settings with a single disguised click, which the vendor patched by tightening frame-ancestors enforcement. These cases share a pattern: the underlying application logic was correct, authentication worked as intended, and the only missing control was a frame-restriction directive.

How is clickjacking different from tapjacking and cookiejacking?

Clickjacking is a desktop-and-mobile-web UI redress attack; tapjacking is its Android-specific variant, and cookiejacking targets browser chrome rather than page content. Tapjacking, documented publicly around 2012, abuses Android's Toast and overlay window APIs so a malicious app draws a transparent layer over a permission dialog or another app's button, capturing a tap intended for the legitimate UI — Google hardened FLAG_WINDOW_IS_OBSCURED handling in response, which now makes Android reject touches on views obscured by another app's overlay by default. Cookiejacking, presented by researcher Rosario Valotta at Black Hat Europe 2011, tricked users into a drag-and-drop interaction that exfiltrated cookies out of Internet Explorer's UI chrome rather than clicking a hidden button on a web page. All three share the same root cause — a UI layer the user trusts is not the layer actually receiving the input — but clickjacking specifically refers to the iframe-overlay pattern on the web, which is why the mitigations (frame-ancestors, X-Frame-Options) are web-specific and don't address the mobile or browser-chrome variants.

How do X-Frame-Options and CSP frame-ancestors stop clickjacking?

X-Frame-Options and the CSP frame-ancestors directive stop clickjacking by instructing the browser to refuse to render the page inside a frame at all, which removes the attacker's overlay surface entirely. Microsoft introduced X-Frame-Options in Internet Explorer 8 in 2009, and it reached broad adoption in Chrome, Firefox, and Safari by 2012; set to DENY, it blocks framing anywhere, and set to SAMEORIGIN, it allows framing only from the same origin as the page itself. The header has one well-known limitation: it accepts only a single value and cannot allowlist multiple trusted parent origins, which is why the Content Security Policy Level 2 specification, finalized by the W3C in 2014, added frame-ancestors — a directive that accepts a list of allowed framing origins, for example Content-Security-Policy: frame-ancestors 'self' https://partner.example.com. Browsers that support CSP2 (all major engines since roughly 2015) prioritize frame-ancestors over X-Frame-Options when both are present, so current guidance is to set both, with CSP as the primary control and X-Frame-Options: SAMEORIGIN as a fallback for the rare legacy client. Legacy JavaScript "frame-busting" scripts (if (top !== self) top.location = self.location) are no longer recommended as a primary defense because they can be neutralized by sandbox attributes or by opening the target in a new unframed context before executing the click.

How can security teams find clickjacking risk before attackers do?

Security teams find clickjacking risk by scanning HTTP responses for missing or misconfigured frame-control headers across every route that renders authenticated, state-changing UI, not just the login page. Automated scanners flag the absence of X-Frame-Options or frame-ancestors on a response, but the harder problem is coverage: a single missing header on one microservice behind a shared load balancer, or a CDN configuration that strips security headers from cached responses, reintroduces the vulnerability even when the application code sets it correctly upstream. Manual verification, per OWASP WSTG-CLNT-09, involves building a proof-of-concept HTML page that frames the target URL and confirming in a real browser whether the frame renders — automated header checks alone miss cases where a header is present but malformed (for example, a comma-separated X-Frame-Options value, which several browsers historically ignored entirely, defaulting to allow-framing).

How Safeguard Helps

Safeguard's platform correlates web-facing routes and their response headers with the rest of your application's dependency and deployment graph, so a missing frame-ancestors or X-Frame-Options directive on a state-changing endpoint isn't just a scanner finding buried in a report — it's prioritized against reachability analysis showing whether that route is actually internet-exposed and authenticated. Griffin AI generates auto-fix pull requests that add the correct security headers at the framework or reverse-proxy configuration layer, matched to your stack (Express, Spring, nginx, or CDN edge rules), instead of leaving remediation as a generic checklist item. SBOM generation and ingestion tracks which web framework versions and middleware are in use, flagging outdated frame-protection defaults inherited from libraries that predate CSP2 support. For teams tracking header-hygiene regressions across dozens of services, that combination turns clickjacking from a recurring pentest finding into a continuously enforced baseline.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.