Want to learn more about OAuth 2.0 and OpenID Connect?
Save yourself days of digging through dozens of specs with this online course
More informationCurrent best practices to restrict framing in the browser
Frame-based attacks such as clickjacking and UI redressing may be obscure, but they are (still) very real. They even threaten APIs, which have nothing to do with iframes and web pages. This article gives an overview of the threats, discusses recent changes in framing restriction mechanisms, and provides concrete recommendations to secure modern web applications.
15 September 2020 Security Policies Browser Security, X-Frame-Options (XFO), Content Security Policy (CSP)
Best practices for framing policies
This section outlines current best practices to configure framing policies for modern frontend applications and APIs in contemporary browsers. If you want more context or need an overview of more detailed configuration options, you will want to read the full article.
Recommendation for frontend applications
Modern frontend applications typically do not rely on being framed as part of other applications. Therefore, the recommended best practice is to disable framing by sending the following headers in the response.
Current best practice for frontend applications
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
If necessary, you can enable framing within your application’s origin by setting the headers as follows:
Alternative recommendation for frontend applications
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
Recommendation for APIs
While APIs are typically not rendered in frames, allowing the rendering of responses in frames could open the API up to quirky attacks. Therefore, the recommended best practice is always to send headers to disable framing.
Current best practice for APIs
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
Clickjacking and UI redressing attacks
You’re still reading after you got the TL;DR version. Love it! So why does it matter that an application tells the browser not to load it in a frame?
By itself, it doesn’t really matter. The browser’s https://web.dev/same-origin-policy/” text=”Same Origin Policy” %} will prevent direct interaction between the frame and its parent if they are cross-origin. In essence, this means that a page running on https://maliciousfood.com
cannot load https://restograde.com
in an iframe and start inspecting its contents. Browsers have always prevented such behavior and righteously so!
Regardless of the Same Origin Policy, browsers always allow a malicious parent to frame a victim application. Additionally, the hostile parent can decide on the size, position, and styling of the frame. With these properties, the malicious parent can execute UI redressing attacks, such as clickjacking. In such an attack, the malicious page confuses the user with an ambiguous UI experience (e.g., a minuscule or invisible frame), tricking the user into interaction with the victim application in the frame.
Traditional UI redressing attacks result in “stolen clicks”, but more modern incarnations trick the user into dragging contents or copy/pasting potentially sensitive text. The enabler for each of these attacks is loading the victim application in a frame. If the victim can prevent that from happening, virtually all of these attacks go away.
Did you know that the name “UI Redressing” is confusing in English? Apparently “redressing” means “to rectify a situation”, which is definitely not applicable here. The naming refers to “re-dress”, as in “to dress the UI differently”. That’s also how I always interpreted it, until Eric Lawrence pointed this out on Twitter
You might be wondering if not loading the application in a frame is the best defense. Can’t the browser just detect the malicious interaction and warn the user? In theory, yes, but in practice, that turns out to be more difficult than you’d assume. This work attempted to provide a heuristics-based security mechanism to determine if UI interactions are safe or not. Unfortunately, the problem was much harder to solve than initially assumed, stalling the proposal indefinitely.
Now that we know why it matters that we restrict framing, let’s take a look at how to do that with the X-Frame-Options
header and with the Content-Security-Policy
header.
Framing restrictions with X-Frame-Options
The X-Frame-Options
is the oldest security mechanism to restrict framing. Supporting browsers look for the X-Frame-Options
header on responses that will be rendered in a frame. Based on the header’s value, the browser will either allow the response to be loaded in a frame or block the loading and return an error instead.
The X-Frame-Options
header supports three configuration scenarios:
- Not sending an
X-Frame-Options
header enables the default browser behavior, which allows all framing - Sending an
X-Frame-Options
header with a valueDENY
prevents all framing, regardless of the origin of the parent frame - Sending an
X-Frame-Options
header with a valueSAMEORIGIN
allows framing when the parent has the same origin as the resource loaded in the frame, but denies all framing where the parent has a different origin
In the past, the header used to support a fourth configuration, with a value of ALLOW-FROM x
, where x
represents an origin, such as https://restograde.com
. This configuration allowed framing by that particular origin, but not by any other origin. This paragraph is about the past because mainstream browsers dropped support for that option. Chrome and Webkit-based browsers never supported this feature to begin with, and Firefox recently changed its position as well. In essence, this feature is no longer useful.
Framing restrictions with Content Security Policy
More recent is Content Security Policy (CSP), an elaborate browser security policy. Like the X-Frame-Options
header, a server can send a Content-Security-Policy
header back to the browser. With that header, the server can tell the browser to enforce various security restrictions, including framing behavior. In this section, we will discuss the framing restriction feature of CSP in more detail. More information about CSP in general is available on MDN.
By setting the frame-ancestors
directive in a CSP header, the server tells the browser who is allowed to frame the page in the response. Just like the X-Frame-Options
header, the frame-ancestors
directive supports four configuration scenarios:
- Not sending a
frame-ancestors
directive in the CSP header enables the default browser behavior, which allows all framing - Sending a
frame-ancestors 'none'
directive in the CSP header prevents all framing, regardless of the origin of the parent frame - Sending a
frame-ancestors 'self'
directive in the CSP header allows framing when the parent has the same origin as the resource loaded in the frame, but denies all framing where the parent has a different origin - Sending a
frame-ancestors https://restograde.com https://virtualfoodie.com
directive in the CSP header allows framing by a parent from eitherhttps://restograde.com
orhttps://virtualfoodie.com
Note that CSP’s restrictions apply not only to the direct parent of a frame, but to all ancestors of that frame.
Restricting framing in practice
Having two mechanisms to restrict framing makes it difficult to figure out which configuration makes sense. That’s why we discuss four different scenarios below, all with sample header configurations.
Deny framing by anyone
The configuration below tells the browser never to load the response in a frame.
Current best practice for APIs
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
This is the recommended best practice for modern frontend applications, which typically do not rely on being integrated through iframes. For applications that depend on frame-based integration, one of the other configurations will be more applicable.
Even though API responses are not intended to be rendered in frames, nothing prevents that from happening. To ensure API responses are not vulnerable to obscure attacks, it is recommended to set both framing headers on each API response as well.
Allow framing within the application’s origin
The configuration below tells the browser that a response can only be loaded in a frame if the parent is from the same origin.
Current best practice for APIs
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
Note that this configuration is acceptable for frontend applications that rely on framing within their origin.
Allow selective framing
The configuration below tells the browser to render the page in a frame if the parent is listed in the CSP header. In this example, framing is allowed by origins https://restograde.com
and https://virtualfoodie.com
.
Current best practice for APIs
Content-Security-Policy: frame-ancestors https://restograde.com https://virtualfoodie.com
This configuration should only be used in use cases that specifically depend on framing.
Note that the X-Frame-Options
header no longer supports selective framing, except in Internet Explorer 10. The configuration given below allows all framing in Internet Explorer 10, which is not necessarily problematic. If IE10 users are a representative subset of your userbase, and you only need to allow a single origin, you can complement the Content-Security-Policy
header with an X-Frame-Options
header using the ALLOW-FROM
option.
Always allow framing
To allow framing by anyone, it suffices to avoid setting either of the framing policies. Omitting the policies results in the browser allowing framing by default. This setting is not recommended, except for public applications that rely on being rendered in a frame.
Summary
To conclude, let’s summarize a long story in three concrete bullet points:
- Both HTML-based web applications and APIs should set headers to restrict framing
- The recommended best practice is to deny framing by setting both the
X-Frame-Options
header and theContent-Security-Policy
header - Unless you are dealing with IE10 users, forget about
X-Frame-Options
to configure selective framing
About Dr. Philippe De Ryck
Hi, I'm Philippe, and I help developers protect companies through better web security. Learn more about my security training program, advisory services, or check out my recorded conference talks.
Want to learn more about OAuth 2.0 and OpenID Connect?
Save yourself days of digging through dozens of specs with this online course
More informationDr. Philippe De Ryck
Hi, I'm Philippe, and I help developers protect companies through better web security. As the founder of Pragmatic Web Security, I travel the world to teach practitioners the ins and outs of building secure software.
Talks and workshops
You will often find me speaking and teaching at public and private events around the world. My talks always encourage developers to step up and get security right.
Articles
Security is often about small nuances. In my articles, I dive deeper into various security topics, providing concrete guidelines and advice. My articles also answer questions I often get while speaking or teaching.
Security resources
Getting security right is all about knowledge. I strongly believe in sharing that knowledge to move forward as a community. Among my resources, you can find developer cheat sheets, recorded talks, and extensive slide decks.
Mailing list
Subscribe to the Pragmatic Web Security mailing list to stay up to date on the latest activities and resources.