Improving security of your web application with Security Headers
Security Headers are a subset of HTTP response headers that, when sent by the server, allow the web application to tell the web browser to enable or configure certain security-related behaviours.
The article presents a list of the most important Security Headers, shows their impact on web application security and provides resources that can be useful in the implementation of best practices in your own application.
Are Security Headers important?
The most important question is: why should one care about setting the headers? There are several reasons to consider this:
- A measurable increase in application security
Security Headers provide an additional layer of defence (defense-in-depth approach) against various classes of web application risks. When implemented properly, these may decrease the impact of existing vulnerabilities present in the application logic, or even fully mitigate it. The solution is also backward compatible – some headers do not take effect in legacy browsers, but will not break your application. Worst case scenario is, that the headers will be simply ignored.
Moreover, implementing the practices shows the level of maturity as web developer and thinking about details which boosts your customer’s confidence in your skills.
- Make pentesters cry
Your application might get pentested. If your company or clients implement a secure development process, 3rd party pentesting is usually a part of it. Various laws and compliance regulations, such as GDPR, HIPAA PCI-DSS also recommend running them.
And be sure that pentesters will pick up the lack of the proper configuration of headers should it be missing.
- It costs only a penny to start
And last, but not least – all the above comes at a low cost of including a proper library and adding a few lines of configuration code.
How to test the configuration of Security Headers?
Scott Helme provides a great service: https://securityheaders.io, which offers a set of tests that can be run against a web application. The service grades the result and provides instructions on how to improve.
Remember, when your web application goes live, anyone can use it to test how well you have done!
Security Headers overview
Content-Security-Policy is a header, which allows controlling the origins that are used by a web browser to download assets. There are several directives related to various kinds of resources, the most basic being:
- default-src (covers types of assets that were not set explicitly using other directives)
- img-src (restricts sources of images)
- script-src (restricts sources of scripts)
- connect-src (restricts sources of data that can be obtained)
A server can instruct the browser to whitelist all origins that can be used for dynamic resources. Some of the values that can be used include:
- ‘none’ (prevents loading from any sources)
- ‘self’ (allows only same origin resources)
- domain (allows loading from specific domain, e.g. example.com)
- protocol, e.g. data:, blob: (allows loading resources from specific schemes)
- ‘unsafe-inline’ (allows including scripts and styles as in page source – as attributes, or script tags)
Examples of controlling resources using a CSP header
- A very strict (and probably not very practical) CSP that will not allow the browser to download any dynamic resources:
Content-Security-Policy: default-src ‘none’
- CSP policy which blocks the loading of external images (only images from the same origin are possible):
Content-Security-Policy: default-src ‘self’; image-src imgur.com
- Possibly insecure policy – provides no mitigation for script injections:
Content-Security-Policy: script-src ‘unsafe-inline’
- When ‘unsafe-inline’ is not present, Content-Security-Policy mitigates basic XSS vulnerabilities:
Content-Security-Policy: script-src ‘self’
The Content Security Policy (CSP) should not be used as the sole mechanism for XSS countermeasure. It is rather a defense-in-depth mechanism. And, if users of your web application are using IE11, you are out of luck – this browser simply ignores the directive.
Introducing CSP into an existing legacy application might be hard, due to compatibility issues. Fortunately, a Content-Security-Policy-Report-Only mechanism exists. It allows you to set up experimental policy and monitor (but not enforce) its effects.
The Content-Security-Policy is a header that is being constantly improved. Current versions of web browsers support Content Security Policy Level 2 (also referred to as CSP 2.0). Two headers exist, which were introduced in browsers when CSP standard was being developed: X-Content-Security-Policy and X-WebKit-CSP. Support for them is not well documented, buggy and might lead to problems. I do not recommend using them.
More to read:
- MDN web docs – Content Security Policy
- Content Security Policy Reference
- Using Content Security Policy to Prevent XSS
X-Frame-Options control the way the site can be framed. The following settings are possible:
- X-Frame-Options: DENY (forbids framing)
- X-Frame-Options: SAMEORIGIN (allows the site to be framed only in the same origin)
- X-Frame-Options: ALLOW-FROM https://example.com/ (allows the site to be framed only by websites hosted at example.com, not supported in many browsers)
For modern browsers, this header is replaced by frame-ancestor CSP directive, but it is still a good practice to use both techniques together for compatibility.
X-Frame-Options allow protecting the user from clickjacking attacks which rely on deceiving the user into interacting with content displayed in a hidden iframe embedded in a specially crafted webpage (this attack is also called „UI Redressing”).
An example of a simple clickjacking attack where an iframe stealing Facebook likes is redressed as “What type of pizza am I” quiz is shown below.
<form> <div> <h3>What kind of pizza are you?</h3> <ul> <li> <input id="opt1" type="radio" name="q1" value="1" /><label for="opt1">Marinara</label> </li> <li> <input id="opt2" type="radio" name="q1" value="1" /><label for="opt2">Margherita</label> </li> <li> <input id="opt3" type="radio" name="q1" value="1" /><label for="opt3">Hawaiian</label> </li> <li> <input id="opt4" type="radio" disabled="disabled" name="q1" value="1" /> <label for="opt4">I'm not sure</label> </li> </ul> <div> <input id="btnToggle" type="submit" value="Vote" /> </div> </div> <iframe id="likeFrame" src="like.htm" scrolling="no" class="likeFrame" style="overflow: hidden; cursor: pointer; width: 80px; height: 30px; position: absolute; opacity: 0; top: 180px; left: 40px;"> </iframe> </form>
More to read:
- MDN web docs – X-Frame Options
- Troy Hunt’s post about Clickjacking
- Browser support for X-Frame-Options
X-XSS-Protection allows to set the configuration for the XSS filters built into most browsers.
The correct value of the header is: X-XSS-Protection: 1; mode=block
XSS filters are implemented in IE (8+) and Edge. In Chrome and Safari, this feature is called XSS Auditor. While it does not block all the possible attacks (bypass exploits for all of filters are available), it is a defense-in-depth mechanism which can make the life of an attacker at least a little bit harder.
More to read:
HTTP Strict Transport Security is a feature, which strengthens your implementation of TLS by getting the User Agent to enforce the use of HTTPS.
Modern browsers provide an additional mechanism, called HSTS Preload List which allows you to submit your own domain to be hardcoded into the web browser as HTTPS only.
Introducing HSTS may bring risk for domains that are used to host both secure, HTTPS-enabled websites and legacy HTTP-only systems. Once a user visits the domain and HSTS is parsed by the browser, access to the server over unencrypted connection will no longer be possible.
More to read:
X-Content-Type-Options instructs a browser to stick with Content-Type declared by the server, disabling client-side content sniffing (guessing the type of retrieved asset based on file signature). The correct value for this header is: X-Content-Type-Options: nosniff
Content sniffing might, in specific cases, allow the attacker to change non-executable MIME types into executable MIME types, leading to XSS vulnerabilities.
More to read:
Referrer-Policy is a header which allows a website to control the value of referrer header sent by the web browser in a detailed way. The following values are possible:
Each value controls what should be sent as the referrer value based on protocols (e.g. HTTP vs HTTPS) and origins (same origin vs cross-origin request) used to serve the content.
This level of configuration detail allows to limit the information sent to third parties to domain only, while keeping a full URL available for resources requested from origin site (e.g. for analytics) when origin-when-crossorigin value is used.
Unfortunately, support for this header is limited only to the most recent browsers. But hey, at least the word referrer is not misspelled this time!
Server and technology-related headers
A set of headers exists, which can reveal technologies used by a web application. Examples of such headers include:
- Server – webserver banner, e.g. „Microsoft-IIS/8.0” or „nginx 1.7.2”.
- X-Powered-by – application frameworks run by the site, e.g. “ASP.NET”, “PHP/5.2.3”, “Express”
- X-AspNet-Version – shows version of ASP.NET engine
Usually, it is not considered a critical risk, as it is possible to identify most of web building technologies even without the headers using various fingerprinting techniques. Those can be based on error handling, HTTP parameter processing or other frameworks specific behaviour. However, exposing the detailed version of server software is especially dangerous for older vulnerable versions. The attacker can look for exploitable servers using publicly available search engines (such as shodan.io).
More to read:
Cache Control headers
Cache-related headers, when configured correctly might considerably speed up the application. However, there are security related aspects that must also be considered when setting them up. Basic recommendations for cache settings:
Cache-Control: must-revalidate, no-cache, no-store, pre-check=0, post-check=0, max-age=0, s-maxage=0 Expires: 0 Pragma: no-cache
will prevent the following problems:
- Going back to application data after logging out of the application and being able to access the sensitive data by an unauthenticated user
- Retrieving data from disk storage, where it might be stored by the web browser in an unencrypted form
- Caching data in intermediate proxies, making sure that a previously cached response will not be re-used or stored
More to read:
How to implement Security Headers in your application?
Several frameworks exist that will help you to easily implement the headers in your technology of choice:
- ASP.NET (including ASP.NET core) – NWEBSEC
- Nodes.js (Express) – Helmet
- Crystal: crystal-helmet
- Flask (Python framework): Talisman
- Go: secure
- Koa (Node framework): koa-helmet
- Ring (Clojure framework): ring-secure-headers
- PHP: Secure headers
Who should be responsible for the implementation and configuration of headers? This can be done by developers, but input of a Security Engineer can be helpful in setting all the details correctly.