Securing your HTTP headers

Last week, I wrote an article here on my blog about securing your site's HTTP headers.

In the case of my blog, setting the headers took only about 30-40 minutes total to gain complete security coverage. However, not all websites are my blog. What I've found is that the vast majority of security headers are very easy to set up - some only require seconds, whereas others require more attention to detail and have potential to break a website or app if not implemented properly. At the end of the day, default settings cause a ton of attacks to be able to take place on victims all around the world. Just as it's a horrible idea to leave your server credentials as root/password123, it is equally as bad to assume that the server framework is properly configuring your security policies... As they are not.

In this blog, I will give each one an "effort rating." I've found that there are many pages online which describe the HTTP headers affecting security if you search the web for "security related HTTP headers." However, difficulty of implementation and gotchas are not well covered. I intend to cover that here. I am going to go down the list from least effort to most effort:

1. Securing your session cookies

Set-Cookie should supply HttpOnly and Secure on all session cookies which could be used to impersonate a user or reveal sensitive data.

HttpOnly - Specifies that the browser can only allow server-side code to access the cookie.

Secure - Specifies that the cookie may only be transmitted via HTTPS/SSL/TLS.

It's surprising to me how many session cookies I find which do not have these two flags set. Many sites use entirely HTTPS, yet they allow cookies to be sent over HTTP. This could occur if a 3rd party service tries to send the cookie to their server and they, unlike your site, do not implement HTTPS for the endpoint.

Here is an example of how to do this in Express.js:

res.cookie('session_id', '7', { httpOnly: true });
res.cookie('session_id', '7', { httpOnly: true });

For PHP and ASP.NET, refer to the OWASP page .

Gotcha: This will break your site if it does not implement HTTPS or if your application relies on client-side access to the cookie, which most auth systems do not.

2. X-Frame-Options

Determine whether or not your page needs to be loaded in an iFrame of an external website (most do not) and if it does not, set: X-Frame-Options: SAMEORIGIN. This will allow the page to load in an iFrame ONLY on pages coming from your domain. This prevents your users from falling victim to clickjacking attacks. This is an older header which is being replaced with Content-Security-Policy, specifically the frame-ancestors directive.

Gotcha: If your page needs to be loaded into a 3rd party site, you need to specify it with X-Frame-Options: allow-from otherwise it will break.

3. X-XSS-Protection header

This header is a "best practice" header that certainly won't hurt, but only certain browser versions comply with it. The newer Content Security Policy is preferred, which we will cover later on. Setting this header is extremely simple and to do it, view this short page . It is usually enabled by default in browsers which support it, but there's a chance the user turned it off, in which case this will turn it back on for pages.

Gotchas: None. Several browsers (Chrome, IE) implement this already anyway by default.

4. X-Content-Type-Options

This header prevents browsers from MIME-sniffing content. MIME-sniffing just means that when a request is made to the server and the server doesn't specify a Content-Type, the browser scans the data payload to determine what it thinks the Content-Type should be. Meaning, if the browser makes a request to and some_image.jpg is actually full of HTML and JavaScript, the browser will render the HTML and run the JavaScript. If MIME-sniffing is disabled, the browser will not. This is also true if homepage.html contained a JPG instead of HTML, CSS, or JavaScript. MIME-sniffing can be dangerous because if someone is able to upload a javascript file as a JPG, the Contest-Type is not specified, and a user browsers to the JPG, code can be executed on the user's browser.

To stop this from happening, we set X-Content-Type-Options to "nosniff"

Gotcha: This will break parts of your site if your server is not sending responses with explicit Content-Type set. So to implement this protection, you need to ensure that your responses are sending a proper Content-Type.

5. HTTP Strict Transport Security (HSTS): strict-transport-security

This header simply will not allow the browser to interact with your website via http. So if there is an http request made without TLS or SSL, it will force the browser to use the https version instead. Obviously, if the endpoint is not available via HTTPS, then this setting will break your site until HTTPS is available across all endpoints. Setting this one requires a max-age, which ideally should be large such as 1 month, 6 months, 1 year, 2 years, etc... The max-age is what tells the browser how long the site should only be accessed via HTTPS. Adding the parameter includeSubdomains enforces this for all * subdomains as well. This is separated out in cases where a specific subdomain should not be HTTPS for some reason. Finally, there's one more optional parameter called preload which will add your site to the HTTPS Preload List maintained by Google which is cross-referenced by browser vendors when deciding whether they should connect via HTTP or not. If it's on the preload list, it will speed up the check because the preload list is hardcoded into browsers. There are requirements for a site to be on a preload list:

In order to be accepted to the HSTS preload list through this form, your site must satisfy the following set of requirements:

Serve a valid certificate. Redirect from HTTP to HTTPS on the same host, if you are listening on port 80. Serve all subdomains over HTTPS. In particular, you must support HTTPS for the www subdomain if a DNS record for that subdomain exists. Serve an HSTS header on the base domain for HTTPS requests: The max-age must be at least 31536000 seconds (1 year). The includeSubDomains directive must be specified. The preload directive must be specified. If you are serving an additional redirect from your HTTPS site, that redirect must still have the HSTS header (rather than the page it redirects to).

