loading...
Cover image for Security Headers to use on your webserver

Security Headers to use on your webserver

shostarsson profile image Rémi Lavedrine Updated on ・15 min read

If you want to have a lot of information about Information Security, be sure to follow my Youtube Channel.


Introduction

Dozens of websites are hacked because of misconfiguration or a lack of protection.

You can indeed see on the OWASP Top 10, which are the Top 10 identified flaws on web servers and services from the OWASP (Open Web Application Security Project), that the Security Misconfiguration is the most identified cause of vulnerabilities on web services.
This is commonly a result of insecure default configurations, incomplete or ad-hoc configurations, open cloud storage, misconfigured HTTP headers, and verbose error messages containing sensitive information.
Not only must all operating systems, frameworks, libraries, and applications be securely configured, but they must be patched/upgraded in a timely fashion.

Let's deep dive on the weaknesses and impacts of it.

Threat Agents / Attack Vectors Security Weakness Impacts
App Specific/Exploitability:3 Prevalence:3/Detectability:3 Technical:2/Business ?
Attackers will often attempt to exploit unpatched flaws or access default accounts, unused pages, unprotected files and directories, etc to gain unauthorized access or knowledge of the system. Security misconfiguration can happen at any level of an application stack, including the network services, platform, web server, application server, database, frameworks, custom code, and pre-installed virtual machines, containers, or storage. Automated scanners are useful for detecting misconfigurations, use of default accounts or configurations, unnecessary services, legacy options, etc. Such flaws frequently give attackers unauthorized access to some system data or functionality. Occasionally, such flaws result in a complete system compromise. The business impact depends on the protection needs of the application and data.

In this post, we are going to go through the headers and configuration you should use on your project in order to secure your server.
At first, we consider that all requests and responses are transmitted over https.
And that all the logging and information from the server configuration are hidden.

Before you start :

  • Don't forget to backup your current configuration before making any change to your configuration.
  • Moreover some headers may not be compatible regarding on the browser. I encourage you to check out the browser compatibility on the compatibility matrix that is available on that page.
  • Mod-Headers must be enabled in Apache to implement these headers. Ensure the line is uncommented in httpd.conf file.
Let us review the HTTP headers list that we are going to cover :
  1. X-XSS-Protection
  2. X-Frame-Options
  3. X-Content-Type-Options
  4. Content-Security-Policy
  5. Referrer-Policy
  6. HTTP Strict Transport Security
  7. SameSite
  8. HttpOnly
  9. Secure
  10. Conclusion
  11. Annexes

Note : If you want to have the sum up and the Apache and NGinx configurations, you can go to the "Conclusion".


1. X-XSS-Protection

The X-XSS-Protection header can prevent some level of XSS (CrosSite-Scripting) attacks.
XSS attacks enable attackers to inject client-side scripts into web pages viewed by other users.

Browser Support

Internet Explorer Edge Firefox Chrome Safari Opera Android
8 . NS 4+ . . .

There are 4 possible ways you can configure that header.

Parameter Value Meaning
0 XSS filter disabled
1 XSS filter enabled and sanitized the page if attack detected
1;mode=block XSS filter enabled and prevented rendering the page if attack detected
1;report=http://example.com/report_URI XSS filter enabled and reported the violation if attack detected

What we recommend to implement : 1;mode=block

Server type How to
Apache HTTP Server Add the following entry in httpd.conf of your Apache web server. Header set X-XSS-Protection "1; mode=block". Restart the apache to verify
NGinx Add the following in nginx.conf under http block. add_header X-XSS-Protection "1; mode=block";. Nginx restart is needed to get this reflected on your web page response header.

2. X-Frame-Options

The X-Frame-Options header prevents Clickjacking vulnerability on your website.
By implementing this header, you instruct the browser not to embed your web page in frame/iframe.

Browser Support

Internet Explorer Edge Firefox Chrome Safari Opera Android
8 13 47 49 9.1 39 4.4

There are 3 possible ways you can configure that header.

Parameter Value Meaning
SAMEORIGIN Frame/iFrame of content is only allowed form the same site origin.
DENY Prevent any domain to embed your content using frame/iframe.
ALLOW-FROM Allow framing the content only on particular URI.

What we recommend to implement : DENY

Server type How to
Apache HTTP Server Add the following entry in httpd.conf of your Apache web server. Header always append X-Frame-Options DENY. Restart the apache to verify
NGinx Add the following in nginx.conf under http block. add_header X-Frame-Options “DENY”;. Nginx restart is needed to get this reflected on your web page response header.

