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 :
- X-XSS-Protection
- X-Frame-Options
- X-Content-Type-Options
- Content-Security-Policy
- Referrer-Policy
- HTTP Strict Transport Security
- SameSite
- HttpOnly
- Secure
- Conclusion
- 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
Video produced by Wild & Secure, your consulting firm to all things security and real estate.
If you want to receive weekly quality content about security, subscribe to our newsletter on our website.
Top comments (9)
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
Hi, Rémi, thanks for the fabulous tutorial. Stay safe! Stuart
Thank you so much for your comment Stuart.