Gotcha: You should not add your site to the preload list unless you are 100% certain that all of your subdomains currently and will in the future implement HTTPS-only. This is because removing the site from the preload list takes time and can break functionality until it is removed should you have failed to realize that a subdomain needs to use plain HTTP. You should scan your site using Qualys' SSL Labs prior to adding to the preload list to make sure there are no issues. For example, view my site's report here .

6. Feature-Policy header

The Feature-Policy header tells the browser which features your page/app needs access to. For example, does your page need to use the device's camera? What about the microphone? How about gyroscope or GPS? You can (and should) specify all of these things in the Feature-Policy. This should be a no-brainer for anyone on the team who knows the architecture well. For example, my blog has no reason to use most of these technologies so my Feature-Policy looks like this:

accelerometer 'none'; ambient-light-sensor 'none'; autoplay 'none'; camera 'none'; cookie 'self'; docwrite 'self'; domain 'self'; encrypted-media 'self'; fullscreen 'self'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none'; speaker 'none'; usb 'none'; vibrate 'none'

This should give you a good idea of how to configure your own Feature-Policy.

Gotcha: This will obviously break functionality if you are wrong and forget that a particular page or app needs to access any of the features that you disabled. A simple fix is to adjust the Feature-Policy. This provides a nice whitelist approach so that should you fall victim to a cross-site script attack, the scripts are limited in what they can do on the user's browser.

7. Content-Security-Policy

This one is the most powerful, but also the trickiest to get right and requires the most work. When properly implemented, CSP makes XSS attacks very difficult or impossible to perform on your users' browsers. This is also the "modern" way to handle security on the client-side with a ton of support. In fact, X-Frame-Options and X-XSS-Protection are becoming deprecated in favor of CSP and CSP has much wider support from browser vendors.

Know your site

Before we get into the meat of CSP, the number one most important prerequisite is to simply know your site. With all of the frameworks, libraries, and APIs used in modern web applications and websites, it's easy to lose track of what exactly is going on across the interface and which assets use HTTPS vs HTTP, which assets need to run JavaScript, and etc...

The Set-up

When it comes to setting up a very secure CSP, you must do some homework and learn exactly which pages of your site need access to various resources and by which means they need to access them (HTTP or HTTPS). The settings which can be applied to CSP are called "directives." They look like: default-src, script-src, frame-ancestors, etc... Each of these is called a directive.

For example, a blog site which needs to run no JavaScript can simply disable JavaScript completely with: Content-Security-Policy: script-src 'none' which tells the browser "do not load any scripts from any source ever."

Obviously, many sites need to use scripts so a less-strict policy would be Content-Security-Policy: script-src 'self'which tells the browser to only load and run scripts from the same origin aka your site's domain.

To open this up even more, you may specify a whitelist of allowed domains from which the browser should load and execute scripts like so:

Content-Security-Policy: script-src 'self'

This tells the browser to run all scripts from your own domain as well as those Facebook and Google services.

Another notable directive is frame-ancestors which is the replacement for X-Frame-Options. Setting Content-Security-Policy: frame-ancestors 'self' will disallow your page from being iFramed by another site, preventing clickjacking attacks in the same manner that X-Frame-Options would.

Another important directive to set is default-src which applies to all types of resources by default, especially those who have not been explicitly set with other directives. Meaning, if you don't want the user's browser to load any content from another domain, you can set Content-Security-Policy: default-src 'self'. Likewise, if you want to only load content over HTTPS, you can set Content-Security-Policy: default-src https:

As you can see, CSP provides all of the tools you need to create a very explicit policy or a somewhat loose policy.

Note that you can stack policies and they will be enforced by level of explicitness like so: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; form-action 'self'; frame-ancestors 'self'

In this example, we specify that by default, no content is loaded from any source except for scripts which are allowed to be run from the same origin, requests cannot be made to other domains from inline JavaScript, images may only be loaded from your domain, CSS may only be loaded from your domain, forms can only be submitted to URLs in your domain, and your page cannot be iFramed by pages from other domains.

This is a modified example from which is a fantastic reference in setting all of this up.

As you can see, CSP is an extremely powerful way to be explicit and lock down your site from all kinds of attacks like CSS, CSRF, clickjacking, Man-In-The-Middle attacks, and more. Sadly, only a small percentage of websites actually implement this state-of-the-art browser security technology. Jump on board to protect your users!

Gotchas: Again, you need to know your web service's architecture well. Disabling content from 3rd party sources can break pages if the pages need that content to function, so it will require some testing for complex web services.

Configuration Location

Note that many of these headers can be applied at either the web server (e.g. nginx, apache) level or the application level. Meaning, the headers can be set by the server configuration file to automatically be included on every page the server sends back OR they can be set within the web server framework (e.g. Express.js, .NET, PHP, Spring, etc...). It's a good idea to think through these policies and work with the server administrators to decide on which policy should be applied where.

Comments (1)

Bayu Angora's photo

Then check your score here ->