HTTP Security Headers with Nginx

Introduction

There are several web application threats that manifest themselves in the client's browser. We can defend against these on the server side but the execution of the attack happens in the client's browser. HTTP security headers can help mitigate some of the threats by instructing the client's browser on how to handle our website's content and connections to the server.

HTTP Headers

HTTP is the language of the web. Clients and servers communicate with each other using HTTP. HTTP messages can contain one or more optional headers that allow the client and the server to pass additional information with the request or the response.

You can inspect the request/response headers within the browser by visiting a web page and opening the browser console (Ctrl+Shift+K on Firefox, Ctrl+Shift+J on Chrome) and clicking on the Network tab.

browser console showing http headers

Note that on visiting the homepage of Attosol at http://www.attosol.com, the server responds with a 301 (Moved Permanently) status code and sets the Location header to https://www.attosol.com, forcing the browser to make the request again using HTTPS.

Nginx

Nginx can be configured to set response headers by modifying the server blocks in the configuration files.

Add Header Directive

The add_header directive sets response headers.

add_header X-Content-Type-Options "nosniff" always;

Here, we set the X-Content-Type-Options header, used to protect against MIME sniffing vulnerabilities. The always parameter ensures that the header is set for all responses, including internally generated error responses.

Header Inheritance

Generally, you place the add_header directive in the top‑level server block. When you do this, Nginx will ignore the headers from the parent block. Similarly, if a location includes an add_header directive itself, it does not inherit headers from enclosing server block. When you specify a header inside a child block, you need to re-declare all add_header directives from the parent block inside the child block again.

HTTP Strict Transport Security (HSTS)

When a browser requests a page manually without an explicit http://, the server responds in cleartext. A secure server must immediately send a redirect response to ensure that all further requests happen on https:// connection. However, even this approach is vulnerable to Man-in-the-Middle attacks for a well placed attacker. An attacker could intercept the request and redirect the client to a different page, stealing the user's information. Modern web applications need to be deployed over HTTPS. If you haven't set up your web server to use SSL till now, here's a guide for that.

HSTS is a way to deal with this potential vulnerability by instructing the browser that a domain can only be accessed using HTTPS. If the user enters or uses an HTTP link, the browser strictly upgrades the connection to HTTPS:

Setting the HSTS header

On receiving the HSTS header from a website, the browser will prevent any further communication to that website over HTTP and will use HTTPS instead. We can configure nginx to set the HSTS header on every response using the add_header directive.

server {  
    listen 80;
    listen [::]:80;

        server_name example.com;
        return 301 https://$server_name$request_uri;
}
server {  
    listen [::]:443 ssl ipv6only=on;
    listen 443 ssl;

    ssl_certificate path_to/fullchain.pem;
    ssl_certificate_key path_to/privkey.pem;
    include path_to/options-ssl-nginx.conf;
    ssl_dhparam path_to/ssl-dhparams.pem;

    root /var/www/html/your_app;

    index index.html;

    server_name your_domain;

    location / {
        try_files $uri $uri.html $uri/ =404;
    }

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
}

When a browser sees this header from an HTTPS website, it “learns” that this domain must only be accessed using HTTPS. It caches this information for the max-age period (typically 31,536,000 seconds, equal to about 1 year). On initially testing HSTS, it is recommended to start with very small values for the max-age timeout.

The optional includeSubDomains parameter tells the browser that the HSTS policy also applies to all subdomains of the current domain. Check the image below and notice the Status code 307.

browser console showing internal redirect due to hsts

The previous requests follows with a regular redirect, and you can find the Strict-Transport-Security header in the next request as follows (we have kept the age to 1 day or 86400 seconds only for demonstration purpose):
strict-transport-security header

HSTS Preload

You can only set the HSTS header over HTTPS. HSTS works with a trust-on-first-use assumption. To eliminate this and to force HTTPS even on the very first request, you can submit your domain to a preload list. Preload lists are hard-coded into the browsers, ensuring that even the very first request would be made over HTTPS.

