Content Security Policy
Content Security Policy Concepts
Content Security Policy is a set of rules governing the behavior of user agents in regard to inline and external sources of Web content. The policy is declared by the server and is applied by the browser to a protected resource.
To frame a CSP directive, the server must append the Content-Security-Policy header to an HTTP response conveying the protected resource as its payload. The header contains a policy token made up of one or more directives. CSP directives are separated by semicolons:
Content-Security-Policy: script-src cdn.example.com; media-src media.example.net; style-src 'self'
In script-src cdn.example.com
the script-src is the name of the directive, and the cdn.example.com
URL is the directive value.
CSP directives can have the following values:
- a URL scheme (for example, https:, http:, blob:, data:);
- a Uniform Resource Locator consisting of a scheme, a URL authority (host, port) and a path;
- a quoted keyword -
'self'
,'unsafe-inline'
or'unsafe-eval'
; - the special
'none'
value; - the value expressed as the asterisk character (*); it has the "allow assets from all sources" connotation;
- a nonce source encoded in Base64 and preceded by the
nonce-
keyword; a nonce is a sequence of bytes generated by a cryptographically strong random number generator; a nonce value should have a length of at least 128 bits; - a hash source - Base64 representation of an SHA-256, SHA-384 or SHA-512 hash value; the directive value starts with the
sha256-
,sha384-
orsha512-
keyword, accordingly.
Multiple values of a CSP directive are separated by spaces:
Content-Security-Policy: script-src cdn.example.com cdn.example.net cdn.example.org
URL Schemes in CSP Directives
Let's create a protected resource coded in PHP. PHP in the examples below will be mingled with the static markup. The most straightforward way to append the Content-Security-Policy header to an HTTP response is to call the header() function before sending any other content from the server:
<?php
header('Content-Security-Policy: script-src https:');
?>
<!DOCTYPE html>
. . .
The https: directive value will make the browser load only those scripts that are transferred through secure HTTP communications:
<?php
header('Content-Security-Policy: script-src https:');
?>
<!DOCTYPE html>
<html>
. . .
<script src='http://example.com/js/widgets.js'></script>
. . .
</html>
As a result of the scheme discrepancy, the browser will block the loading of widgets.js
and display a notice about the CSP violation in the console:
Content Security Policy: The page's settings blocked the loading of a resource at http://example.com/js/widgets.js ("script-src https:").
Host Sources in CSP Directives
The host name is a mandatory part of a URL declared as a host source in CSP directives:
<?php
header('Content-Security-Policy: script-src example.com');
?>
<!DOCTYPE html>
<html>
. . .
<script src='http://example.com/js/utilities.js'></script>
. . .
</html>
The script utilities.js
satisfies the demands of the current security policy, so the file will be loaded by the browser. The same CSP directive could be formulated as Content-Security-Policy: script-src http://example.com
or Content-Security-Policy: script-src http://example.com/
.
Appending a path to the URL would make CSP settings more precise: e. g. declaring the host source as http://example.com/js/
would mark all scripts deployed in the js
directory as "trusted". A directory name ending an URL in a CSP directive should be ended with the slash character (/), otherwise the browser will regard the last part of the URL as a file name.
A single file can be specified as the safe source. too:
Content-Security-Policy: script-src http://example.com/js/utilities.js
The blob: URL Scheme
The blob: URL scheme is used in conjunction with a Blob object created in JavaScript:
<?php
header("Content-Security-Policy: script-src 'unsafe-inline'; img-src 'self'");
?>
<!DOCTYPE html>
<html>
. . .
<script>
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
. . .creating canvas graphics . . .
canvas.toBlob(blob => {
var url = URL.createObjectURL(blob);
var img = new Image();
img.src = url;
document.body.appendChild(img);
}, 'image/png');
</script>
. . .
</html>
The code above converts a canvas to a Blob, then creates a blob: URL and tries to employ it as a source of the new <img> element. The attempt will fail because of the CSP directive for image assets. To authorize the use of the Blob with the image, the blob: scheme should be explicitly declared in the CSP header:
header("Content-Security-Policy: script-src 'unsafe-inline'; img-src blob:");
The data: URL Scheme
Another specific scheme frequently used with image and media resources is data::
Content-Security-Policy: img-src data:
The CSP directive above will allow the browser to render an image with the following src attribute:
<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhE . . . image data encoded in Base64 . . . '>
Using 'self' Source Expression
Creating a directive with the 'self'
keyword permits the load of any resource sharing the same scheme, host, and port URL components with the protected resource:
Content-Security-Policy: script-src 'self'
If the directive above is specified in http://example.com/csp-demo/protected-resource.php
, then all external scripts deployed at http://example.com
are considered conforming to the current CSP.
The 'self'
source expression based on the rule of the same origin does not allow the browser to execute inline JavaScript code inserted in the protected resource by <script> elements. Likewise, DOM event attributes (onload, onclick, onmouseover, etc.) are also ignored by the browser. Further restrictions can be implemented by browser vendors: for example, in Chrome 45 the 'self'
value will not "whitelist" the blob: URL.
Using 'unsafe-inline' and 'unsafe-eval' Source Expressions
To sanction the use of inline scripts and styles, the 'unsafe-inline'
keyword source should be specified:
Content-Security-Policy: script-src 'self' 'unsafe-inline'
The directive permits the browser to load external scripts as well as to execute inline script routines of the protected resource.
The Global object instantiated implicitly by JS engines exposes a number of global properties and methods. One of its methods is the eval() function evaluating a string of JS code and executing it:
eval('document.location.origin');
The eval() function is considered potentially dangerous, so if a resource has CSP restrictions imposed on client scripts, the call to eval() will be blocked. To "legitimate" the function, the CSP script-src directive should have the 'unsafe-eval'
value:
Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval'
JavaScript Timers
JavaScript timers created by the setInterval() and setTimeout() functions can depend on dynamic code evaluation. The example below schedules a timeout to run the supplied JavaScript code every 1000 milliseconds:
setInterval('document.title=new Date().toLocaleTimeString()', 1000);
If a timer is created with a string of JavaScript code as its first argument, then it abides by the same security policy as the global eval(): the 'unsafe-eval'
value is necessary for such timers to be launched by the browser.
The javascript: URL Scheme
CSP restrictions declared in the script-src directive are fully applied to hyperlinks with the javascript: URL:
<?php
header("Content-Security-Policy: script-src 'self'");
?>
<!DOCTYPE html>
<html>
. . .
<a href='javascript:console.log("testing javascript: URL . . .");'>click to test the URL</a>
. . .
<a href='javascript:eval("document.body.textContent=Math.PI");'>Math.PI</a>
. . .
</html>
An attempt to click any of the hyperlinks above will cause violation of the security policy. To make both links "clickable" the CSP directive should be rewritten as
Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval'
The 'none' Directive Value
The 'none'
value blocks the load of all resources of a specified type. For example, the following CSP header compels the browser not to load Web assets for <object>, <embed> or <applet> elements:
Content-Security-Policy: object-src 'none'
Wildcard Directive Value
If a security directive has a value specified as the asterisk character, then assets from all sources are allowed:
Content-Security-Policy: img-src *
However, the "allow all" value has its own restrictions: it will not sanction inline scripts and CSS. Besides, browser vendors impose additional restraints upon the wildcard directive: for example, it does not permit the use of the blob: and data: URLs in Firefox 40.
Nonce Value
A raw nonce value has a minimum length of 16 bytes:
<?php
$nonce = base64_encode(openssl_random_pseudo_bytes(16));
$directive = "script-src 'nonce-$nonce'";
header("Content-Security-Policy: $directive");
?>
<!DOCTYPE html>
<html>
. . .
<script>console.log('This code will be blocked');</script>
<?php
echo "<script nonce='$nonce'>console.log('This code is trusted');</script>";
?>
. . .
</html>
The openssl_random_pseudo_bytes() function generates a sequence of 16 random bytes. An additional boolean argument can be used to find out if the underlying random number generator is cryptographically strong:
$rnd = openssl_random_pseudo_bytes(16, $strong);
if ($strong == 1) {
$nonce = base64_encode($rnd);
. . .
}
The base64_encode() transforms the given raw nonce into a Base64 string. The string is then passed to the CSP directive for client scripts. The first inline script will be blocked as it does not have the nonce attribute with the value matching the value of the script-src directive. The second script created dynamically in PHP will be executed because its nonce is exactly the same as the nonce in the CSP header:
Content-Security-Policy: script-src 'nonce-yqqJY9QLJSRSKri1JWybSw=='
<script nonce='yqqJY9QLJSRSKri1JWybSw=='>
console.log('This code is trusted');
</script>
Hash Functions
Hash functions provide a reliable way to check the integrity of Web assets. The developer can compute hashes of inline client scripts and use the results of computation in CSP directives.
Digest computation of JS code snippets might be performed with the help of a desktop cryptographic tool:
echo -n "console.log('The script will be executed');" | openssl dgst -sha256 -binary | openssl enc -base64
The CSP directive with the SHA-256 source expression is declared as
header("Content-Security-Policy: script-src 'sha256-cJdulit0W1lNuxPiSACtaTn8HdVLV9E/1h2pYILIUhI='");
The first inline <script> element in the example below will be blocked; the second one will be loaded and executed: its SHA-256 value matches the digest indicated in the CSP header.
<script>console.log('The script will be rejected');</script>
<script>console.log('The script will be executed');</script>