nginx and security related headers

Goals

As a serious server owner you’re strongly advised to keep your server configuration up to date and implement every common security technique available to secure your server. And even more if you provide services for customers. Therefore I decided to update my nginx configuration to the „state-of-the-art“ level… Switching completely to HTTPS was already done in the last time.

There are quiet a lot of sites on the net which spread the words of security but there are also a lot of questionable ones… But my bookmarks and friend google helped me finding some sites which are serious – IMHO. (Find list at the end)
So all currently respected headers are now in my configuration (HKP is not, for several reasons). A short summary of all headers will follow:

Header Description
Strict-Transport-Security HTTP Strict-Transport-Security (HSTS) enforces secure (HTTP over SSL/TLS) connections to the server.
X-Frame-Options The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a <frame>, <iframe> or <object>. Sites can use this to avoid clickjacking attacks, by ensuring that their content is not embedded into other sites.
X-Content-Type-Options The only defined value, „nosniff“, prevents Internet Explorer and Google Chrome from MIME-sniffing a response away from the declared content-type
X-XSS-Protection This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
Content-Security-Policy If enabled, CSP has significant impact on the way browser renders pages (e.g., inline JavaScript disabled by default and must be explicitly allowed in policy). CSP prevents a wide range of attacks, including Cross-site scripting and other cross-site injections.

And(!) another goal was to keep the configuration as small and simple as possible…

The first try

After reading about the different security headers and tutorials covering the implementation I came up with the following configuration:

server {
  add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload;";
  add_header X-Frame-Options SAMEORIGIN;
  add_header X-Content-Type-Options "nosniff";
  add_header X-XSS-Protection "1; mode=block";
  add_header Content-Security-Policy "default-src 'none'; 
                                      script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; 
                                      connect-src 'self' https:; 
                                      img-src 'self' https:; 
                                      style-src 'self' 'unsafe-inline' https:; 
                                      font-src 'self' https:; 
                                      frame-src 'self' https:; 
                                      object-src 'none';";
  ...
  location / {...}
  location ~* ^.+.(jpg|jpeg|gif|bmp|ico|png|css|js|swf)$ {
    expires max;
    access_log off;
    add_header Pragma public;
    add_header Cache-Control "public, must-revalidate, proxy-revalidate";
  }
}

As said I wanted to keep configuration small and simple. In the nginx-docs (ngx_http_headers_module.html) it is mentioned that „add_header“ can be set on every level (http, server, location) with inheritence. So i tried to set some security related header in the server block related to one domain. My expectation was that all headers will also be valid for each sub-level (=locations).

nginx! – where are my headers???

After the nginx restart I checked the send headers and – nothing… There were absolutely no security headers nowhere…
My first guess was that this is a cache related issue – cleaned up all caches and – nothing again…
Hmm… maybe a typo somewhere in the configuration? Checked – nope…
Ok, my nginx was latest stable – maybe there is a bug? So I tried the mainline version of nginx which was 1.7.9. And you guess it – no headers at all…

The final solution

Before becoming total mad I decided to post my problem on the nginx forum and ask for help. And – surprisingly fast – I got it.
But it was not that kind of that I expected. In my faulty tries I added all headers to the common server level as I wanted them to apply for the whole domain and all locations. That was in fact not the real problem but adding additional headers on an assets location. Like in any common configuration I added some cache related headers to all css, js and picture files. And this addition was responsible for all security headers to disappear.
Well, this seems to be my own fault as I missed the essential line in the offical docs (ngx_http_headers_module.html#add_header) which declares „These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.“
My reading seems to have ended after „are inherited“ so that I missed the real important part…

Finally I ended up with copying all those headers to each location level block. So the final version looks like:

server {
  ...
  location / {
    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload;";
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options "nosniff";
    add_header X-XSS-Protection "1; mode=block";
    add_header Content-Security-Policy "default-src 'none'; 
                                        script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; 
                                        connect-src 'self' https:; 
                                        img-src 'self' https:; 
                                        style-src 'self' 'unsafe-inline' https:; 
                                        font-src 'self' https:; 
                                        frame-src 'self' https:; 
                                        object-src 'none';";
    ...
  }
  location ~* ^.+.(jpg|jpeg|gif|bmp|ico|png|css|js|swf)$ {
    expires max;
    access_log off;
    add_header Pragma public;
    add_header Cache-Control "public, must-revalidate, proxy-revalidate";
    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload;";
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options "nosniff";
    add_header X-XSS-Protection "1; mode=block";
    add_header Content-Security-Policy "default-src 'none'; 
                                        script-src 'self' https:; 
                                        connect-src 'self' https:; 
                                        img-src 'self' https:; 
                                        style-src 'self' 'unsafe-inline' https:; 
                                        font-src 'self' https:; 
                                        frame-src 'self' https:; 
                                        object-src 'none';";
  }
}

Lessons learned

Always read the documentation to the end! 😉

Useful resources

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Du kannst diese HTML Tags und Attribute benutzen:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>