3. X-Content-Type-Options

The X-Content-Type-Options header prevents MIME types security risk by adding this header to your web page’s HTTP response. Having this header instruct browser to consider files types as defined and disallow content sniffing.

Browser Support

Internet Explorer Edge Firefox Chrome Safari Opera Android
8 . 51 1.0 NS 13 .

There are 1 possible way you can configure that header.

Parameter Value Meaning
nosniff Consider files types as defined and disallow content sniffing.

What we recommend to implement : nosniff

Server type How to
Apache HTTP Server Add the following entry in httpd.conf of your Apache web server. Header set X-Content-Type-Options nosniff. Restart the apache to get the configuration active and then verify.
NGinx Add the following in nginx.conf under server block. add_header X-Content-Type-Options nosniff;. Nginx restart is needed to get this reflected on your web page response header.

4. Content Security Policy

The Content Security Policy prevent XSS, clickjacking, code injection attacks by implementing the Content Security Policy (CSP) header in your web page HTTP response.
CSP instruct browser to load allowed content to load on the website.
Nevertheless, if you implement CSRF, in some framework (like AngularJS) the browser retrieves the CSRF cookie and add a custom header XSRF-HEADER to the response in order to implement a CSRF prevention method. So you have to be very careful about how you implement that header. You can find a lot of great methods to prevent CSRF on the OWASP website.

Browser Support

Internet Explorer Edge Firefox Chrome Safari Opera Android
11 13 47 49 9.1 39 4.4

There are 23 possible ways you can configure that header.

Parameter Value Meaning
base-uri Define the base uri for relative uri.
default-src Define loading policy for all resources type in case of a resource type dedicated directive is not defined (fallback).
script-src Define which scripts the protected resource can execute.
object-src Define from where the protected resource can load plugins.
style-src Define which styles (CSS) the user applies to the protected resource.
img-src Define from where the protected resource can load images.
media-src Define from where the protected resource can load video and audio.
frame-src Deprecated and replaced by child-src.
child-src Define from where the protected resource can embed frames.
frame-ancestors Define from where the protected resource can be embedded in frames.
font-src Define from where the protected resource can load fonts.
connect-src Define which URIs the protected resource can load using script interfaces.
manifest-src Define from where the protected resource can load manifest.
form-action Define which URIs can be used as the action of HTML form elements.
sandbox Specifies an HTML sandbox policy that the user agent applies to the protected resource.
script-nonce Define script execution by requiring the presence of the specified nonce on script elements.
plugin-types Define the set of plugins that can be invoked by the protected resource by limiting the types of resources that can be embedded.
reflected-xss Instructs a user agent to activate or deactivate any heuristics used to filter or block reflected cross-site scripting attacks, equivalent to the effects of the non-standard X-XSS-Protection header.
block-all-mixed-content Prevent user agent from loading mixed content.
upgrade-insecure-requests Instructs user agent to download insecure resources using HTTPS.
referrer Define information user agent must send in Referer header.
report-uri (deprecated) Specifies a URI to which the user agent sends reports about policy violation.
report-to Specifies a group (defined in Report-To header) to which the user agent sends reports about policy violation.

What we recommend to implement : default-src on self with reporting enabled

Server type How to
Apache HTTP Server Add the following entry in httpd.conf of your Apache web server. Header set Content-Security-Policy: default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi. Restart the apache to get the configuration active and then verify.
NGinx Add the following in nginx.conf under server block. add_header Content-Security-Policy "default-src 'self';", "report-uri http://reportcollector.example.com/collector.cgi;"". Nginx restart is needed to get this reflected on your web page response header.

5. Referrer Policy

The Referrer-Policy HTTP header governs which referrer information, sent in the Referer header, should be included with requests made.

Browser Support

Internet Explorer Edge Firefox Chrome Safari Opera Android
NS NS 50 56 NS 43 .

There are 8 possible ways you can configure that header.

