Clickjacking
CWE-1021OWASP A05:2021Updated September 4, 20265 min read
In a clickjacking attack, someone loads your application invisibly in a frame on their own page and lays their own buttons over it. The victim believes they are clicking on the attacker's site, but in reality they click a button inside your application, where they are still logged in. The action runs with all the rights of that user.
Most attacks revolve around input that an application trusts when it should not. Clickjacking works differently: the attacker abuses not your code but the eyes of your user. This article covers how an invisible frame redirects a click, why older JavaScript tricks do not help, and which two headers close the problem in one go.
What is clickjacking?
Clickjacking is an attack in which someone loads your application inside a frame on their own page, makes that frame almost transparent, and places their own layout on top of it. The user sees the attacker’s page, but their mouse clicks and keystrokes land inside your application, where they are still logged in.
Think of a sheet of glass with a button drawn on it, placed exactly over the button of a cash machine. You see “check balance”, you press what you believe is that button, but your finger actually operates the “withdraw cash” button underneath. The machine does nothing wrong: it registers a genuine press by a genuine customer. The deception lies entirely in the layer in between.
That is precisely why clickjacking is so hard for a user to spot. No password is stolen, no malicious script runs on your domain, and the session is completely legitimate. The attacker only steers where the click ends up.
How does a clickjacking attack work?
The attack consists of two layers positioned exactly on top of each other. At the bottom sits your application in an iframe, scaled and shifted until the desired button lies under the victim’s cursor. On top sits the bait page, with an appealing reason to click in that very spot.
Vulnerable:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: no-store
This response makes no statement at all about who may embed the page. Any site on the internet may therefore place this application in a frame, and the browser will happily cooperate. That is all an attacker needs:
<!-- On the attacker's page -->
<style>
iframe {
position: absolute; top: -180px; left: -420px;
width: 1200px; height: 900px;
opacity: 0.02; /* practically invisible, yet clickable */
border: 0;
}
.bait { position: absolute; top: 320px; left: 300px; z-index: -1; }
</style>
<div class="bait">
<h1>You have won a prize</h1>
<button>Claim</button>
</div>
<iframe src="https://portal.example/settings/administrators"></iframe>
The frame sits on top, is almost transparent and therefore captures the click; the bait text sits underneath and is pure decoration. When the victim clicks “Claim”, the pointer actually hits the “Add administrator” button in the embedded portal. The session cookie travels along automatically, the request comes from the user themselves, and your server sees a perfectly normal action.
Safe:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY
With frame-ancestors 'none' the browser refuses to render the page inside any frame at all; the empty or blocked frame makes the attack useless. The line X-Frame-Options: DENY says the same thing in the older, more widely supported form and serves as a safety net for outdated browsers and webviews. If you do embed your own pages, for instance inside a portal, use frame-ancestors 'self' and SAMEORIGIN respectively. If a specific partner must embed you, name them explicitly with frame-ancestors https://partner.example, and never with a wildcard.
What is the impact of clickjacking?
The severity depends entirely on what a single click can achieve. On an informational page the effect is negligible and the finding stays low. As soon as the application contains actions that can be completed in one movement, that picture shifts: deleting an account, confirming a payment, approving a connection to an external service, or granting someone administrator rights.
Clickjacking becomes more dangerous the more efficiently your interface is designed. The button that settles something “in one click” is exactly the button an attacker wants to get under the victim’s cursor. A variant of this, sometimes called likejacking or cursorjacking, uses the same technique to capture drag movements or keystrokes, which brings completed forms within reach as well.
One thing is worth keeping in mind: the victim notices nothing, and neither do your logs. The audit trail shows an ordinary user pressing an ordinary button. That makes later investigation difficult and abuse hard to prove.
How do you detect clickjacking?
The first check is trivial: request a page and see whether the response carries a Content-Security-Policy with frame-ancestors or an X-Frame-Options. If both are missing, the page is embeddable in principle. A tester then confirms this with their own HTML file that loads the page in an iframe: if the application simply appears, the weakness is demonstrated.
After that the real work begins, because the interesting questions are more subtle. Does the header apply to all routes or only to the home page? Is it sent with error pages and with responses from a cache or CDN as well? Is there perhaps an outdated ALLOW-FROM value that no modern browser honours any more? And has the protection accidentally been switched off for a subdomain that does offer sensitive functions? During a penetration test, AssistSec walks the full route tree rather than taking a single sample, and also assesses which actions in the application are irreversible in one click, because that determines whether the finding comes out low or medium.
How do you prevent clickjacking?
- Send a
Content-Security-Policywith an explicitframe-ancestorsdirective on every HTTP response:'none'if you are never embedded,'self'if you only embed yourself. - Add
X-Frame-Options: DENYorSAMEORIGINas a safety net for older browsers and webviews. - Always name permitted embedders explicitly and never use a wildcard in
frame-ancestors. - Configure the headers centrally in the web server, reverse proxy or middleware, so new routes inherit them automatically.
- Do not rely on JavaScript framebusters; the
sandboxattribute on an iframe switches them off easily. - Ask for a second, deliberate confirmation on irreversible or sensitive actions, for example re-entering the password.
- Check that error pages, redirects and responses from your CDN send the headers as well.
Sources
Frequently asked questions
Is X-Frame-Options still needed alongside a CSP?
For modern browsers, frame-ancestors in the Content Security Policy takes precedence and even overrides X-Frame-Options when both are present. Sending both is still sensible, because older browsers and some embedded webviews do not understand frame-ancestors and fall back on the older header.
Why do JavaScript framebusters no longer work?
Framebusters, scripts that check whether the page sits inside a frame, were used for years but can be defeated with the sandbox attribute on the iframe, which simply switches off the scripts of the embedded page. A defence the attacker can turn off is no defence; only an HTTP header works reliably.
Is clickjacking serious if there are no sensitive buttons?
Then the impact stays limited, which is also why the finding is often rated low. The severity depends entirely on what a single click can achieve: a delete-account button, a payment confirmation or the granting of rights turns the same weakness into a medium or high issue.
Does a CSRF token protect against clickjacking?
No. In a clickjacking attack the real user clicks inside your real page, so the CSRF token travels along quite happily, because it is your own form. CSRF tokens and frame protection cover different attacks and are not substitutes for one another.
Related articles
- VulnerabilitiesCWE-693A05:2021Missing Content Security PolicyWithout a Content Security Policy the browser may load scripts from any source. Learn what a CSP does, how to build one, and which mistakes make it useless.
- VulnerabilitiesCWE-79A03:2021Cross-site scripting (XSS)Cross-site scripting (XSS) lets attackers inject malicious scripts into web pages that run in visitors' browsers. Learn how XSS works and how to prevent it.
- VulnerabilitiesCWE-352A01:2021Cross-site request forgery (CSRF)Cross-site request forgery (CSRF) explained: how an attacker abuses a logged-in user's browser to perform unwanted actions, and how you prevent it.
- VulnerabilitiesCWE-16A05:2021Security misconfigurationSecurity misconfiguration explained: how default passwords, debug modes and open cloud buckets let attackers in, and how to harden your systems.