Clickjacking — formally "UI redressing" — is an attack where a malicious site loads your application inside an invisible or disguised frame and overlays it on decoy content, so a victim who thinks they are clicking the attacker's page is actually clicking buttons in your app. Because the clicks happen inside the victim's authenticated session, the attacker can trigger state-changing actions: transferring funds, changing settings, granting OAuth permissions, or deleting data. It is classified as CWE-1021 (Improper Restriction of Rendered UI Layers or Frames).
The technique was publicly detailed in 2008 by researchers Robert Hansen and Jeremiah Grossman, who demonstrated it against the Adobe Flash Player settings manager to silently enable a victim's webcam and microphone. It went mainstream on social platforms as "likejacking" — invisible "Like" buttons layered under enticing content — and the same primitive still works against any site that lets itself be framed. Unlike injection bugs, clickjacking requires no flaw in your code at all; the vulnerability is simply permitting your pages to be framed by anyone.
How Clickjacking Works
Browsers let one page embed another using an iframe. Clickjacking weaponizes that by making the framed target invisible while positioning it precisely under a lure the victim wants to interact with. The attacker sets the framed page's CSS opacity to zero (or covers it with an opaque decoy that has a strategic hole), then aligns a tempting element — "Play video," "Claim prize" — directly beneath a sensitive control in your app, such as a "Confirm transfer" button.
The victim visits the attacker's page, sees only the lure, and clicks. The click passes through to the invisible frame and lands on your app's button, executing inside the victim's logged-in session. Nothing about the request is malformed, and no credentials are stolen — the victim's own browser performs a genuine, authenticated action that the victim never intended. Variants include drag-and-drop hijacking (tricking users into dragging data), cursorjacking (faking the pointer position), and combining framing with a pre-filled form to complete a multi-step flow.
The defense is not to sanitize input; it is to tell the browser who, if anyone, is allowed to frame your pages.
Vulnerable vs. Fixed
Any framable, state-changing page is exposed. The fix is a framing policy delivered in response headers.
HTTP/1.1 200 OK
Content-Type: text/html
# VULNERABLE: no framing policy — any site may embed this page
HTTP/1.1 200 OK
Content-Type: text/html
Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: DENY
# FIXED: modern browsers honor frame-ancestors; XFO covers legacy clients
In application code the header is a one-liner, for example in Express:
// FIXED: send both headers on every HTML response
app.use((req, res, next) => {
res.setHeader("Content-Security-Policy", "frame-ancestors 'self'");
res.setHeader("X-Frame-Options", "DENY"); // legacy fallback
next();
});
frame-ancestors 'self' is the authoritative modern control — it supports multiple trusted origins and is not limited like the older X-Frame-Options. Send X-Frame-Options: DENY alongside it only to cover outdated browsers; where they conflict, CSP wins.
Prevention Checklist
- Set
Content-Security-Policy: frame-ancestorsto'self'or an explicit list of trusted origins on every page, especially authenticated and state-changing ones. - Send
X-Frame-Options: DENYorSAMEORIGINas a fallback for legacy browsers. - Do not rely on
X-Frame-Optionsalone — it cannot express multiple allowed origins and is superseded by CSP. - Require explicit confirmation (re-authentication, a typed value, or a CAPTCHA) for the highest-impact actions so a single hijacked click is not enough.
- Use
SameSitecookies (LaxorStrict) as defense in depth against cross-site request rides. - Apply the policy at a gateway or middleware layer so no route can forget it.
How Safeguard Detects Clickjacking
Whether a page is framable is decided by response headers at runtime, so Safeguard checks it by fetching your pages and analyzing the framing directives directly. The dynamic application security testing engine crawls your routes and flags any HTML response — particularly authenticated, state-changing views — that lacks a frame-ancestors directive or a restrictive X-Frame-Options, reporting exactly which pages are exposed.
Each finding is prioritized by Griffin AI, which highlights the pages where a hijacked click would do real damage and maps the issue to CWE-1021, while automated remediation proposes the header configuration for your stack. Dropping the Safeguard CLI into CI lets you assert the header is present so a regression fails the build. For a broader view of how header and configuration checks fit alongside code scanning, see our platform comparison.
Frequently Asked Questions
Is clickjacking still a real threat in 2026? Yes. Any page that can be framed by an arbitrary origin and performs a sensitive action on click is exploitable, and plenty of applications still ship without a framing policy. The controls are trivial to deploy, which makes an unprotected page an easy, avoidable win for attackers.
Do SameSite cookies alone stop clickjacking?
No. SameSite limits cross-site cookie sending and helps against CSRF, but clickjacking rides the victim's own first-party session inside the frame. You still need a framing policy — frame-ancestors — to prevent your pages from being embedded in the first place.
What is the difference between frame-ancestors and X-Frame-Options?
X-Frame-Options is the older header and only supports DENY or SAMEORIGIN (a single origin at most). CSP's frame-ancestors directive is the modern replacement: it accepts a list of trusted origins and takes precedence in browsers that support both. Use frame-ancestors as primary and X-Frame-Options as a legacy fallback.
Want to know which of your pages an attacker could frame right now? Sign up free or read the header-testing guide in the Safeguard docs.