43

Chrome 18 Dev/Canary has just been released, and content_security_policy will be needed in the manifest for certain extensions.

I'm trying to get a CSP working for inline scripting, but I don't know if I'm doing something wrong or if this is a Chrome 18 bug.

manifest.json:

{
    "name": "CSP Test",
    "version": "1.0",
    "manifest_version": 2,
    "options_page": "test.html",
    "content_security_policy": "default-src 'unsafe-inline'"
}

test.html:

<html><head>
<script type="text/javascript">
        alert("hello");
</script>
</head></html>

In Chrome 18, this unpacked extension fails to load, displaying an error:

Could not load extension from '[extension directory]'. Invalid value for 'content_security_policy'.

If I change 'unsafe-inline' to 'self', the extension loads fine, but alert() does not work, and the option page's console contains an error:

Refused to execute inline script because of Content-Security-Policy.

In Chrome 16, using 'unsafe-inline' lets the extension load fine and alert() works, too. However, in Chrome 16, replacing 'unsafe-inline' with 'foo' lets the extension load, but of course does not let alert() work, so perhaps Chrome 18 is stricter than 16, but...

Is default-src 'unsafe-inline' actually invalid, or is this a bug? What CSP value can I use to make alert() work in Chrome 18?


Based on the accepted answer below, inline scripts no longer work in extensions in Chrome 18. alert() will need to be placed in its own JavaScript file.

Anjum....
  • 4,086
  • 1
  • 35
  • 45
Chris McFarland
  • 6,059
  • 5
  • 43
  • 63
  • Use sandboxed iframes, allowing eval and inline scripts. Nothing in the content security policy needs to be touched. Take a look: https://developer.chrome.com/docs/extensions/mv2/sandboxingEval/ – Saiansh Singh Jan 20 '21 at 03:42

4 Answers4

42

For recent versions of Chrome (46+), the previously accepted answer is no longer true. unsafe-inline still has no effect (in the manifest and in meta header tags), but per the documentation, you can use the technique described here to relax the restriction.

Hash usage for <script> elements

The script-src directive lets developers whitelist a particular inline script by specifying its hash as an allowed source of script.

Usage is straightforward. The server computes the hash of a particular script block’s contents, and includes the base64 encoding of that value in the Content-Security-Policy header:

Content-Security-Policy: default-src 'self';
                     script-src 'self' https://example.com 'sha256-base64 encoded hash'

Example

Consider the following:

manifest.json:

{
  "manifest_version": 2,
  "name": "csp test",
  "version": "1.0.0",
  "minimum_chrome_version": "46",
  "content_security_policy": "script-src 'self' 'sha256-WOdSzz11/3cpqOdrm89LBL2UPwEU9EhbDtMy2OciEhs='",
  "background": {
    "page": "background.html"
  }
}

background.html:

<!DOCTYPE html>
<html>
  <head></head>
  <body>
    <script>alert('foo');</script>
  </body>
</html>

Result:
alert dialog from inline script

Further investigation

I also tested putting the applicable directive in a meta tag instead of the manifest. While the CSP indicated in the console message did include the content of the tag, it would not execute the inline script (in Chrome 53).

new background.html:

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'sha256-WOdSzz11/3cpqOdrm89LBL2UPwEU9EhbDtMy2OciEhs='">
  </head>
  <body>
    <script>alert('foo');</script>
  </body>
</html>

Result:
console error messages about content security policy

Appendix: Generating the hashes

Here are two methods for generating the hashes:

  1. Python (pass JS to stdin, pipe it somewhere else):
import hashlib
import base64
import sys

def hash(s):
    hash = hashlib.sha256(s.encode()).digest()
    encoded = base64.b64encode(hash)
    return encoded

contents = sys.stdin.read()
print(hash(contents))
  1. In JS, using the Stanford Javascript Crypto Library:
var sjcl = require('sjcl');
// Generate base64-encoded SHA256 for given string.
function hash(s) {
  var hashed = sjcl.hash.sha256.hash(s);
  return sjcl.codec.base64.fromBits(hashed);
}

Make sure when hashing the inline scripts that the whole contents of the script tag are included (including all leading/trailing whitespace). If you want to incorporate this into your builds, you can use something like cheerio to get the relevant sections. Generically, for any html, you can do:

