Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Heroku profile imageCaleb Hearth
Caleb Hearth forHeroku

Posted on • Originally published atcalebhearth.com on

     

Using HTTP Headers to Secure Your Site

Observatory by Mozilla helps websites by teaching developers, system administrators, and security professionals how to configure their sites safely and securely.

Let's take a look at the scores Observatory gives for a fairly straightforwardStatic Buildpack app,https://2017.keeprubyweird.com.

Test Scores

TestPassScoreExplanation
Content Security Policy-25Content Security Policy (CSP) header not implemented
Cookies0No cookies detected
Cross-origin Resource Sharing0Content is not visible via cross-origin resource sharing (CORS) files or headers
HTTP Public Key Pinning0HTTP Public Key Pinning (HPKP) header not implemented (optional)
HTTP Strict Transport Security-20HTTP Strict Transport Security (HSTS) header not implemented
Redirection0Initial redirection is to https on same host, final destination is https
Referrer Policy0Referrer-Policy header not implemented (optional)
Subresource Integrity0Subresource Integrity (SRI) not implemented, but all scripts are loaded from a similar origin
X-Content-Type-Options-5X-Content-Type-Options header not implemented
X-Frame-Options-20X-Frame-Options (XFO) header not implemented
X-XSS-Protection-10X-XSS-Protection header not implemented

Even though we use Heroku'sAutomated Certificate Management to easily get an SSL certificate for our domains, our overall score is an F, 20/100. We'll walk through each failing test, learn what caused the failure, and try to fix them.

Content Security Policy (CSP)

The failure here is "CSP header not implemented", and when we view the linked security guideline we see that CSP gives us control over where scripts and resources we reference on our site can be loaded from. For Keep Ruby Weird, this means fonts, several external image sources, and a couple of analytics sources.

CSP gives us a few levels of strictness:

  • default-src <source>,script-src <source>,object-src <source>, etc. These limit the sources of various types of resources.
    • https: limit resources of the specified type, or all resources, to HTTPS only
    • https://example.com limit resources to this domain. Multiple such sources can be provided for the same*-src directive.
    • 'self' means that resources can only be loaded from the current host, useful for relative resources like<script src="/index.js">.
    • SeeCSP: default-src on MDN for full options here.
  • TheContent-Security-Policy header disallows<script> tags with inline code by default. Those with asrc instead are allowed. This can be disabled by adding'unsafe-inline' which makes our site less secure. You can also specifynonces or SHA sums of the content of those scripts to allow them to execute.
  • frame-ancestors 'none' Prevents your site from being loaded in an iframe and being used in aclickjacking attack. If you do need to display your site in an iframe, you can specify URLs instead.

For our purposes, we will want to be able to use self-hosted resources, images from Twitter and AWS, scripts from Google and Twitter analytics, and both a stylesheet and font entry from Google Fonts. We'll also need tore-define'self' in a few directives because they don't fall back on the default unless the options aren't specified. For example, we haven't specifiedobject-src so it falls back on thedefault-src value of'self'.

Content-Security-Policy: default-src 'self';                         script-src https://static.ads-twitter.com https://www.google-analytics.com;                         img-src 'self' https://s3.amazonaws.com https://twitter.com https://pbs.twimg.com;                         font-src 'self' https://fonts.gstatic.com;                         style-src 'self' https://fonts.googleapis.com;                         frame-ancestors 'none';
Enter fullscreen modeExit fullscreen mode

We can add this to our static.json as a part of the headers collection for all paths:

#..."headers":{"/**":{"Content-Security-Policy":"default-src 'self'; script-src https://static.ads-twitter.com https://www.google-analytics.com; img-src 'self' https://s3.amazonaws.com https://twitter.com https://pbs.twimg.com; font-src 'self' https://fonts.gstatic.com; style-src 'self' https://fonts.googleapis.com; frame-ancestors 'none';"}}
Enter fullscreen modeExit fullscreen mode

When we add or remove external resources, we'll need to update this collection or our users will see errors in the browser's console and the resources will be unavailable.

HTTP Strict Transport Security (HSTS)

