CSP and CORS

CSP

CSP (Content Security Policy) is a policy set by the developer by a HTTP header or meta tag in the HTML and enforced by browsers so resources cannot be loaded from not approved sources.

So CSP defends the client so it wont load resources from unknown sources.

Besides reducing XSS vulnerabilites a strict CSP can even give you a heads up about some package that you have installed and is doing network requests to some unknown place.

Adding CSP trough meta tag

The content security policy can be set up on the server or by adding it trough a meta tag.

1
<meta http-equiv="Content-Security-Policy" content="default-src 'self' *.example.com">

Here the http-equiv attributes simulates HTTP response header.
If you need dynamic content security policy a good option is to use webpacks html webpack plugin.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const CSP = 'Content-Security-Policy': {
'http-equiv': 'Content-Security-Policy',
'content':
'default-src \'self\'; \
script-src \'self\'; \
style-src \'self\' \'unsafe-inline\' blob:; \
img-src \'self\' data: blob:; \
font-src \'self\' data:; \
connect-src \'self\' ws.localhost'
}
}

// ...
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, 'index.html'),
meta: CSP,
})

Syntax

Notice keywords like ‘self’ are contained in quotes.
Notice each subsection has a ; character separating except the last one.
The full list of available options can be found here .

Interesting CSP directives

  • report-uri specifies a URL where a browser will send reports when a content security policy is violated. This directive can’t be used in tags.

  • frame-ancestors directive specifies valid parents that may embed a page

Changing the CSP

CSP are great but they are not flawless, some workarounds are possible by browser extensions.
One trick is to create listen to the chrome.webRequest.onHeadersReceived event and modify the headers. As far as i know this is only possible on google chrome by the extension that has been last installed.

Example:

1
2
3
4
5
6
7
8
chrome.webRequest.onHeadersReceived.addListener(
modifyCspHeaders,
{
urls : [ 'http://*/*', 'https://*/*' ],
types: [ 'main_frame' ]
},
[ 'blocking', 'responseHeaders' ]
);

You can find more here.

CORS

CORS (Cross Origin Sharing) is a way to manage the strictness of the Same origin policy.

Cors secures clients sessions and provides some level of ddos protection to the servers.

Before cors you could not do requests to different domains from your site.
Cors allows this but comes with a more strict handling of ajax requests on the client side.

According to the documentation browsers differentiate between two type of requests, simple requests and preflighted requests.

In broad terms more sensitive operations / methods and unusualy types ( json ) are preflighted.

Simple requests are done normally, just how you would expect.
Preflighted requests first send a HTTP OPTIONS request to the server. This includes the HTTP ORIGIN header. The server decides wheter or not this matches its origin policy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Server side
function isOriginAllowed(origin, allowedOrigin) {
if (Array.isArray(allowedOrigin)) {
for (var i = 0; i < allowedOrigin.length; ++i) {
if (isOriginAllowed(origin, allowedOrigin[i])) {
return true;
}
}
return false;
} else if (isString(allowedOrigin)) {
return origin === allowedOrigin;
} else if (allowedOrigin instanceof RegExp) {
return allowedOrigin.test(origin);
} else {
return !!allowedOrigin;
}
}

If its accepted by the server the actual request is done, if not the request fails.
The validity of the ORIGIN header and the honoring of the server is done by the browser automatically.
Parts of it are enforced trough the fetch api.

Typical CORS problems

You just wrote the first fetch call in your sweet new webapp. Its a POST request or to a different port and the ‘No ‘Access-Control-Allow-Origin’ header is present on the requested resource. ‘ error pops up. This is because you did not set CORS setting on the backend and the browser identified your request as a prefligthed request.

Adding CORS headers

Most of the time you only need to enable some middleware to enable it on the server side.
For example you can use the popular plugin express-cors.
If you take a look at the source of the plugin you can see its not magic. It simply checks if the ORIGIN is matching and sets headers accordingly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//...
function cors(options, req, res, next) {
var headers = [],
method = req.method && req.method.toUpperCase && req.method.toUpperCase();

if (method === 'OPTIONS') {
// preflight
headers.push(configureOrigin(options, req));
headers.push(configureCredentials(options, req));
headers.push(configureMethods(options, req));
headers.push(configureAllowedHeaders(options, req));
headers.push(configureMaxAge(options, req));
headers.push(configureExposedHeaders(options, req));
applyHeaders(headers, res);

if (options.preflightContinue) {
next();
} else {
// Safari (and potentially other browsers) need content-length 0,
// for 204 or they just hang waiting for a body
res.statusCode = options.optionsSuccessStatus;
res.setHeader('Content-Length', '0');
res.end();
}
} else {
// actual response
headers.push(configureOrigin(options, req));
headers.push(configureCredentials(options, req));
headers.push(configureExposedHeaders(options, req));
applyHeaders(headers, res);
next();
}
}

Bypassing CORS

Since CORS are enforced by the browser they can be disabled.
The user can disable CORS:

1
2
# not a good idea
google-chrome --disable-web-security

The fetch api has a mode property with ‘no-cors’ but it also limits the possible requests to simple requests.

Additionally you can also put a proxy between you and the server.
In this article the writer forked the cors everywhere package so in the proxy request it overwrites the ORIGIN header to something that is accepted by the server then return the response to the page that needs the data.