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.
Key takeaways
X-Frame-Options
(XFO) is an obsolete HTTP security header originally intended to protect against clickjacking attacks.X-Frame-Options
header put users at risk by allowing attackers to embed a site or web application within their malicious site.X-Frame-Options
header always had several limitations and is no longer the recommended way to control frame embedding permissions.frame-ancestors
directive in your Content Security Policy (CSP) header to replace X-Frame-Options
.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:
X-Frame-Options: DENY
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 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
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:
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.
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:
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.
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.
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.
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.
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.