Parameter Value Meaning
no-referrer The Referer header will be omitted entirely.
no-referrer-when-downgrade This is the user agent's default behavior if no policy is specified.
origin Only send the origin of the document as the referrer in all cases.
origin-when-cross-origin Send a full URL when performing a same-origin request, but only send the origin of the document for other cases.
same-origin A referrer will be sent for same-site origins, but cross-origin requests will contain no referrer information.
strict-origin Only send the origin of the document as the referrer to a-priori as-much-secure destination (HTTPS->HTTPS), but don't send it to a less secure destination (HTTPS->HTTP).
strict-origin-when-cross-origin Send a full URL when performing a same-origin request, only send the origin of the document to a-priori as-much-secure destination (HTTPS->HTTPS), and send no header to a less secure destination (HTTPS->HTTP).
unsafe-url Send a full URL (stripped from parameters) when performing a a same-origin or cross-origin request.

What we recommend to implement : no-referrer

Server type How to
Apache HTTP Server Add the following entry in httpd.conf of your Apache web server. Header set Referrer-Policy "no-referrer". Restart the apache to get the configuration active and then verify.
NGinx Add the following in nginx.conf under server block. add_header Referrer-Policy no-referrer;. Nginx restart is needed to get this reflected on your web page response header.

6. HTTP Strict Transport Security

HTTP Strict Transport Security (HSTS) is a web security policy mechanism which helps to protect websites against protocol downgrade attacks and cookie hijacking. It allows web servers to declare that web browsers (or other complying user agents) should only interact with it using secure HTTPS connections, and never via the insecure HTTP protocol.

Browser Support

Internet Explorer Edge Firefox Chrome Safari Opera Android
11 13 47 49 9.1 39 4.4

There are 2 possible ways you can configure that header.

Parameter Value Meaning
max-age=SECONDS The time, in seconds, that the browser should remember that this site is only to be accessed using HTTPS.
includeSubDomains If this optional parameter is specified, this rule applies to all of the site's subdomains as well.

What we recommend to implement : max-age=31536000; includeSubDomains

Server type How to
Apache HTTP Server Add the following entry in httpd.conf of your Apache web server. Header set Strict-Transport-Security "max-age=31536000; includeSubDomains". Restart the apache to get the configuration active and then verify.
NGinx Add the following in nginx.conf under server block. add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';. Nginx restart is needed to get this reflected on your web page response header.

7. Cookies

When receiving an HTTP request, a server can send a Set-Cookie header with the response. The cookie is usually stored by the browser, and then the cookie is sent with requests made to the same server inside a Cookie HTTP header. An expiration date or duration can be specified, after which the cookie is no longer sent. Additionally, restrictions to a specific domain and path can be set, limiting where the cookie is sent.

The Set-Cookie and Cookie headers

The Set-Cookie HTTP response header sends cookies from the server to the user agent.
A simple cookie is set like this Set-Cookie: <cookie-name>=<cookie-value>.

Session cookies

The cookie created above is a session cookie: it is deleted when the client shuts down, because it didn't specify an Expires or Max-Age directive. However, web browsers may use session restoring, which makes most session cookies permanent, as if the browser was never closed.

Permanent cookies

Instead of expiring when the client closes,permanent cookies expire at a specific date (Expires) or after a specific length of time (Max-Age). Permanent cookies are set like this Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2020 07:28:00 GMT;.

Browser Support

Internet Explorer Edge Firefox Chrome Safari Opera Android
. . . . . . .

There are many possible cookies you can add to improve the security of your product.

"SameSite" Cookie

SameSite cookies let servers require that a cookie shouldn't be sent with cross-site requests, which somewhat protects against cross-site request forgery attacks (CSRF). SameSite cookies are still experimental and not yet supported by all browsers.

The same-site attribute can have one of two values:
|Value|Meaning|
|---|---|
|strict|If a same-site cookie has this attribute, the browser will only send cookies if the request originated from the website that set the cookie. If the request originated from a different URL than the URL of the current location, none of the cookies tagged with the strict attribute will be included.|
|lax|If the attribute is set to lax, same-site cookies are withheld on cross-domain subrequests, such as calls to load images or frames, but will be sent when a user navigates to the URL from an external site, for example, by following a link.|

The default behavior if the flag is not set, or not supported by the browser, is to include the cookies in any request, including cross-origin requests.

"Secure" Cookie

A secure cookie is only sent to the server with an encrypted request over the HTTPS protocol. Even with Secure, sensitive information should never be stored in cookies, as they are inherently insecure and this flag can't offer real protection. Starting with Chrome 52 and Firefox 52, insecure sites (http:) can't set cookies with the Secure directive.

"HttpOnly" Cookie

To prevent cross-site scripting (XSS) attacks, HttpOnly cookies are inaccessible to JavaScript's Document.cookie API; they are only sent to the server. For example, cookies that persist server-side sessions don't need to be available to JavaScript, and the HttpOnly flag should be set.