Content Security Policy

The CSP header is a way of whitelisting the things that your site is allowed to run. This includes images, stylesheets, javascript, inline styles, frames.

Content-Security-Policy: policy  

CSP makes it possible to reduce or eliminate the vectors by which XSS can occur by specifying the domains that the browser should consider to be valid sources of executable scripts. A CSP compatible browser will then only run scripts loaded in source files received from those whitelisted domains.

Policy

The policy is a list of whitelisted sources for content on the website. Check out the results of setting a CSP on a website.

Content-Security-Policy: default-src 'self'  

browser console with error messages

A policy contains one or more directives, with each directive followed by a space separated list of allowed origins. The directives are separated by a semi-colon.
default-src directive defines valid sources for fonts, images, media, scripts and styles. self matches the scheme, port and origin of the document it is served with. The browser effectively blocked loading of all external stylesheets and scripts.
Let's modify this directive to allow the blocked content with the new CSP.

Content-Security-Policy: default-src 'self' https://code.jquery.com https://cdnjs.cloudflare.com https://stackpath.bootstrapcdn.com;

results with new csp Our content now loads properly with the new CSP in place.
results with new csp

Fine Grained Controls

One major feature of CSP is the granularity of the controls that it allows. To further lock down our CSP while allowing the required content on our page, we could rewrite our policy, disallowing images/videos, frames.

Content-Security-Policy: script-src 'self' https://code.jquery.com https://cdnjs.cloudflare.com https://stackpath.bootstrapcdn.com; style-src https://stackpath.bootstrapcdn.com;

script-src, style-src are some of the directives that help define the policy with higher granularity. Checkout this CSP Cheat Sheet to learn more about the CSP directives.

CSP Reporting

CSP can be tested in a report-only mode or deployed in production with violation reporting.

Content-Security-Policy-Report-Only: <policy-directive>; <policy-directive>

Content-Security-Policy: default-src 'self'; report-uri https://reportcollector.example.com/collector  

You can configure your own back-end service to handle the incoming reports. Also, report-uri.com provides a free CSP report collection service.

Nginx Configuration with CSP Header

Let's check out our final Nginx configuration.

server {  
    listen 80;
    listen [::]:80;

    server_name example.com;
           return         301 https://$server_name$request_uri;
}
server {


    listen [::]:443 ssl ipv6only=on;
    listen 443 ssl;

    ssl_certificate path_to/fullchain.pem;
    ssl_certificate_key path_to/privkey.pem;
    include path_to/options-ssl-nginx.conf;
    ssl_dhparam path_to/ssl-dhparams.pem;

    root /var/www/html/your_app;

    index index.html;

    server_name your_domain;

    location / {
        try_files $uri $uri.html $uri/ =404;
    }

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Content-Security-Policy "default-src 'self' https://code.jquery.com https://cdnjs.cloudflare.com https://stackpath.bootstrapcdn.com;";

    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options SAMEORIGIN always;
    add_header X-XSS-Protection "1; mode=block" always;

}

Other Headers

Note the 3 new headers added in the configuration file shown above.

add_header X-Content-Type-Options "nosniff" always;  
add_header X-Frame-Options SAMEORIGIN always;  
add_header X-XSS-Protection "1; mode=block" always;  

The X-Frame-Options header can help prevent click-jacking attacks. This header indicates whether the web page can be rendered in a <frame>.

The X-XSS-Protection header is used to ensure that the browser's XSS Protection filter is turned on.

While these headers provide protection in older browsers, implementing a strong Content-Security-Policy covers much more ground.

Parting words

The web platform offers a wide variety of tools and techniques for security. securityheaders.io is a very helpful tool that you can use to check if your web server is setting the headers properly. We covered techniques to mitigate a few threats to web applications. Stay safe!

Ramit Mittal

Read more posts by this author.

Subscribe to Attosol Technologies

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!