October 2, 2026
$6,200 for an Invisible Button: Clickjacking an OAuth Consent Screen Into Account Takeover
Clickjacking has a reputation problem. Most testers treat it as a box to check โ send a request, see if X-Frame-Options is missing, write aโฆ

By T4nv1
7 min read
Clickjacking has a reputation problem. Most testers treat it as a box to check โ send a request, see if X-Frame-Options is missing, write a low-severity finding, move on. That reputation exists because most clickjacking findings genuinely are low severity: tricking someone into liking a post or following an account isn't worth much to an attacker. This one was different, because the page I could frame wasn't a social action. It was an OAuth consent screen deciding what a third-party application could do with someone's account.
The target, which I'll call Wavecrest here per their program's anonymization requirement, is a workplace productivity platform that supports third-party integrations through standard OAuth โ external apps request access, the user sees a consent screen listing what's being requested, and clicking "Allow" grants it. That consent screen was the page in question.
Confirming the Page Could Be Framed at All
The first test for any clickjacking candidate is simple: can the page load inside an iframe embedded on a page you control at all. Modern applications generally prevent this deliberately, either through the X-Frame-Options header or the newer, more flexible frame-ancestors directive within Content-Security-Policy, specifically because framing is the prerequisite for every clickjacking attack โ without it, none of what follows is possible.
<iframe src="https://wavecrest-app.com/oauth/authorize?client_id=..."></iframe><iframe src="https://wavecrest-app.com/oauth/authorize?client_id=..."></iframe>Loaded on a test page on my own infrastructure, this rendered Wavecrest's actual OAuth consent screen, fully interactive, inside my iframe. No X-Frame-Options header present in the response, and no frame-ancestors directive in whatever Content-Security-Policy Wavecrest was sending elsewhere. The page simply had no framing protection at all. That's a real finding on its own, but by itself it's still just a missing header โ the question that actually determines severity is what a user could be tricked into doing once the page is embedded, and whether the consequences of that action matter.
Why an OAuth Consent Screen Is a High-Value Clickjacking Target
Most clickjacking proof-of-concept writeups demonstrate tricking a user into an action with mild or cosmetic consequences, because most framed pages only offer mild or cosmetic actions. An OAuth consent screen is structurally different: it offers exactly one meaningful interactive element โ an "Allow" button โ and clicking it, in the real application's own context, grants a third-party application whatever permission scope the authorization request specified. If an attacker controls both the page framing the consent screen and the OAuth client requesting access, the attacker gets to choose what's being consented to, and the victim, if tricked into clicking through an invisible overlay, never sees the real consent screen's content clearly enough to object.
I registered a legitimate-looking third-party OAuth client against Wavecrest's developer platform โ the same registration process any real integration would go through, available to any account without special review โ and requested a permission scope broader than most users would expect from a normal consent flow: read access to documents, calendar, and contact lists, rather than something narrower and more innocuous-sounding.
Building the Attack Page
The standard clickjacking technique layers the real target page in a fully transparent iframe directly on top of an attacker-controlled page showing something the victim actually wants to interact with โ positioned so that whatever the victim believes they're clicking on the visible decoy page, their click actually lands on the invisible "Allow" button sitting exactly beneath it.
<html>
<head>
<style>
.decoy-button {
position: absolute;
top: 420px;
left: 180px;
z-index: 1;
}
iframe {
position: absolute;
top: 0;
left: 0;
width: 800px;
height: 600px;
opacity: 0.0;
z-index: 2;
}
</style>
</head>
<body>
<h1>Claim Your Free Gift Card</h1>
<button class="decoy-button">Click Here to Claim</button>
<iframe src="https://wavecrest-app.com/oauth/authorize?client_id=<my-client-id>&scope=documents+calendar+contacts"></iframe>
</body>
</html><html>
<head>
<style>
.decoy-button {
position: absolute;
top: 420px;
left: 180px;
z-index: 1;
}
iframe {
position: absolute;
top: 0;
left: 0;
width: 800px;
height: 600px;
opacity: 0.0;
z-index: 2;
}
</style>
</head>
<body>
<h1>Claim Your Free Gift Card</h1>
<button class="decoy-button">Click Here to Claim</button>
<iframe src="https://wavecrest-app.com/oauth/authorize?client_id=<my-client-id>&scope=documents+calendar+contacts"></iframe>
</body>
</html>The precise pixel alignment between the decoy button and the real consent screen's "Allow" button took some iteration โ OAuth consent screens generally render consistently enough across a given user's session that once I'd measured the button's position in a real browser window at a standard viewport size, the overlay lined up reliably on repeat tests. The opacity: 0.0 iframe sits directly on top of the decoy page, invisible to the eye but still fully receiving clicks, with its z-index ensuring it intercepts the click event before it could reach anything beneath it.
Confirming the Attack End-to-End, Safely
I tested this against my own Wavecrest account, logged in in the same browser as the one loading my attack page โ a deliberately realistic setup, since clickjacking specifically depends on the victim already being authenticated to the target in their browser at the time they encounter the malicious page, exactly as would be true for any real user with Wavecrest open in another tab while browsing elsewhere. I loaded the attack page, saw only the decoy "Claim Your Free Gift Card" button with no visual indication anything else was present, and clicked it.
Checking my Wavecrest account's connected-applications settings afterward confirmed the third-party OAuth client I'd registered now had active access to my documents, calendar, and contacts โ granted without me ever seeing, reading, or consciously interacting with the real consent screen's content at all. From an attacker's perspective, this is a complete account-data compromise achieved through a single misdirected click on what looked like an unrelated, innocuous button.
Why This Rates Well Above a Typical Clickjacking Finding
It's worth stating directly why this deserved a meaningfully higher severity rating than clickjacking findings typically receive, since "missing X-Frame-Options" on its own is routinely triaged as low or informational by programs that have seen the same low-impact version of this report many times before. The distinguishing factor here is that the framed page wasn't a cosmetic action โ it was an authorization decision with real, lasting access consequences, and the attacker fully controlled both halves of the equation: the decoy page luring the click, and the OAuth client receiving whatever scope got consented to. Once granted, that access persists independent of the clickjacking vector itself โ revoking it requires the victim to notice an unfamiliar connected application in their account settings and manually remove it, which most users never proactively check.
Keeping the Demonstration Contained
Everything here ran against my own Wavecrest test account, using an OAuth client I'd registered specifically for this research and clearly labeled as such in its application name and description, visible to anyone who checked Wavecrest's developer console. I didn't distribute the attack page anywhere, didn't attempt this against any other account, and revoked the OAuth client's access to my test account and deleted the registered application entirely as soon as I'd captured sufficient evidence of the full chain.
The Report
Clickjacking reports chained into something with real consequences need to work hard against triage fatigue, because a huge fraction of clickjacking submissions genuinely are low-value, and a report needs to make the escalation path to real impact unmistakable from the opening paragraph rather than assuming severity speaks for itself.
Title: Missing clickjacking protection on OAuth consent screen enables UI redress attack granting unauthorized third-party application access
Root cause: The OAuth authorization/consent page sends no X-Frame-Options header and no frame-ancestors Content-Security-Policy directive, allowing the page to be embedded in an attacker-controlled iframe. Combined with the ability for any account to register an OAuth client requesting broad permission scopes with no additional review, an attacker can construct a transparent-iframe overlay attack that tricks a victim into unknowingly consenting to third-party access over their documents, calendar, and contacts.
Reproduction: The full attack page HTML and CSS, a description of the button-alignment process, and a before/after comparison of the connected-applications settings page showing the unauthorized OAuth grant appearing after a single misdirected click โ demonstrated entirely against a self-owned test account with a clearly labeled research-purpose OAuth client.
Impact: Any authenticated user who encounters the attack page while logged into Wavecrest in the same browser can be tricked into granting a malicious third-party application persistent access to their documents, calendar, and contact data, with no visual indication anything occurred and no straightforward way to detect the compromise short of manually auditing connected applications.
Fix recommendations:
- Add
X-Frame-Options: DENYor an equivalentframe-ancestors 'none'Content-Security-Policy directive to the OAuth consent page specifically, and ideally to all authenticated pages platform-wide where framing serves no legitimate purpose - Consider requiring an additional, frame-resistant confirmation step for consent requests involving broad permission scopes โ a re-entered password or a distinct, clearly-labeled confirmation dialog that's more resistant to overlay-based manipulation than a single button click
- Notify users by email whenever a new third-party application is granted access to their account, so a successful attack of this kind is at least visible after the fact rather than silently persisting until a manual audit
What Happened After
Wavecrest's team confirmed this within four days and added the missing frame-protection header to the consent page within a week as an immediate fix. The more involved recommendation โ an additional confirmation step for broad-scope consent requests โ took longer to design properly and shipped roughly a month later, alongside the email notification for new application grants. Their resolution notes mentioned auditing other sensitive, action-taking pages across the platform for the same missing header, finding one additional instance on a billing-plan-change confirmation page. The report closed at $6,200, rated high severity, specifically because of the OAuth consent escalation rather than the underlying missing-header issue alone, which the triager noted explicitly would have rated significantly lower in isolation.
Why Clickjacking Deserves a Second Look Past the Header Check
This bug class gets dismissed quickly because the overwhelming majority of real-world instances genuinely don't matter much โ but the header check itself is only step one, and stopping there means missing the cases that actually do matter. A few habits worth carrying forward:
Don't stop at confirming a page can be framed. That's the prerequisite, not the finding. The actual severity question is always "what single interactive action does this page offer, and what happens if a user takes it without meaning to."
Authorization and consent screens are the highest-value clickjacking targets by a wide margin. Anywhere a single click grants access, changes a security-relevant setting, or confirms an irreversible action is worth testing specifically, even on platforms where most other framed pages would be low-severity findings.
Build the proof of concept with real pixel-accurate alignment, not just a conceptual overlay. A triager evaluating clickjacking severity will reasonably want to see that the attack is actually practical to execute precisely, not just theoretically possible โ getting the alignment right is part of demonstrating real-world exploitability.
Chain to whatever concrete consequence the framed action actually has, and show it explicitly โ a before/after account state, not just a description of what should theoretically happen. The difference between a dismissed low-severity report and a well-paid high-severity one is almost always in how convincingly the real consequence gets demonstrated, not in the underlying missing header itself.
What stuck with me about this one is how completely unglamorous the root cause was next to how serious the consequence turned out to be. A missing header, a kind most programs have seen reported dozens of times with a shrug attached. It was only the specific page it was missing from โ the one single screen on the entire platform where a misdirected click hands over real, lasting access to someone else's data โ that turned a routine low-severity report into something worth taking seriously.