What we recommend to implement : Secure=True; HttpOnly=True, SameSite=strict

Apache
Server type How to
Apache HTTP Server Add the following entry in httpd.conf of your Apache web server. Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure;SameSite=Strict. Restart the apache to get the configuration active and then verify.
Apache HTTP Server lower than Aache 2.2.4 Add the following entry in httpd.conf of your Apache web server. Header set Set-Cookie HttpOnly;Secure;SameSite=Strict. Restart the apache to get the configuration active and then verify.
PHP
setcookie ( string $key [, string $value = "" [, int $expires = 0 [, string $path = "" [, string $domain = "" [, bool $secure = FALSE [, bool $httponly = FALSE ]]]]]] ) : bool
Node.JS
response.setHeader('Set-Cookie', 'key=value; secure; HttpOnly; SameSite=Strict');
Python
self.set_secure_cookie('key', value, secure=True, httponly=True)
Ruby on Rails
cookies["key"] = { :value => "value", :secure => true, :http_only => true, :same_site =>  }

Then you can test your website http response header against that website.


Conclusion

Let's sum up the server configuration you can use.

Apache

Header Implementation
X-XSS-Protection Header set X-XSS-Protection "1; mode=block"
X-Frame-Options Header set X-Frame-Options "DENY"
X-Content-Type-Options Header set X-Content-Type-Options "nosniff"
Content-Security-Policy Header set Content-Security-Policy: default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi
Referrer-Policy Header set Referrer-Policy "no-referrer"
HTTP Strict Transport Security Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains"

Nginx

Header Implementation
X-XSS-Protection add_header X-XSS-Protection "1;mode=block";
X-Frame-Options add_header X-Frame-Options "DENY";
X-Content-Type-Options add_header X-Content-Type-Options "nosniff";
Content-Security-Policy add_header Content-Security-Policy "default-src 'self';", "report-uri http://reportcollector.example.com/collector.cgi;"
Referrer-Policy add_header Referrer-Policy no-referrer;
HTTP Strict Transport Security add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";

Cookies

Server type How to
Apache HTTP Server Add the following entry in httpd.conf of your Apache web server. Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure;SameSite=Strict. Restart the apache to get the configuration active and then verify.
Apache HTTP Server lower than Aache 2.2.4 Add the following entry in httpd.conf of your Apache web server. Header set Set-Cookie HttpOnly;Secure;SameSite=Strict. Restart the apache to get the configuration active and then verify.

Browser Support

Internet Explorer Edge Firefox Chrome Safari Opera Android
HTTP Strict Transport Security (HSTS) 11 13 47 49 9.1 39
Public Key Pinning Extension for HTTP (HPKP) NS NS 47 49 NS 39
X-Frame-Options 8 13 47 49 9.1 39
X-XSS-Protection 8 . NS 4+ . .
X-Content-Type-Options 8 . 51 1.0 NS 13
Content-Security-Policy 11 13 47 49 9.1 39
X-Permitted-Cross-Domain-Policies . . . . . .
Referrer-Policy NS NS 50 56 NS 43
Expect-CT . . . 61 . 48
Feature-Policy . . . . . .

How to Test in Real Life

This is the easiest way to test security headers. Just open up a console and fire Curl;
Curl will grab the headers for you within seconds. We need to use Curl with parameters I and L. I switch will tell curl to grab the head and L parameter will help to follow the redirects, if our target has any.

curl -I -L --url <target domain or IP>
➜ curl -I -L --url example.fr/espace-client
HTTP/1.1 301 Moved Permanently
Location: https://www.example.fr/
Content-Length: 0
Connection: close
Date: Thu, 04 Jul 2019 13:18:30 GMT

HTTP/2 200
content-type: text/html; charset=UTF-8
x-frame-options: SAMEORIGIN
strict-transport-security: max-age=15724800; includeSubDomains
cache-control: max-age=3600, public
link: <http://www.example.fr/>; rel="shortlink", <http://www.example.fr/>; rel="canonical"
link: </node/1>; rel="revision"
x-ua-compatible: IE=edge
content-language: fr
x-content-type-options: nosniff
x-frame-options: DENY
expires: Sun, 19 Nov 1978 05:00:00 GMT
last-modified: Thu, 04 Jul 2019 12:29:39 GMT
etag: W/"1562243379"
x-drupal-cache: HIT
x-xss-protection: 1; mode=block
access-control-allow-origin: *
content-security-policy: upgrade-insecure-requests
access-control-allow-methods: POST, GET, DELETE, PUT
access-control-max-age: 1000
access-control-allow-headers: Content-Type, origin, accept
referrer-policy: no-referrer
date: Thu, 04 Jul 2019 13:18:30 GMT
vary: cookie