var $ = cheerio.load(html);
var csp_hashes = $('script')
  .map((i, el) => hash($(el).text())
  .toArray()
  .map(h => `'sha256-${h}'`)
  .join(' ');
var content_security_policy = `script-src 'self' 'unsafe-eval' ${csp_hashes}; object-src 'self'`;

This is the method used in hash-csp, a gulp plugin for generating hashes.

jpaugh
  • 6,634
  • 4
  • 38
  • 90
Chris Hunt
  • 3,840
  • 3
  • 30
  • 46
  • 1
    How do you get the SHA256 Base 64 String from, let's say this [url](https://npmcdn.com/core-js@2.4.1/client/shim.min.js) ? Tried to make a js file out of it but the generated SHA doesn't work. – Standaa - Remember Monica Jul 26 '16 at 15:00
  • You can use this [service](http://hash.online-convert.com/sha256-generator) to get a Base64 SHA256 string out of an url. – Standaa - Remember Monica Jul 26 '16 at 16:04
  • @Stanislasdrg I added additional information on generating the hashes. – Chris Hunt Dec 20 '16 at 03:05
  • 1
    I got 'content_security_policy': CSP directive 'object-src' must be specified (either explicitly, or implicitly via 'default-src') and must whitelist only secure resources. – AlexMorley-Finch Jul 22 '19 at 10:56
  • well detailed. manifest.json works for me even today september 2019 – KD.S.T. Sep 17 '19 at 15:14
  • @StanislasdrgReinstateMonica you need to download the script and compute she using one of these functions. – Phani Rithvij Jul 26 '20 at 13:06
  • 1
    @ChrisHunt Going forward, please drop a link to another answer whenever you refer to it. I tried to update the first sentence here, but I'm not sure which answer was 'current' when you posted this. – jpaugh Nov 05 '20 at 20:32
15

The following answer is true for older versions of Chrome (<46). For more recent ones, please check @Chris-Hunt answer https://stackoverflow.com/a/38554505/422670

I just posted a very similar answer for the question https://stackoverflow.com/a/11670319/422670

As is said, there's no way to relax the inline security policy in v2 extensions. unsafe-inline simply does not work, intentionally.

There really is no other way than moving all your javascript into js files and point to them with a <script src>.

There's, though, the option to do Eval and new Function inside a sandboxed iframe, for instance with the following lines in the manifest:

"sandbox": {
    "pages": [
      "page1.html",
      "directory/page2.html"
    ]
},

A sandboxed page will not have access to extension or app APIs, or direct access to non-sandboxed pages (it may communicate with them via postMessage()). You can further restrict the sandbox rights with a specific CSP

There's now a full example from the Google Chrome team on the github eval in iframe on how to circumvent the problem by communicating with a sandboxed iframe, as well as a short analytics tutorial

Thanks to Google, there's a lot of extension rewriting in the lineup :(

EDIT

It is possible relax the security policy for REMOTE scripts. But not for inlines.

The policy against eval() and its relatives like setTimeout(String), setInterval(String), and new Function(String) can be relaxed by adding 'unsafe-eval' to your policy: "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"

However, we strongly recommend against doing this. These functions are notorious XSS attack vectors.

this appeared in the trunk documentation and is discussed in the thread "eval re-allowed"

inline scripts will not be back though:

There is no mechanism for relaxing the restriction against executing inline JavaScript. In particular, setting a script policy that includes 'unsafe-inline' will have no effect.

Community
  • 1
  • 1
Stefano
  • 18,083
  • 13
  • 64
  • 79
  • @Derek I know!! but something might be changing, cross fingers. Just updated my answer :) – Stefano Sep 17 '12 at 08:36
  • But they still don't let us to use inline scripts... `There is no mechanism for relaxing the restriction against executing inline JavaScript.` – Derek 朕會功夫 Sep 18 '12 at 19:49
  • @Derek you are right for inline scripts, but I thought I would update my answer with the upcoming news, I just edited to be more precise, thanks! – Stefano Sep 19 '12 at 08:58
  • 1
    This answer no longer applies, see my comment with the updated status [here](http://stackoverflow.com/a/38554505/1698058). – Chris Hunt Jul 24 '16 at 22:12
  • @Stefano I am using the Loklak API in a chrome extension which is served over http only. I am facing the same error `Content Security Policy`. Could you please let me how to go around this? API sample link: loklak.org/api/search.json?q=from%3Atwitter The entire error is: `Refused to load the script 'http://loklak.org/api/search.json?callback=loklakFetcher.handleData&q=muanchiou&count=25&source=cache&fields=&limit=&timezoneOffset=60&minified=true' because it violates the following Content Security Policy directive: "script-src 'self'".` – Aditya C Dec 17 '16 at 21:35
  • Simply skimming over this I cringed reading "iframe" and "eval". Mentors in this field have stress to me Eval == Evil. The purpose of a content policy is to secure your app, but not while opening up other vulnerabilities. – Jed Lynch Jan 27 '17 at 14:45
  • @AdityaC please check Chris-Hunt answer, and if that's not enough ask a new question - comments are only suited for... short comments :-) – Stefano Jan 31 '17 at 10:54
  • @JedLynch not sure I see what point you are making in your comment. There are licit cases for using eval, and in particular with older browsers that was the only way to get dynamic library loading. As long as the developer knows what he's doing, and he's not eval-ing user code, there's no issue. You are welcome to ask a new question on when eval should or should not be used! – Stefano Jan 31 '17 at 10:57
3

Hash usage for inline scripts is permitted in Content Security Policy Level 2. From the example in the spec:

Content-Security-Policy: script-src 'sha512-YWIzOWNiNzJjNDRlYzc4MTgwMDhmZDlkOWI0NTAyMjgyY2MyMWJlMWUyNjc1ODJlYWJhNjU5MGU4NmZmNGU3OAo='

An alternative is the nonce, again from the examples:

Content-Security-Policy: script-src 'self' 'nonce-$RANDOM';

then

<script nonce="$RANDOM">...</script>
<script nonce="$RANDOM" src='save-because-nonce'></script>

These appears supported in Chrome 40+, but I am uncertain what luck one would have with other browsers at the moment.

Brian M. Hunt
  • 81,008
  • 74
  • 230
  • 343
  • 1
    But does that actually work in Chrome **extensions**? Can you add an example? Because [the docs are quite clear](https://developer.chrome.com/extensions/contentSecurityPolicy#relaxing-inline-script): _"There is no mechanism for relaxing the restriction against executing inline JavaScript."_ – Xan Jun 22 '15 at 07:21
  • @Xan You will have to experiment to be sure; the docs may be outdated as they appear to refer only to CSP Level 1. In any case, if there ever will be a way to circumvent the prohibition on inline scripts, it will almost certainly be with either a hash or [nonce](http://www.w3.org/TR/CSP/#script-src-the-nonce-attribute) from CSP Level 2. – Brian M. Hunt Jun 22 '15 at 11:24
  • Extensions do not blindly implement the spec; only some things are allowed. So I highly doubt your observation is relevant, unless there's a sample to show otherwise. – Xan Jun 22 '15 at 11:37
  • @Xan: The question as it currently reads and appears in search results is not limited to extensions. Perhaps this may be useful information for others who come across this topic. – Brian M. Hunt Jun 22 '15 at 12:39
  • Then the title needs updating. You can see from the question it is specifically about a change in Chrome Extensions that added a content security policy setting to the manifest and tightened the default. – Xan Jun 22 '15 at 13:01
  • Nonce isn't working for Chrome Ext CSP for me either. – Pacerier Aug 12 '17 at 14:15
2

Afaik, this is a bug.

"default-src 'self' https://ssl.google-analytics.com"

works, while

"default-src 'self' http://ssl.google-analytics.com"

doesnt.

It's really bleeding edge technology, check http://code.google.com/p/chromium/issues/detail?id=105796 for details.

Update: http://code.google.com/p/chromium/issues/detail?id=107538 refers to this issue.

Maximilian Hils
  • 6,309
  • 3
  • 27
  • 46
  • 7
    Turns out this is not a bug, but a new security measure. Confirmed from feedback in the second link. Other helpful info/examples in there, too. – Chris McFarland Dec 14 '11 at 20:35