We failed this test for basically the same reason: "HTTP Strict Transport Security (HSTS) header not implemented".

HSTS tells a browser that our site should only be viewed over HTTPS. Looking at the HSTS security guideline, we see that HSTS provides several nonexclusive flags:

  • max-age=<seconds>. How long user agents will redirect to HTTPS, in seconds. This tells a browser "once you've seen this, assume that all requests to this domain will be over HTTPS for this long." Mozilla recommends 2 years, or63072000 seconds. This flag is required.
  • includeSubDomains. Whether user agents should upgrade requests on subdomains. Unless you have a reason you wouldn't have SSL on all subdomains, you probably want this. I'd recommend it even if you don't currently have subdomains.
  • preload. If you have this flag and also register your domain on theChrome HSTS preload list, browsers will not even need to see this header before forcing HTTPS requests. This is useful, but opt-in as you do need to register your own domain by looking it up on the preload list, checking some boxes, and submitting. It's pretty easy and we'll do it here.
#..."headers":{"/**":{"Strict-Transport-Security":"max-age=63072000; includeSubDomains; preload"}}
Enter fullscreen modeExit fullscreen mode

X-Content-Type-Options

This header tells browsers not to load scripts and stylesheets if their MIME type as indicated by the server is incorrect. It's a good thing to have on.

#..."headers":{"/**":{"X-Content-Type-Options":"nosniff"}}
Enter fullscreen modeExit fullscreen mode

X-Frame-Options

This header prevents your site from being loaded in an iframe. It helps prevent"clickjacking" attacks. It is the same protection offered byframe-ancestors 'none' in Content-Security-Policy but adds support for older browsers. If you do need to display your site in an iframe on another page of your site, you can instead use theSAMEORIGIN option.

#..."headers":{"/**":{"X-Frame-Options":"DENY"}}
Enter fullscreen modeExit fullscreen mode

X-XSS-Protection

This header protects fromcross-site scripting (XSS) attacks. It provides similar protection as Content-Security-Policy but again protects older browsers.

#..."headers":{"/**":{"X-XSS-Protection":"1; mode=block"}}
Enter fullscreen modeExit fullscreen mode

Putting It All Together

Adding this header block to our static.json increases our score from an F to an A on the Observatory.

"headers":{"/**":{"Content-Security-Policy":"default-src 'self'; script-src https://static.ads-twitter.com https://www.google-analytics.com 'sha256-q2sY7jlDS4SrxBg6oq/NBYk9XVSwDsterXWpH99SAn0='; img-src 'self' https://s3.amazonaws.com https://twitter.com https://pbs.twimg.com; font-src 'self' https://fonts.gstatic.com; style-src 'self' https://fonts.googleapis.com; frame-ancestors 'none';","Strict-Transport-Security":"max-age=63072000; includeSubDomains; preload","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","X-XSS-Protection":"1; mode=block"}}
Enter fullscreen modeExit fullscreen mode

When we load up the browser, everything looks right! Unfortunately we did miss one thing, which we can see in the console.

Refused to execute inline script because it violates the following Content Security Policy directive: "script-srchttps://static.ads-twitter.comhttps://www.google-analytics.com". Either the 'unsafe-inline' keyword, a hash ('sha256-q2sY7jlDS4SrxBg6oq/NBYk9XVSwDsterXWpH99SAn0='), or a nonce ('nonce-...') is required to enable inline execution.

Even though we addedhttps://www.google-analytics.com to our script-src, because it is being loaded in an inline script we'll need to allow it to be run explicitly. The error message is kind enough to offer us a couple of options: "Either the 'unsafe-inline' keyword, a hash ('sha256-q2sY7jlDS4SrxBg6oq/NBYk9XVSwDsterXWpH99SAn0='), or a nonce ('nonce-...') is required to enable inline execution."