Nmap can be used to test and validate security headers very easily. We can leverage an nmap script named “http-security-headers”. Download it from this link: https://svn.nmap.org/nmap/scripts/http-security-headers.nse

nmap -p 443,80 --script http-security-headers <target IP or Domain>

Refer to the below result: It gives a nice overview of implemented header values.

➜ nmap -p 443,80 --script http-security-headers example.fr
Starting Nmap 7.70 ( https://nmap.org ) at 2019-07-04 15:19 CEST
Nmap scan report for example.fr (92.223.124.199)
Host is up (0.012s latency).

PORT    STATE SERVICE
80/tcp  open  http
| http-security-headers:
|   Strict_Transport_Security:
|     Header: Strict-Transport-Security: max-age=15724800; includeSubDomains
|   X_Frame_Options:
|     Header: X-Frame-Options: SAMEORIGIN, DENY
|     Description: The browser must not display this content in any frame.
|   X_XSS_Protection:
|     Header: X-XSS-Protection: 1; mode=block
|     Description: The browser will prevent the rendering of the page when XSS is detected.
|   X_Content_Type_Options:
|     Header: X-Content-Type-Options: nosniff
|     Description: Will prevent the browser from MIME-sniffing a response away from the declared content-type.
|   Content_Security_Policy:
|     Header: Content-Security-Policy: upgrade-insecure-requests
|     Description: Instructs user agent to download insecure resources using HTTPS.
|   Cache_Control:
|     Header: Cache-Control: max-age=3600, public
|   Expires:
|_    Header: Expires: Sun, 19 Nov 1978 05:00:00 GMT
443/tcp open  https
| http-security-headers:
|   Strict_Transport_Security:
|     Header: Strict-Transport-Security: max-age=15724800; includeSubDomains
|   X_Frame_Options:
|     Header: X-Frame-Options: SAMEORIGIN, DENY
|     Description: The browser must not display this content in any frame.
|   X_XSS_Protection:
|     Header: X-XSS-Protection: 1; mode=block
|     Description: The browser will prevent the rendering of the page when XSS is detected.
|   X_Content_Type_Options:
|     Header: X-Content-Type-Options: nosniff
|     Description: Will prevent the browser from MIME-sniffing a response away from the declared content-type.
|   Content_Security_Policy:
|     Header: Content-Security-Policy: upgrade-insecure-requests
|     Description: Instructs user agent to download insecure resources using HTTPS.
|   Cache_Control:
|     Header: Cache-Control: max-age=3600, public
|   Expires:
|_    Header: Expires: Sun, 19 Nov 1978 05:00:00 GMT

Nmap done: 1 IP address (1 host up) scanned in 0.88 seconds

Annexes

OWASP Secure Headers Project - OWASP
HTTP Cookies Security - Mozilla Developer Network

Posted on by:

shostarsson profile

Rémi Lavedrine

@shostarsson

Software Dev turned Security Dev. Follow me on dev.to, twitch.tv/shostarsson or youtube.com/shostarsson

Discussion

pic
Editor guide
 

Very much appreciated, Rémi. Your Security Headers tutorial covers everything needed to understand the intricacies and how to implement. I found your examples, the recommendations and the browser compatibility info a bonus.

Can I please pick your brain for a moment? On an Apache server, would you add the Security Headers to the .htaccess file in the Home Directory or in the Web Root for best security?

BIG Thanks!

 

Hi Julian,

Usually, we put that into an .htaccess file.

 

Hi Rémi,

Yes, I know it the Security Headers go into an .htaccess file. I have .htaccess files in my Home Directory and another .htaccess file in my Web Root directory. So, do you know which .htaccess/directory is best to place the Security Headers into?

Thanks!

Yes, sorry.
Usually, we put it into the Home directory with proper rights.

 

Loved your article Rémi!
I'll include it in my "default server configs" checklist!
Thank you.

 

I'm so happy that you love it. And that it would of some help to you. :-)

 

Another useful link for those that want to validate their security headers: securityheaders.com