Security headers are an important component of the security of any modern web application. Although this measure is relatively easy to implement, the security headers of many applications are incomplete or incorrectly configured.
Table of contents
Security headers, or more precisely security response headers, consist of meta-information that a web server sends to a client in its responses. The server thus provides the browser with important information on how to handle the content of the response. The selection of security headers that a web server can insert in its responses has seen a substantial increase. The presence of outdated headers, and in rare instances, headers that may pose security risks, adds a layer of complexity to the situation. In this blog post, we will elucidate this topic and provide up-to-date recommendations on suitable security headers and their optimal use.
Security headers are security measures that are primarily active in the visitor’s browser. They protect the visitor from attacks such as cross-site scripting and clickjacking. This class of attacks is also known as “client-side attacks”.
The following headers will be covered in this blog post:
- Content-Type and X-Content-Type-Options
- X-Frame-Options
- Strict-Transport-Security (HSTS)
- Referrer-Policy
- X-XSS-Protection
- Content-Security-Policy
Content-Type and X-Content-Type-Options
The Content-Type Header tells the user’s browser which media type (MIME-Type) is contained in the response. This helps the browser to process the received data correctly. Typically, the majority of web servers and frameworks automatically configure this header correctly. Nevertheless, errors may arise in self-built web applications. A normal HTLM website is delivered with the following header:
Content-Type: text/html; charset=UTF-8
The browser will attempt to render the received data as a website and will also execute any script code. However, if the Content-Type is set as “text/plain; charset=UTF-8”, the browser will display the same response as plain text, i.e., as HTML code. It is therefore important that the Content-Type header is set according to the purpose of the response. If it is not, data will be misinterpreted, which can enable attackers to execute arbitrary code in the context of a website.
In cases where the Content-Type header is incorrect or not set at all, modern browsers attempt to guess the correct MIME type based on the received response (MIME Sniffing). Not only is this process prone to errors, but there is also a risk that the used algorithm itself contains security vulnerabilities. This behavior can be controlled via the X-Content-Type-Options Header. It is good practice to ensure that the content of the Content-Type header is always correct and to disable MIME sniffing with the following header:
X-Content-Type-Options: nosniff
Referrer-Policy
The Referrer-Policy controls how much information a browser should write in the Referer header when it sends requests. The attentive reader will have noticed a spelling mistake at this point. This error originates from the original implementation and was also included as such in the standardization (RFC-1945). The Referer header contains the URL of the previous request, which only plays a minor role as long as the browser always sends requests to the same page. However, if this page redirects the browser to a third party, the URL in the Referer header may contain sensitive information that should not be shared. Moreover, this action compromises user privacy by revealing the source page to the destination of the redirect. To avoid these scenarios, it is recommended to set the Referrer-Policy as follows:
Referrer-Policy: no-referrer
X-XSS-Protection
The X-XSS-Protection header controlled the built-in cross-site scripting filter in Internet Explorer. Older versions of Chrome, Edge and Safari briefly supported it (versions from before 2020). However, all modern browsers ignore this header. To harden your site against cross-site scripting, it is recommended to use the Content-Security-Policy header.
X-Frame-Options
Websites can embed and display the content of other websites. The HTML tags “<frame>” and “<iframe>” are used for this purpose. Attackers can embed the content of a harmless page into their own malicious website and trick a user into interacting with elements of the supposedly harmless page. What the user does not know, however, is that an attacker has placed an additional, invisible layer of control elements over the harmless page. When the victim clicks on a control element, it triggers actions that were not intended. This type of attack is called clickjacking.
In an illustrative example, an attacker could prepare a page with a fictitious lottery and place a second, invisible button over the “Win” button (see figure 1). The victim wants to click on the “Win” button, but in fact clicks on the invisible one, which confirms a bank payment in the victim’s name.
To prevent such attacks, it is recommended to set the X-Frame-Options Header on your application as follows:
X-Frame-Options: deny
This header is still supported by modern browsers but is considered to be outdated. You should therefore also set the “frame-ancestors” directive in the Content-Security-Policy.
Content-Security-Policy
The Content-Security-Policy is a security measure that is supported by all modern browsers (since around 2016). It replaces outdated headers such as X-Frame-Options and X-XSS-Protection, but also offers a variety of additional security settings. Creating a CSP that allows an existing application to function correctly but still offers significant protection is a challenging task. There are several services and browser plug-ins available on the web that can help generate an initial CSP. In order to take advantage of the full potential of protection offered by a CSP, it is sometimes also necessary to make adjustments to the website that is to be protected. For this reason, it is only possible to make a limited recommendation at this point.
To protect your application from clickjacking, it is recommended to set at least the following header:
Conten-Security-Policy: frame-ancestors 'none';
Further directives can specify restrictions on the sources from which specific content may be loaded and the functions that a page is permitted to execute. When creating a CSP, the sources “unsafe-inline” and “unsafe-eval” should be avoided, as these will otherwise undermine the protection of the CSP.
Strict-Transport-Security
The Strict-Transport-Security header (HSTS) informs the user’s browser that the requested page is only allowed to be loaded via an encrypted connection. This helps to prevent attacks in which attackers interpose themselves between the victim and a requested website (attacker-in-the-middle). Another advantage is that all unencrypted requests to the application are converted directly into encrypted requests by the browser. This can prevent scenarios in which sensitive data is transmitted via an unencrypted connection. To activate HSTS, set the following header:
Strict-Transport-Security: max-age=31536000; includeSubDomains
The “max-age” directive specifies how long the browser remembers that the website should only be accessed via an encrypted connection. The “includeSubDomains” directive tells the browser that these rules also apply to all subdomains. This means that a browser that has already visited “example.com” will also only access “sub.example.com” via an encrypted connection. However, this does not mean that “sub.example.com” should not set the HSTS header, as there is no guarantee that a visitor has already visited the parent domain.
Data Minimization in Product Headers
There are other headers that do not directly control the security functions in the browser but can still influence the security of an application. Various frameworks and platforms set headers by default that provide the visitor with information about the software products used. The visitor’s browser does not process this information, but it can provide attackers with important clues about their target. Attackers can use this information, for example, to search online for known security vulnerabilities. The following common headers fall into this category:
- Server
- X-Powered-by
- X-AspNet-Version
For an Appache server that delivers a PHP website, this could look something like this:
Server: Apache/2.4.18 (Ubuntu)
X-Powered-By: PHP-7.2.0+ubuntu16.04.1+deb.sury.org+2
Disabling these headers does not fix any of the actual security vulnerabilities, but an attacker must now identify the technologies used manually. It also prevents botnets and worms from automatically identifying the system as a target. Data minimization therefore pays off in this case. The way in which these headers can be deactivated depends greatly on the technologies and products used. In many cases, a simple directive in a configuration file is sufficient.
Where Do You Set Security Headers
When configuring security headers, it is best to do this in a central location in your application. If an application is protected by a reverse proxy or a web application firewall, this is an ideal place to set the desired security headers. This means that the configuration can be managed in just one place, which reduces the likelihood of errors. If you do not use a reverse proxy or a WAF, it is best to set the security headers in the web server configuration. This ensures that the headers are always set, even if the same web server delivers several websites (virtual host). You should avoid setting security-relevant headers only for a specific part of a single application as much as possible. Headers such as X-Content-Options do not add any value to a REST API or static resources, but they do not have any negative effects either.
It is also necessary to avoid the duplication of headers and directives. If a response contains several or even contradictory headers, as in the example below, it depends on the respective browser, which instructions are implemented:
X-Frame-Options: DENY SAMEORIGIN
Content-Type: text/html; charset=UTF-8
Content-Type: application/json
In the example above, the browser must decide whether it should implement the “DENY” or “SAMEORIGIN” directive of the X-Frame-Options header. It is also not clear whether the browser should interpret the response received as text data in JSON format or as an HTML page. When you make adjustments to the headers of your application, you should subsequently check whether the end result matches your intentions.
Conclusion
By properly configuring modern security headers, existing vulnerabilities cannot be fixed, but their exploitation can be prevented or at least significantly hindered. Security headers therefore form an important and relatively inexpensive safety net and are quick and easy to implement. To achieve solid basic protection, the security headers of your application should look something like this:
Content-Type: text/html; charset=UTF-8
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
X-Frame-Options: deny
Conten-Security-Policy: frame-ancestors 'none';
Strict-Transport-Security: max-age=31536000; includeSubDomains
Please be aware that the Content-Type header is typically automatically configured correctly by the web server based on the content being delivered. You can further tailor several of these mentioned headers to suit your application, whether to incorporate exceptions or establish more stringent security measures. Customizing the CSP to the individual application can be particularly worthwhile.
In the area of information security, it is also worthwhile to have your applications regularly tested by a third party. Oneconsult has many years of experience in web application security and offers various services in this area. This enables you to customize your upcoming audit to align perfectly with your application and specific requirements. The Oneconsult Penetration Testing Team will be happy to help you comprehensively test the security of your applications. In our Cyber Security Academy, we also train your next security specialist or teach the whole team the basics of secure web development.
We look forward to hearing from you: