Clickjacking (UI Redress)
Also known as: Clickjacking, UI redress attack, UI redressing, Likejacking
A web attack in which a sensitive page is loaded in a hidden or disguised frame on another site and positioned so the victim's clicks land on the framed page's buttons instead of what they think they are clicking.
How it works
Clickjacking, also called UI redress, tricks a user into clicking something different from what they perceive. An attacker's page embeds the target application in an iframe, makes it transparent or hides it under a decoy, and aligns the target's button beneath something the victim wants to click. The click goes to the real application, with the victim's own session.
The flaw needs two conditions. The sensitive page allows itself to be framed by an arbitrary origin, and a single click, or a few, performs a meaningful action such as approving a payment, changing a setting or granting access. Because the request really comes from the victim's browser on the real page, token checks such as anti-CSRF tokens do not stop it.
The primary defence is to tell the browser who may frame the page. The Content-Security-Policy frame-ancestors directive does this precisely: 'none' blocks all framing, 'self' allows the same origin, and a list allows named partners. X-Frame-Options with DENY or SAMEORIGIN is the older equivalent and a useful fallback.
Scripts that try to break out of frames are fragile and easy to defeat, so they are only defence in depth. For truly sensitive actions, ask the user to re-confirm in a way a single stray click cannot satisfy, such as typing a value or re-entering a password.
Walk through it
- 1Review the response headers
- 2Name the missing header
- 3Choose the policy value
- Add the legacy fallback
- Re-confirm and verify the fix
A pre-release review flags the payment approval page. One click on its Approve button releases funds. Read the headers the server returns for that page and note which ones tell the browser who may embed it.
1HTTP/1.1 200 OK2Content-Type: text/html; charset=utf-83Set-Cookie: session=<opaque>; Path=/; Secure; HttpOnly; SameSite=Lax4Cache-Control: no-store5X-Content-Type-Options: nosniff6 7# Fixed version should add:8# Content-Security-Policy: frame-ancestors 'none'9# X-Frame-Options: DENYSpot it
- Response headers for sensitive pages that lack both Content-Security-Policy frame-ancestors and X-Frame-Options.
- A frame-ancestors value of * or a very broad allow-list on pages that perform actions.
- Referer or Sec-Fetch-Dest: iframe on requests to action pages from origins that are not partners.
- Users reporting approvals or settings changes they did not intend, shortly after visiting an unrelated site.
- Pages that rely only on JavaScript frame-busting without any header protection.
Application access log
ts=2026-10-11T10:02:11Z method=GET path=/payments/approve user=u_2290 status=200 sec_fetch_dest=iframe referer=https://prize-draw.example/play
ts=2026-10-11T10:02:13Z method=POST path=/payments/approve user=u_2290 status=302 sec_fetch_dest=iframe referer=https://prize-draw.example/playHeader scan
ts=2026-10-11T08:00:00Z host=portal.contoso-pay.example path=/payments/approve csp_frame_ancestors=absent x_frame_options=absent finding=CWE-1021splSplunk: action pages requested inside a foreign frame
index=app sourcetype=access sec_fetch_dest=iframe path IN ("/payments/approve","/account/*")
| where NOT match(referer, "^https://portal\.contoso-pay\.example")
| stats count by user, path, refererAllow-list real partner embeds before alerting.
grepgrep: header scan results missing framing protection
grep 'csp_frame_ancestors=absent' header-scan.log | grep 'x_frame_options=absent'Stop it
Send Content-Security-Policy: frame-ancestors
Use frame-ancestors 'none' for pages that must never be embedded, 'self' for same-origin framing, or an explicit list of partner origins. This is the primary control and the one the modern browsers enforce.
Add X-Frame-Options as a fallback
Send X-Frame-Options: DENY, or SAMEORIGIN where same-origin framing is needed, for older clients. Do not use the obsolete ALLOW-FROM value.
Re-confirm sensitive actions; treat frame-busting as extra
Require a step a stray click cannot satisfy, such as typing a confirmation value or re-entering a password, for approvals, payments and permission grants. JavaScript frame-busting is easy to defeat and only adds depth.
Set framing headers on every response
Vulnerable
app.use((req, res, next) => {
res.set("X-Content-Type-Options", "nosniff");
next();
});Hardened
app.use((req, res, next) => {
res.set("X-Content-Type-Options", "nosniff");
res.set("Content-Security-Policy", "frame-ancestors 'none'");
res.set("X-Frame-Options", "DENY");
next();
});Use an allow-list value instead of 'none' only for pages that partners must embed.
Response headers with framing protection
Vulnerable
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8Hardened
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY- Send frame-ancestors on every HTML response by default, from one place such as a middleware or the edge.
- Keep X-Frame-Options DENY or SAMEORIGIN alongside it for older browsers.
- Never use * as a frame-ancestors source on pages that perform actions.
- Require re-confirmation for payments, permission grants and credential changes.
- Mark session cookies SameSite=Lax or Strict so embedded third-party contexts do not carry them.
- Add a header check to CI and to periodic scans so a new route cannot ship without framing protection.
If it already happened
Deploy frame-ancestors and X-Frame-Options at the edge or middleware now, and pause one-click approval actions until re-confirmation is in place.
Identify actions taken from framed contexts using Sec-Fetch-Dest and Referer data, review and reverse the unintended ones, and revoke any grants made.
Notify affected users, confirm the page now refuses to load in a foreign frame, and monitor for blocked framing attempts.
Make framing headers a default in the shared template, add a header check to CI, and add a review item for every one-click sensitive action.
Check yourself
1. What makes a page vulnerable to clickjacking?
2. Which header is the primary, modern control for preventing a page from being framed by other sites?
3. A payment approval page must never be embedded anywhere. Which setting fits?