codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Follow publication

Content Security Policy with Amazon CloudFront: Part 2

We wrap up this series and provide a final configuration that gives us a perfect score.

This a continuation of the series starting with, Content Security Policy with Amazon CloudFront: Part 1.

Clickjacking

Having implemented Strict-Transport-Security, Mozilla Observatory reports:

What’s a good next step?

The use of the X-Frame-Options header and Content Security Policy’s frame-ancestors directive are a simple and easy way to protect your site against clickjacking attacks.

Mozilla Observatory

To keep things simple, we restrict our web application to not load in an iframe with a number of HTTP response headers.

Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY

We update the Amazon Lamda function (re-creating the CloudFront distribution and CNAME records) as follows:

'use strict';exports.handler = (event, context, callback) => {
...
const headerHSTS = 'Strict-Transport-Security';
const headerCSP = 'Content-Security-Policy';
const headerXFO = 'X-Frame-Options';

...
headers[headerHSTS.toLowerCase()] = [{
key: headerHSTS,
value: 'max-age=31536000',
}];
headers[headerCSP.toLowerCase()] = [{
key: headerCSP,
value: 'frame-ancestors \'none\'',
}];
headers[headerXFO.toLowerCase()] = [{
key: headerXFO,
value: 'DENY',
}];
callback(null, response);
};

With this simple improvement we dramatically improve our score.

Valid Mime Types

You’re halfway finished! Nice job!

The X-Content-Type-Options header tells browsers to stop automatically detecting the contents of files. This protects against attacks where they’re tricked into incorrectly interpreting files as JavaScript.

Mozilla Observatory

Only halfway finished?!

A more complete description of this feature is:

The X-Content-Type-Options response HTTP header is a marker used by the server to indicate that the MIME types advertised in the Content-Type headers should not be changed and be followed. This allows to opt-out of MIME type sniffing, or, in other words, it is a way to say that the webmasters knew what they were doing.

Mozilla — X-Content-Type-Options

The header implementation is…

X-Content-Type-Options: nosniff

We update the Amazon Lamda function (re-creating the CloudFront distribution and CNAME records) as follows:

'use strict';exports.handler = (event, context, callback) => {
...
const headerCTO = 'X-Content-Type-Options';
...
headers[headerCTO.toLowerCase()] = [{
key: headerCTO,
value: 'nosniff',
}];

callback(null, response);
};

And we go from a B- to a B.

Content Security Policy

The feedback the Observatory gives is:

You’re doing a wonderful job so far!

Did you know that a strong Content Security Policy (CSP) policy can help protect your website against malicious cross-site scripting attacks?

In a nutshell, Content-Security-Policy HTTP response header controls which resources an HTML page can use. Reading up on this, a recommended starter policy is:

Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';

This creates a default that blocks content and then allows exceptions for scripts (JS), connects (XHR), images (Img), and styles (CSS).

Using Chrome developer tools, we can see that our sample app only needs scripts, images, and styles (I thought) from the same domain.

When I first tried this, I thought I could removed the connect (XHR) exception and discovered that the service worker setup with create-react-app reported errors (apparently it is setup to pre-load files); a sample error:

service-worker.js:1 Refused to connect to 'https://d1ivzly8he3uqo.cloudfront.net/index.html?_sw-precache=cef06143bd734e39a03a769077b815dc' because it violates the following Content Security Policy directive: "default-src 'none'". Note that 'connect-src' was not explicitly set, so 'default-src' is used as a fallback.

We update the Amazon Lamda function (re-creating the CloudFront distribution and CNAME records) as follows:

'use strict';exports.handler = (event, context, callback) => {
...
headers[headerCSP.toLowerCase()] = [{
key: headerCSP,
value: 'default-src \'none\'; script-src \'self\'; connect-src \'self\'; img-src \'self\'; style-src \'self\';',
}];
...
};

Yeah, we got our A+.

But, of course the tool gave us some more feedback (since when is A+ was not perfect).

Referrer Policy

You’re on the home stretch!

The use of Referrer Policy can help protect the privacy of your users by restricting the information that browsers provide when accessing resources kept on other sites.

Mozilla Observatory

To completely stop the browser from sending referrer headers when leaving our web application, we use:

Referrer-Policy: no-referrer

We update the Amazon Lamda function (re-creating the CloudFront distribution and CNAME records) as follows:

'use strict';exports.handler = (event, context, callback) => {
...
const headerRP = 'Referrer-Policy';
...
headers[headerRP.toLowerCase()] = [{
key: headerRP,
value: 'no-referrer',
}];

callback(null, response);
};

Finally

The final full Amazon Lamda function is:

'use strict';exports.handler = (event, context, callback) => {
const response = event.Records[0].cf.response;
const headers = response.headers;
const headerNameSrc = 'X-Amz-Meta-Last-Modified';
const headerNameDst = 'Last-Modified';
const headerHSTS = 'Strict-Transport-Security';
const headerCSP = 'Content-Security-Policy';
const headerXFO = 'X-Frame-Options';
const headerCTO = 'X-Content-Type-Options';
const headerRP = 'Referrer-Policy';
if (headers[headerNameSrc.toLowerCase()]) {
headers[headerNameDst.toLowerCase()] = [{
key: headerNameDst,
value: headers[headerNameSrc.toLowerCase()][0].value,
}];
console.log(`Response header "${headerNameDst}" was set to ` +
`"${headers[headerNameDst.toLowerCase()][0].value}"`);
}
headers[headerHSTS.toLowerCase()] = [{
key: headerHSTS,
value: 'max-age=31536000',
}];
headers[headerCSP.toLowerCase()] = [{
key: headerCSP,
value: 'default-src \'none\'; script-src \'self\'; connect-src \'self\'; img-src \'self\'; style-src \'self\';',
}];
headers[headerXFO.toLowerCase()] = [{
key: headerXFO,
value: 'DENY',
}];
headers[headerCTO.toLowerCase()] = [{
key: headerCTO,
value: 'nosniff',
}];
headers[headerRP.toLowerCase()] = [{
key: headerRP,
value: 'no-referrer',
}];
callback(null, response);
};

Enjoy.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Written by John Tucker

Broad infrastructure, development, and soft-skill background

Responses (2)

Write a response