Missing X-Frame-Options header? You should be using CSP anyway

When clickjacking attacks using iframes first became possible, browser vendors reacted by adding X-Frame-Options as a dedicated security header for controlling page embedding permissions. Learn how setting the right Content Security Policy makes up for a missing X-Frame-Options header today.

Missing X-Frame-Options header? You should be using CSP anyway

Key takeaways

 

  • X-Frame-Options (XFO) is an obsolete HTTP security header originally intended to protect against clickjacking attacks.
  • In the past, a missing X-Frame-Options header put users at risk by allowing attackers to embed a site or web application within their malicious site.
  • The X-Frame-Options header always had several limitations and is no longer the recommended way to control frame embedding permissions.
  • Use the frame-ancestors directive in your Content Security Policy (CSP) header to replace X-Frame-Options.

Why was the X-Frame-Options header introduced?

The X-Frame-Options header was introduced by Microsoft with Internet Explorer 8 specifically as a means of preventing clickjacking attacks. Support for the header was quickly added by other web browsers since, at the time, X-Frame-Options was the only easy way to tell the browser whether a page should be allowed to render in an iframe.

Being more of a quick fix than a comprehensive solution, X-Frame-Options provided only two universally supported parameters:

  • To prevent the current page from being embedded in any iframe, you would set X-Frame-Options: DENY
  • To allow embedding but only for requests originating from the same domain, you would set X-Frame-Options: SAMEORIGIN

A third parameter, ALLOW-FROM URI, would in theory let you allow embedding from a specific named origin, but in practice this had inconsistent browser support and could cause the entire header to be ignored, negating any protection. Unlike some other headers, X-Frame-Options had to be set in the web server config file, so putting it in an HTML meta tag like <meta http-equiv="X-Frame-Options" content="deny"> would have no effect.

Clickjacking attacks 101

Clickjacking is a UI redressing attack where malicious actors use tricks like iframe embedding, scripting, CSS styling, and transparency manipulation to fool the user into performing unintended actions on a page. Victims believe they are clicking on a visible element when, in reality, they are interacting with a hidden element from a different page loaded into an iframe. This technique can be used to hijack login credentials, bypass authentication, authorize unwanted transactions, or trick users into downloading malware.

Learn more about clickjacking attacks

Why was X-Frame-Options deprecated if it was so useful?

While effective for basic use cases, X-Frame-Options was more of a blunt instrument than a serious security tool. As site structures and configurations got vastly more complex, it became clear that the header was not a practical solution. X-Frame-Options limitations included:

  • Lack of granular control: Your only options were to block all embedding or allow embedding within the same origin.
  • Per-page settings only: You had to set the header separately for every web page, with no way to specify more general behavior at site or domain level.
  • No reporting or testing mode: There was no way to test a setting without immediately enforcing it immediately, leading to potential usability and maintenance issues.
  • Inconsistent browser support: The ALLOW-FROM directive that would give at least a little more flexibility was never universally supported by all major browsers and was quickly deprecated.

Even though modern browsers still support the two basic X-Frame-Options directives, the current best practice for clickjacking protection is to use the frame-ancestors directive in your CSP header instead.

How to use CSP to replace X-Frame-Options

Including the frame-ancestors directive in your Content Security Policy header gives you all the capabilities of X-Frame-Options while eliminating its disadvantages and greatly increasing flexibility:

  • Fine-grained control: The ability to list any number of URLs that are allowed to embed your page (including wildcards) gives you full control while also easing maintenance. 
  • Universal and standardized support: CSP is a recognized and recommended standard for controlling content sources and behaviors.
  • Easier security policy management: Making the frame embedding policy a part of your broader content policy makes it far easier to manage multiple sites and domains. 
  • Report-only header for testing: The additional Content-Security-Policy-Report-Only header lets you test new or changed CSP directives without applying them to the page or disabling existing directives.

Moreover, most modern sites and web apps apply some kind of CSP anyway, primarily to protect against cross-site scripting (XSS), so including frame embedding policies there makes more sense than using a separate header.

Examples of using frame-ancestors to replace X-Frame-Options

To use frame-ancestors as a drop-in replacement for blocking with X-Frame-Options: DENY, set the following header (note that a real CSP header will also include many other directives and can get very long, so these examples focus only on frame-ancestors):

