The Beginners Guide To Security Response Headers

Response headers are an important part of web security. There are lots of headers, some that apply just to requests, some just for responses and some that can appear in both. Some are only relevant to web pages, while others are also useful in API requests.

Here’s an example of a request:

Host: www.google.com
Connection: close
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
X-Client-Data: CIy2yQEIAQygEI+MfKAQj288oBCoMsBCITyywEY4ZrLAQ==
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: __Secure-3PSID=8gf7rLXsKhtq_8uhzjKKSbEnFIjTwlghI2OJ4Tk.; HSID=ArCOggeLGk3OYN; SSID=A31Evdaxdz3OXe; …{I truncated this, there were a lot of cookies}

The headers in this request are every line except the first one. The format of a header is to have the name of the header, followed by a colon (:), followed by the value, for example:

Cache-Control: max-age=0

The header is called “Cache-Control” and the value is “max-age=0”

The response to that request looks like this:

HTTP/1.1 200 OK
Date: Sun, 09 May 2021 20:00:59 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=UTF-8
Strict-Transport-Security: max-age=31536000
Server: gws
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Set-Cookie: 1P_JAR=2021-05-09-20; expires=Tue, 08-Jun-2021 20:00:59 GMT; path=/; domain=.google.com; Secure; SameSite=none…{I truncated the cookies again, there were lots}
Connection: close
Content-Length: 134180
<!doctype html><html…{truncated – there was an HTML page here}

You can see the same format for headers in the response and you can see some headers that are common between request and response, such as Cache-Control.

The headers in a request give the server additional instructions and headers in a response give instructions to the browser receiving them.

The headers we can see here are just a small number of those available. If we want to create a secure web service then it’s important that we understand response headers, in particular those relevant to security.

To add to the complexity, some headers fall out of use and stop being supported by browsers. Some browsers may continue support while others don’t. Implementations of what headers do might even vary slightly between browsers. Browser headers are complicated!

So Which Headers are Important for Web Pages?

The recommended headers do change over time, so it’s worth trying to keep an eye on them.

Currently some of the most important and useful headers for web page security are the following:

  • Strict-Transport-Security
  • Content-Security-Policy
  • X-Content-Type-Options
  • X-Frame-Options
  • Referrer-Policy
  • Permissions-Policy
  • Clear-Site-Data

What Do All of Those Headers Do?

Strict-Transport-Security

This tells the browser to always use secure transport, so HTTPS instead of HTTP. It protects against man-in-the-middle attacks, which largely rely on a lack of encrypted traffic to work.

When the browser first requests a page from a website it can use HTTP, however the server response would contain this header and the browser would perform all further communication over HTTPS.

Telling the browser to use HTTPS for the next year (31536000 seconds) and to apply it to any sub domains, it would look like:

Strict-Transport-Security: max-age=31536000 ; includeSubDomains

Content-Security-Policy (CSP)

This header allows a website to control the resources that are loaded on its pages. You should have complete control of the resources that your web pages load and don’t want an attacker to be able to load their own resources. Attacker controlled resources make it easier for an attacker to execute cross site scripting (XSS) attacks.

This header has a LOT of options and it’s worth a read of the specification to understand everything it can do.

When a page attempts something that the policy isn’t allowed, the browser will stop it and give a message in its console to tell you why it happened.

An example of a very restrictive content security policy is:

Content-Security-Policy: default-src 'self' https:; object-src 'none'; child-src 'self'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content

With:

default-src 'self' https:

we’re saying, by default, only load resources from the origin that this page loaded on (self) and only use HTTPS. The origin is made up of the protocol (e.g. https), port (not normally used in a URL, but is part of the origin if specified) and host (e.g. maps.google.com)

object-src 'none';

this limits the source of data for the HTML elements <object>, <embed> and <applet>. You almost certainly don’t use these, so there’s no harm in limiting what they can do.

child-src 'self';

child-src refers to framed elements (HTML <iframe> and <frame>) and web workers (imported scripts to run in the background of web pages). We’re saying that these should only come from the current origin.

frame-ancestors 'none';

Frame ancestors are pages which are loading the page that contains this header. So anything that loads this page using HTML <frame>, <iframe>, <object>, <embed> or <applet>. By saying ‘none’ we’re saying that this page shouldn’t be loaded by another page.

upgrade-insecure-requests;

If there are any URLs that use HTTP in your page then the browser should treat them as HTTPS instead.

block-all-mixed-content

By mixed content, we mean a mixture of HTTP and HTTPS, so blocking that means that if we load a page over HTTPS and within that page are resources (e.g. images) that try to load over HTTP then they will be blocked

CSP is very powerful, but can also break existing pages and add some complexity. You should be using it, but it might cause the occasional problem.

X-Content-Type-Options

This one prevents the browser from attempting to guess the MIME type (the expected format of a document). If the browser guesses the type then it may be prone to execution by the browser when this isn’t the intended result. An example would be an attacker uploading an HTML file with a .jpeg extension. The browser may sniff the type and instead of trying to display an image, it would execute the HTML, likely with an XSS payload.

This should be used as:

X-Content-Type-Options: nosniff

i.e. don’t guess (sniff) the content type of a document.

X-Frame-Options

This is used to tell the browser if it can display a page in <frame>, <iframe>, <embed> or <object> HTML elements. Allowing this can lead to clickjacking – i.e. an attacker loads your page behind their own. Clicks on their page translate to clicks on yours. If a victim is logged in to your site then that may cause them to perform authorised actions on your paged, believing that they are interacting with a completely different website.

Ideally you would set this to deny:

X-Frame-Options: DENY

If necessary then you can still allow it to work for the same origin, although that’s not ideal:

X-Frame-Options: SAMEORIGIN

Referrer-Policy

When a browser makes a request it can attach information relating to the page the request came from. This can potentially include the full URL of the originating page, which may contain sensitive or useful information for an attacker.

The recommended setting is:

Referrer-Policy: no-referrer

This includes no referrer information in requests. There are a variety of other options, including when it’s acceptable to send referrer information and how much of the referring URL to include.

Permissions-Policy

Previously known as Feature Policy, this header allows a website to control the features that a browser allows it to access. Those features are things like access to the microphone, camera, geolocation, speakers and vibration (for mobile). The less features are enabled, the less an attacker could potentially take advantage of.

In this example, the header would allow access to the microphone for the current origin (self) and gavinjl.me and deny access to geolocation:

Permissions-Policy: microphone=(self "https://gavinjl.me "), geolocation =()

Features can also be enabled in HTML, for example:

<iframe src="https://gavinjl.me " allow="vibrate">

It’s therefore possible to enable a feature in a header, then disable it in HTML, such as an iframe, where it would apply only to that iframe content.

Note that denying access at a parent level will also deny it for children (e.g. iframes), even if those children attempt to enable it.

Clear-Site-Data

This one is useful for actions such as logging out. It’s common to see websites fail to clear cookies and other potentially private data when logging out. This header simplifies that by getting the browser to clear selected browser data.

The recommended setting is:

Clear-Site-Data:"cache","cookies","storage"

When the browser receives this header it will, as you would expect, clear the cache, cookies and storage.

If you want to future-proof this then you can apply a wildcard:

A lot goes into web security and the headers you use are just a part of it. If you’ve implemented all of the headers above then that’s a really good start!


Got a comment or correction (I’m not perfect) for this post? Please leave a comment below.
You've successfully subscribed to Gavin Johnson-Lynn!