What Is Clickjacking? UI Redressing and Clickjacking Explained
A complete technical guide to clickjacking: how transparent iframes trick users into clicking things they didn't intend, and how to detect, verify, and prevent it.
Short answer
Clickjacking (also called UI redressing) tricks a victim into clicking something they can't actually see, by layering a transparent target page over a decoy page the victim believes they're interacting with. The click lands on the hidden page, not the visible one — and the victim never had a chance to consent to what they just triggered.
The idea in one minute
Imagine a street musician playing a violin case with a sign that says "for a good cause." You reach into your pocket, pull out a coin, and drop it in. You feel good about it. Later you find out the case actually belonged to a stranger standing behind the musician, the "good cause" sign was just a decoy taped to the front, and your donation went somewhere you never intended.
That's clickjacking. The page you see — the button you meant to click, the captcha you thought you were solving — is the decoy. The real page is underneath, invisible, with its own buttons aligned precisely beneath the ones you can see. Every click you make on the visible layer lands on the hidden layer beneath. The victim gives informed consent to the decoy but the invisible page receives the action.
How clickjacking actually works
Three ingredients make it possible:
- The target application does not prevent being embedded in an
<iframe>on a third-party origin - The attacker can overlay a transparent iframe of the target page at exact coordinates above a decoy UI
- The victim clicks what they see, but the browser registers the click on the hidden iframe layer
An attacker constructs an attacker-controlled page that loads the target application in a fully transparent <iframe>. The attacker then arranges decoy buttons, links, or game elements on the visible layer at precisely the same screen coordinates as the real interactive elements of the target page. When the victim clicks on what appears to be the decoy, the event targets the iframe below.
| Variant | Mechanism | Notable feature |
|---|---|---|
| Classic clickjacking | Transparent iframe overlaid on decoy elements | Straightforward, technically simple to construct |
| Cursor jacking | An invisible iframe follows the victim's cursor using pointer-events: none on the visible layer | Cursor appears to interact with the decoy but every click goes to the iframe below |
| Likejacking | Clickjacking specific to social-media "like," "share," or "follow" buttons | Widely demonstrated against Facebook's early Like button |
| Nested clickjacking | Multiple stacked iframes, each transparent, to chain actions across different origins | Allows multi-step attacks (e.g. authorise a payment, then confirm it) |
| Browserless/element-level clickjacking | Malicious browser extensions or compromised scripts reposition legitimate elements over unrelated actions | Bypasses frame-level defenses since no <iframe> is involved |
The request flow
sequenceDiagram
participant Victim
participant AttackerSite as Attacker's Decoy Page
participant Target as Target Application (hidden iframe)
Victim->>AttackerSite: Loads decoy page
AttackerSite->>Target: Loads target app in transparent iframe
Target-->>AttackerSite: Renders real buttons at known coordinates
AttackerSite-->>Victim: Shows decoy button aligned over hidden target button
Victim->>AttackerSite: Clicks the visible decoy button
AttackerSite->>Target: Click event passes through to hidden iframe
Target-->>Victim: Action executes on the victim's authenticated session
A minimal example
A vulnerable profile page has a "Delete Account" button with no framing protection. An attacker creates this decoy page:
<style>
iframe {
position: absolute;
top: 0; left: 0;
width: 800px; height: 600px;
opacity: 0; /* fully transparent */
z-index: 10;
}
.decoy-button {
position: absolute;
top: 200px; left: 100px;
z-index: 5;
padding: 10px 20px;
background: #4CAF50;
color: white;
border: none;
cursor: pointer;
}
</style>
<iframe src="https://target.example.com/settings"></iframe>
<button class="decoy-button">Click here to claim your prize!</button>
The victim sees a green button promising a prize. They click it. The click lands on the invisible "Delete Account" button in the hidden iframe, positioned directly underneath. The account is deleted.
The fix prevents the page from being framed at all:
# Flask response header
@app.after_request
def set_frame_options(response):
response.headers["X-Frame-Options"] = "DENY"
return response
Or via CSP:
response.headers["Content-Security-Policy"] = "frame-ancestors 'none'"
With either header in place, the browser refuses to render the page inside any <iframe>, and the attack collapses.
Why the vulnerability exists
- The application omits
X-Frame-OptionsorContent-Security-Policy: frame-ancestorsheaders, so the browser happily renders the page inside a third-party iframe - Application-level logic assumes that iframes are internal-only, without enforcing it via response headers
- The page's interactive elements (submit buttons, payment confirmations, authorisation flows) can be triggered with a single click and don't require additional confirmation that the page is the top-level document
- Defence was overlooked for legacy pages, static assets, or third-party-widget rendering endpoints that were never meant to be embedded but also never locked down
What attackers look for
- Any state-changing action that can be triggered by a single click (account deletion, fund transfer, privilege escalation, content posting)
- OAuth or authorisation flows that approve scopes with a single "Authorize" or "Allow" button
- Pages that render without
X-Frame-Optionsorframe-ancestorsheaders - Applications that use frame-based embedding for legitimate features (payment widgets, dashboards) — these are whitelisted cases that must be locked to specific origins rather than left open
Detection
- Header inspection: check responses for
X-Frame-Options(DENYorSAMEORIGIN) andContent-Security-Policy: frame-ancestors. If neither is present, the page can likely be framed. - Frame proof-of-concept: construct a simple HTML page with an
<iframe src="https://target.example.com/page">and load it in a browser. If the content renders inside the frame, the page is framable. - Click confirmation: once framability is confirmed, align a decoy button over a real action element and verify the action executes when the decoy is clicked.
Verification: real vulnerability or false positive?
A missing X-Frame-Options header is not itself a vulnerability. Some applications intentionally allow framing for legitimate embedding (dashboards, widgets, payment iframes). The real question is: does a framed page contain an actionable element that, when clicked, performs a sensitive operation on the victim's behalf? If the framed page is informational only (a read-only profile, a public document viewer), the attack surface is limited even though framing is possible. Confirm by constructing an overlay proof-of-concept and demonstrating that a click on the decoy layer triggers an actual state change in the hidden iframe, using the victim's authenticated session.
Real-world impact
- Account takeover: a single click on a hidden "Authorize" button during an OAuth flow grants the attacker's application full API access to the victim's account
- Privilege escalation: an admin, while clicking a decoy setting on a separate tab, inadvertently grants an attacker's user elevated permissions
- Financial fraud: hidden "Confirm Payment" or "Transfer Funds" buttons execute transactions the victim never authorised
- Malware distribution: clickjacking can drive drive-by downloads by tricking users into clicking "Run" in a browser download prompt or Java applet confirmation dialog
- Camera/microphone access: a framed permission prompt can silently grant media-access permissions when the victim clicks a decoy element aligned with the browser's "Allow" button
- Multi-step editing: more sophisticated clickjacking aligns with multi-step workflows — the victim's sequence of clicks on a game or puzzle is translated into clicks on a series of hidden iframe buttons that together authorise and complete a dangerous action
Prevention
X-Frame-Options: DENY(orSAMEORIGIN): the simplest, most widely supported header.DENYblocks all framing;SAMEORIGINallows framing only by the same origin. Supported by all major browsers.Content-Security-Policy: frame-ancestors 'none'(or'self' <origin>): the modern, more flexible replacement forX-Frame-Options. Allows multiple origins in the whitelist and supports reporting.frame-ancestorssupersedesX-Frame-Optionsin browsers that support both.frame-ancestorsoverX-Frame-Optionswhen you need an origin whitelist:frame-ancestors 'self' https://trusted.example.compermits framing only from your own site and a single trusted partner, whichX-Frame-Optionscannot express.- SameSite cookies as defense-in-depth:
SameSite=LaxorStricton the session cookie won't prevent framing but does prevent the attacker's page from carrying the victim's session into the iframe in some browser behaviours — a useful secondary layer, not a primary defence. - JavaScript frame-busting (fragile, not recommended alone): a script that checks
if (top !== self)and redirects or hides content. Browsers allow the iframe's parent page to suppresstopaccess via thesandboxattribute withallow-top-navigationomitted, making this bypassable. Use headers instead. - Confirmation dialogs for sensitive actions: requiring a second confirm step before critical actions means that even if a click lands on a hidden button, the action doesn't execute without an additional conscious input.
Related vulnerabilities
- CSRF — like clickjacking, it tricks the victim's authenticated session into unintended actions; CSRF does it via forged requests, clickjacking does it via hidden clicks on a framed page
- XSS — can bypass clickjacking defences entirely, since injected script can interact with the page regardless of framing headers
- Content Security Policy —
frame-ancestorsis the CSP directive that directly prevents clickjacking, but CSP is also the mechanism that controls script execution, making the two topics technically linked - Open redirect — can be chained with clickjacking to deliver a victim to a decoy page from a trusted-looking link
Testing methodology (do this safely)
- Only test pages you're authorised to test using accounts you control.
- First check response headers for
X-Frame-OptionsandContent-Security-Policy: frame-ancestors— if either is set correctly, no further testing is needed for classic clickjacking. - If no frame-protection headers are present, create a minimal HTML proof-of-concept page that loads the target in an
<iframe>. - Open the proof-of-concept directly in a browser (not a local-file opener — some browsers apply special framing rules to
file://origins). Serve it from a separate origin if possible. - If the target renders inside the iframe, identify actionable elements and construct a decoy overlay aligned to the most sensitive action (account deletion, payment confirmation, scope authorisation).
- Document the finding with a screenshot of the framed target and a description of what a single click on the decoy would execute.
Further reading
- OWASP: Clickjacking Defense Cheat Sheet
- PortSwigger Web Security Academy: Clickjacking
- MITRE: CWE-1021 — Improper Restriction of Rendered UI Layers or Frames
- MDN: X-Frame-Options
- MDN: Content-Security-Policy: frame-ancestors
Nyxeara perspective
Clickjacking is one of the few web attacks that exploits the visual layer, not the request layer. That makes it uniquely dangerous: no forged payload, no cross-origin read, no malformed input — just a transparent iframe and a misplaced click. The defence is correspondingly simple (two response headers), but its simplicity leads to a common blind spot: teams secure their primary auth and payment flows and forget about admin panels, settings pages, API authorisation endpoints, and OAuth consent dialogs. Any single-click action on any page without X-Frame-Options or frame-ancestors is a potential clickjacking vector. The fix takes seconds. The gap is remembering where to apply it.