Content-Security-Policy: frame-ancestors 'none';

To directly replace X-Frame-Options: SAMEORIGIN, use:

Content-Security-Policy: frame-ancestors 'self';

More typical usage is to specify multiple trusted sources alongside the current origin, including subdomains if needed:

Content-Security-Policy: frame-ancestors 'self' example.com *.example.com;

This approach offers more flexible control, universal browser support, easier maintenance, and a more comprehensive approach to security compared to X-Frame-Options.

The frame-ancestors directive in your CSP should not be confused with frame-src. While frame-ancestors controls where the current page may be embedded, frame-src tells the browser what content sources are permitted for frames used on the page. The two directives can be combined.

Why am I still seeing “Missing X-Frame-Options header”?

If you’re seeing warnings about a missing XFO header, it’s likely they are coming from an older security tool or some legacy configuration. Before CSP became the norm, many security scanners (including Invicti products) flagged a missing X-Frame-Options header as a low-severity vulnerability or informational-level warning because it could mean the site wasn’t protecting its users from clickjacking attempts.

With the evolution of browser security and the widespread adoption of CSP, setting XFO headers is no longer a best practice. This is why modern application security tools have moved away from recommending X-Frame-Options and flagging its omission, even though any existing XFO headers will continue to work (at least for DENY and SAMEORIGIN directives). Instead, up-to-date vulnerability scanners should advise you to use the CSP frame-ancestors directive, which provides more functionality and is more flexible.

Missing X-Frame-Options header example

To illustrate, here is how older versions of Invicti DAST tools used to warn about a missing XFO header:

Invicti detected a missing X-Frame-Options header, which means that this website could be at risk of a clickjacking attack. The X-Frame-Options HTTP header field indicates a policy that specifies whether the browser should render the transmitted resource within a frame or an iframe. Servers can declare this policy in the header of their HTTP responses to prevent clickjacking attacks, ensuring that their content is not embedded into other pages or frames.

If your security scanner still reports XFO as a recommended header, it could mean that you need to update it or look for a tool that keeps up with modern best practices.

Final thoughts: Keeping up with improving defensive technologies

In the younger and less standardized years of web security, adding a custom security header was often the quickest way to protect users against a new type of attack. With more official recommendations and standards moving at a glacial pace, it was mostly up to major browser vendors to coordinate security header specifications and implementations, often leading to inconsistent browser support and maintenance headaches for website owners.

Today, web technologies are far more mature and standardized, as is web development overall, making it possible to move away from point solutions like X-Frame-Options and towards more holistic protection with CSP. Instead of using a dedicated header just to prevent clickjacking, you can make clickjacking protection one part of a carefully designed content security policy. Staying up to date with best practices and scanning regularly using proven AppSec tools will help keep your websites, applications, and APIs secure from common attacks across your entire attack surface.

Frequently asked questions about missing X-Frame-Options headers

What is “X-Frame-Options Header Not Set”?

This warning means a security tool has detected that your website or application is not setting the X-Frame-Options HTTP header to prevent clickjacking. However, sending this header is no longer considered a best practice, and you should instead use the frame-ancestors directive in CSP.

What is the difference between missing X-Content-Type-Options and X-Frame-Options headers?

X-Frame-Options was used to prevent clickjacking by controlling iframe embedding and is obsolete, whereas X-Content-Type-Options prevents MIME type sniffing attacks by enforcing declared content types and setting it to nosniff is still recommended.

How do I enable X-Frame-Options?

Although the X-Frame-Options header is still supported by browsers and you can set it to DENY to block all embedding or SAMEORIGIN to allow embedding within the same origin, the recommended practice is now to use the frame-ancestors directive in CSP for broader support and more precise control.

How do you check the X-Frame-Options header in Chrome?

You can directly check response headers using dev tools in your browser. Open dev tools (usually F12), go to the Network tab, reload the page, select the loaded page in dev tools, and inspect the Headers tab to see HTTP response headers such as X-Frame-Options or Content-Security-Policy.

Zbigniew Banach

About the Author

Zbigniew Banach - Technical Content Lead & Managing Editor

Cybersecurity writer and blog managing editor at Invicti Security. Drawing on years of experience with security, software development, content creation, journalism, and technical translation, he does his best to bring web application security and cybersecurity in general to a wider audience.