'unsafe-inline' sounds, well, unsafe, so let's skip that. A nonce is a one-time-use number that would allow the inline script to be run. Nonces can be made safe, but as we're talking about static pages that's out of scope here. The hash provided in the error message is the actual sha-256 sum of the content of the inline code block, is more secure than the other options. It will keep attackers from changing the content of the Google Analytics inline script which makes it safer than an unprotected inline script. Like changing external dependencies, if we ever change that script tag we'll also need to change the sha-256 sum.

"Content-Security-Policy": "default-src 'self'; script-src https://static.ads-twitter.com https://www.google-analytics.com 'sha256-q2sY7jlDS4SrxBg6oq/NBYk9XVSwDsterXWpH99SAn0='; img-src 'self' https://s3.amazonaws.com https://twitter.com https://pbs.twimg.com; font-src 'self' https://fonts.gstatic.com; style-src 'self' https://fonts.googleapis.com; frame-ancestors 'none';"
Enter fullscreen modeExit fullscreen mode

We've added the SHA sum, and now Google Analytics is all set up!

The Results

TestPassScoreExplanation
Content Security Policy+5Content Security Policy (CSP) implemented without'unsafe-inline' or'unsafe-eval'
Cookies0No cookies detected
Cross-origin Resource Sharing0Content is not visible via cross-origin resource sharing (CORS) files or headers
HTTP Public Key Pinning0HTTP Public Key Pinning (HPKP) header not implemented (optional)
HTTP Strict Transport Security0HTTP Strict Transport Security (HSTS) header set to a minimum of six months (15768000)
Redirection0Initial redirection is to https on same host, final destination is https
Referrer Policy0Referrer-Policy header not implemented (optional)
Subresource Integrity0Subresource Integrity (SRI) not implemented, but all scripts are loaded from a similar origin
X-Content-Type-Options0X-Content-Type-Options header set to"nosniff"
X-Frame-Options+5X-Frame-Options (XFO) implemented via the CSPframe-ancestors directive
X-XSS-Protection0X-XSS-Protection header set to"1; mode=block"

We're up to A+! Not bad for an hour's work, and our users are much more secure now when visiting our site.

Extra Credit

We're on the home stretch now. There are a few optional things we can do to beef up security and privacy even more.

Referrer Policy

Browsers include aReferrer header that identifies where a user came from when visiting a new page. It's useful in tracking where users are coming from, but there are some privacy concerns with that. TheReferrer-Policy header controls when and how much information is provided.

  • no-referrer. Tells the browser to never send theReferer header.
  • same-origin. Send the referrer, but only on requests inside the site (e.g. /security-in-the-static-buildpack => /posts)
  • strict-origin. Send the referrer information to all origins, but only the URL sans path (e.g.https://example.com/)
  • strict-origin-when-cross-origin. Send full referrer information on same origin, but only the URL sans path on foreign origin.

no-referrer can be used as a fallback for browsers as many of these options have not yet been implemented at this point.

Referrer-Policy: no-referrer, strict-origin-when-cross-origin
Enter fullscreen modeExit fullscreen mode

More?

Mozilla Observatory also has tests forCookies andSubresource Integrity, but it was happy with the Keep Ruby Weird site after the changes we've already made so those are left as an exercise for the reader.

Final Result

Here is the final result of this change, excluding the opt-in "preload" directive to HSTS, and it is our recommendation for all static buildpack apps.

{"headers":{"/**":{"Content-Security-Policy":"default-src 'self'; script-src https://static.ads-twitter.com https://www.google-analytics.com 'sha256-q2sY7jlDS4SrxBg6oq/NBYk9XVSwDsterXWpH99SAn0='; img-src 'self' https://s3.amazonaws.com https://twitter.com https://pbs.twimg.com; font-src 'self' https://fonts.gstatic.com; style-src 'self' https://fonts.googleapis.com; frame-ancestors 'none';","Referrer-Policy":"no-referrer, strict-origin-when-cross-origin","Strict-Transport-Security":"max-age=63072000; includeSubDomains","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","X-XSS-Protection":"1; mode=block"}}}
Enter fullscreen modeExit fullscreen mode

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Code[ish]: a podcast from the team at Heroku, exploring code, technology, tools, tips, and the life of the developer.

More fromHeroku

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp