DoubleClickjacking: The Attack That Bypasses X-Frame-Options, CSP, and SameSite
Published June 2025 · sourced from Paulos Yibelo's disclosure (Jan 2025), The Hacker News, and OWASP
Every mainstream clickjacking defense works the same way: it stops your page from being loaded inside someone else's iframe. X-Frame-Options, the CSP frame-ancestors directive, and SameSite cookies all assume the attacker needs to embed your site to deceive the user.
DoubleClickjacking, disclosed by Paulos Yibelo in January 2025, embeds nothing. It swaps the browser window the user is looking at in the gap between two clicks of a double-click, using window.opener.location. Because there is no iframe, none of the framing headers apply.
How it works
The sequence from Yibelo's write-up:
- The attacker hosts a page with a button that opens a new popup window.
- The popup opens on top and prompts the user to double-click something innocuous — a fake captcha, a “verify you are human” widget, a cookie banner.
- While the user is about to double-click, the popup uses
window.opener.locationto navigate the original (parent) window to a sensitive target page — an OAuth authorization screen, a payment confirmation, an account settings page. - On the first click (the
mousedown), the popup closes itself. - The second click of the double-click lands on the now-exposed sensitive button in the parent window, which has shifted into the exact position the user's cursor was already heading toward.
- The user has authorized the attacker's OAuth app, confirmed a payment, or toggled a security setting without ever knowing the window changed.
Why it bypasses existing protections
- X-Frame-Options: irrelevant. No iframe is used, so there is nothing for it to block.
- CSP
frame-ancestors: irrelevant for the same reason. There is no framing relationship to deny. - SameSite cookies: this is the key insight. The attack runs in a separate popup window and navigates the parent as a top-level document. The target page loads as a top-level navigation, so its cookies are sent.
SameSite=Lax— the default in modern browsers — explicitly permits cookies on top-level navigations. The session is fully authenticated.
The attack exploits the timing gap between mousedown and click, not iframe transparency. That is a property of the input model, which framing headers were never designed to address.
Real impact
- OAuth account takeovers on, in Yibelo's words, “almost every site that supports OAuth.”
- One-click account changes: disabling security settings, authorizing money transfers, deleting data.
- Browser extensions are also vulnerable, including crypto wallet and VPN extensions that expose confirmation prompts.
- A mobile variant called “DoubleTap” reproduces the attack on phones.
Attacker proof-of-concept
A simplified version of the swap from Yibelo's blog. The popup redirects its opener to the target, then closes on the first mousedown:
// In the popup window (the fake captcha)
window.onmousedown = () => {
// Send the parent window to the sensitive target page
window.opener.location = "https://target.com/oauth/authorize?client_id=ATTACKER";
// Remove ourselves so the second click lands on the target
window.close();
};By the time the user's second click fires, the popup is gone and the parent window's authorize button is under the cursor.
Mitigation: the Dropbox / Stripe / GitHub pattern
Yibelo's recommended client-side defense is to keep critical buttons disabled by default and re-enable them only after a genuine user gesture — mouse movement or a keyboard Tab. A swap-and-close attack provides neither of those signals during the window transition, so the sensitive button stays inert.
// Disable critical controls until a real interaction is observed
if (!window.matchMedia("(pointer: coarse)").matches) {
document.querySelectorAll(".critical").forEach((el) => {
el.disabled = true;
});
const enable = () => {
document.querySelectorAll(".critical").forEach((el) => {
el.disabled = false;
});
};
// A genuine user moving the mouse or tabbing re-enables the buttons
document.addEventListener("mousemove", enable);
document.addEventListener("keydown", (e) => {
if (e.key === "Tab") enable();
});
}This is the same shape of defense that sites like Dropbox, Stripe, and GitHub apply to their authorization and account-control screens: block the action until the page has observed real interaction, then allow it. It defeats attacks that depend on simulated or mistimed clicks fired during a window transition.
This protection only exists if the site ships the script. There is no browser-level enforcement — an unmodified site has nothing stopping the attack.
Long-term defense
Yibelo proposed a hypothetical Double-Click-Protection: strict response header, or an equivalent CSP extension, that would let browsers neutralize rapid window swaps without per-site JavaScript. As of mid-2026, no browser has shipped this. The gesture-based JavaScript mitigation remains the only available defense.
What ClickJack Test detects — and what it does not
Our tool checks your X-Frame-Options and CSP frame-ancestors headers. Those headers stop classic, iframe-based clickjacking, which is the overwhelming majority of clickjacking seen in the wild.
No automated scanner can determine whether a site is vulnerable to DoubleClickjacking. It depends on how each sensitive button behaves and whether the site implements gesture-based protection — properties that live in application code, not in HTTP headers. The headers we check are still necessary; they are no longer sufficient.