Back to Glossary
medium severity · Vulnerability Glossary

Clickjacking

Updated June 17, 2026

4 min read

clickjacking
UI redressing
X-Frame-Options
CSP frame-ancestors
iframe attack
transparent iframe
Content-Security-Policy

Clickjacking is a UI redressing attack where an attacker loads your site in a transparent iframe over a decoy page, so users think they're clicking the attacker's content while actually clicking buttons on yours. The fix is two response headers; the detection is loading your page in an iframe and seeing whether the browser blocks it.

What is clickjacking?

Clickjacking (also called UI redressing) works by embedding your site in an <iframe> and styling that iframe to be invisible — opacity: 0 or hidden behind other elements — then positioning a tempting decoy underneath the user's cursor.

The user sees the attacker's page, for example a "Click to win" button or a video play control. But the visible button is just bait. Layered directly above the user's click is your real page: your "Transfer funds", "Delete account", or "Confirm" button. The click passes through to your site, where the victim is already authenticated, and the action executes with their session.

Because the request comes from the real, logged-in user in a real browser, it carries valid session cookies and looks legitimate to your server. Nothing about the request itself is malicious — the attack is entirely in the visual layering.

Why it's a risk / how it's exploited

The damage depends on what one click can do on your site while the user is authenticated:

  • State changes: enabling a setting, changing email or notification preferences, granting an OAuth scope, or toggling 2FA off.
  • Money or content actions: confirming a transfer, posting, liking, following, or subscribing — early Facebook "likejacking" worms spread exactly this way.
  • Multi-step attacks: combined with drag-and-drop or pre-filled forms, several framed clicks can complete a full workflow.

A concrete scenario: an attacker sends a link to a "free gift card" page. The page frames your account-settings screen and aligns its invisible "Add attacker as authorized user" button under a flashy "Claim" button. The victim clicks Claim, and their account silently grants access. They never see your site at all.

Clickjacking is dangerous precisely because it needs no XSS, no stolen credentials, and no software vulnerability — just a page that allows itself to be framed.

How to detect it

The fastest manual check is to try to frame the page yourself. Save this as a local HTML file and open it in a browser:

<iframe src="https://your-site.com/account/settings" width="800" height="600"></iframe>

If the page renders inside the iframe, it is frameable and vulnerable. If the browser shows a blank frame and the console logs something like Refused to display ... in a frame because it set 'X-Frame-Options' to 'deny', the protection is working.

You can also inspect the headers directly:

curl -sI https://your-site.com/account/settings | grep -iE 'x-frame-options|content-security-policy'

A missing X-Frame-Options and a Content-Security-Policy with no frame-ancestors directive means there's nothing stopping the framing.

For a quick automated read across your whole header set, run our security headers checker — it flags missing X-Frame-Options and frame-ancestors along with related issues. A full website vulnerability scan will confirm framing protection across every page, not just the ones you remember to test.

How to fix it

Send two response headers on every HTML response. Content-Security-Policy: frame-ancestors is the modern primary control (it's more granular and supersedes X-Frame-Options); keep X-Frame-Options for older browsers that don't honor CSP framing rules.

To block all framing:

nginx

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

Apache

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

If your own pages legitimately frame each other, allow only same-origin framing instead:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;

To permit specific trusted partners, list their origins: frame-ancestors 'self' https://partner.example.com. Note that X-Frame-Options only supports DENY and SAMEORIGIN (the old ALLOW-FROM is obsolete), so for multi-origin allowlists rely on CSP frame-ancestors.

After deploying, re-run the iframe test above — the frame should now refuse to load.

FAQ

Is X-Frame-Options enough on its own? It stops basic framing in all major browsers, but CSP frame-ancestors is the current standard and the only way to allowlist multiple specific origins. Set both.

Does setting frame-ancestors break my CSP? No. frame-ancestors controls who may embed you; it's independent of script-src, style-src, and other directives. You can add it to an existing policy without affecting them.

Set the two headers, verify with a quick iframe test, and run a full scan to catch any pages that slipped through.