Why is the page still vulnerable to clickjacking?

clickjacking-protection-gaps page

The issue

A page with no effective framing restriction can be loaded by any site inside an invisible frame, so a visitor clicking a decoy is really clicking your page, signed in. It happens two ways: no framing header at all, or an X-Frame-Options header that looks right but is ignored because the Content Security Policy also sets frame-ancestors with a looser value.

The cause of the second is that browsers which support frame-ancestors follow it and disregard X-Frame-Options. When the two headers are set by different systems, they drift apart.

Scope

Framing is decided per response. Different layers often set the two headers, the application X-Frame-Options and a CDN or proxy the policy, so some pages can be covered while others aren’t.

How to check you have it

  • In probeo: a policy with no frame-ancestors is reported as csp:missing-frame-ancestors, graded minor, whether or not X-Frame-Options is also sent. A response with no policy is reported as csp-missing.
  • By hand: run curl -sI https://your-site.example/ and read x-frame-options and the frame-ancestors part of content-security-policy together. Where both exist, frame-ancestors decides.
  • In the browser: load the page in an <iframe> on another origin. A protected page doesn’t render, and the console names the header that blocked it.

The fix

Set frame-ancestors in the Content Security Policy to the framing you intend, and make X-Frame-Options agree with it.

For most pages that is frame-ancestors 'none' with X-Frame-Options: DENY. If the site frames its own pages, use 'self' with SAMEORIGIN. If one system owns the policy and another owns X-Frame-Options, decide which sets framing and have the other follow.

Done when: every response sends frame-ancestors with the intended value, any X-Frame-Options matches it, and the check above comes back clean.

Do not

  • Do not rely on X-Frame-Options alone where the policy sets frame-ancestors.
  • Do not take an X-Frame-Options: DENY header as proof without reading frame-ancestors.
  • Do not use * in frame-ancestors when the intent is to block framing.
  • Do not let two systems set the two headers without checking they agree.

FAQ

If X-Frame-Options is set to DENY and CSP has frame-ancestors ‘self’, which one wins?

frame-ancestors. Browsers that support it ignore X-Frame-Options, so same-origin pages can frame this one despite DENY.

Does this mean X-Frame-Options is useless if CSP is deployed?

In current browsers, when frame-ancestors is set, yes. It still helps old clients that don’t support the policy, so keep it with a matching value.

How does a page end up with no framing protection at all?

By default. Servers, frameworks and CDNs don’t usually add either header unless configured to, so a page has none until someone adds one.

Can frame-ancestors be missing from CSP while XFO still provides protection?

Yes. If the policy doesn’t set frame-ancestors, browsers follow X-Frame-Options. The override only happens when frame-ancestors is present. Probeo still reports the missing directive, because the policy is where framing belongs.

frame-ancestors 'none' in the policy and X-Frame-Options: DENY alongside, or 'self' with SAMEORIGIN if the site frames itself. The two should never disagree.

Why do security scanners sometimes miss the XFO-override problem?

A check that reads X-Frame-Options on its own sees DENY and passes. Seeing the override means reading both headers together, the way the browser does.

Which product detects this

Probeo checks this on every page of your site as part of Site Clarity. This page explains one instance; the product tells you every page that has it.