Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document CSP Threat Model #1

Closed
eholk opened this issue Nov 7, 2017 · 12 comments
Closed

Document CSP Threat Model #1

eholk opened this issue Nov 7, 2017 · 12 comments

Comments

@eholk
Copy link
Contributor

eholk commented Nov 7, 2017

The proposal should specify what threats we are trying to protect against with CSP and how CSP mitigates these threats.

At the recent CG meeting, we had some confusion about what CSP is used for. The primary use seems to be to give developers control over what code runs as part of their application. However, it seemed like there were also efforts to use CSP to limit opportunities for heap spray attacks by restricting an attacker's ability to generate code. We should clarify in the proposal which threats our in scope.

See WebAssembly/design#1510 for the related action item that came out of the meeting.

Let's use this issue to discuss what we should do.

@mikesamuel
Copy link

mikesamuel commented Dec 1, 2017

Here're my 2c on how CSP relates to WebAssembly.{compile,instantiate}.

Problem

WebAssembly future goals include

In the future 🦄, WebAssembly may also be loaded and run
directly from an HTML <script type='module'> tag—and any
other Web API that loads ES6 modules via URL—as part of
ES6 Module integration.

These are trivially addressed by extending CSP source lists.

Neither WebAssembly.compile nor WebAssembly.instantiate are, but we could

  • Treat one as an eval operator for the purposes of unsafe-eval,
  • Add another CSP knob to attenuate them: unsafe-wasm for lack of a better term,
  • Use a secure design pattern like trademarking to gate the byte source passed to compile and/or the imports object passed to instantiate.
  • Do not attenuate either and leave it up to developers to vet the arguments to each in their code.

Definitions:

eval-operators: APIs like eval(str), Function(str), setTimeout(str, dt), etc.
that convert strings to code, and assignments like (location = str) when
str starts with "javascript:" that indirectly invoke the JavaScript parser
on a derivative of the string.

explicit call to foo.bar: When foo.bar(...) appears directly in code.

implicit call to foo.bar: When the API (foo.bar) is referenced indirectly via
commonly used reflective operators as in

      var [x,y] = ['foo','bar'];  // attacker controlled
      this[x[][y](str)

Some eval operators can be implicitly accessed thus

      var o = {};
      var [x,y] = ["__proto__","constructor"];
      o[x][y][x][y](str);  // implicit call to Function(str)

Security assumptions

A Content-Security-Policy that does not allow unsafe-eval implies some security assumptions:

  • Developers at large do not reliably vet string inputs to eval operators.
  • The smaller group of developers whose code generates the Content-Security-Policy
    header can be trusted to pick the code that may load into the origin.

Our first security premise is not a criticism of developers.
It specifies a default policy because code development is a distributed reasoning
problem involving application developers, library developers and open-source
framework developers who have limited access to each others' security assumptions
and requirements.

In our second security premise we trust a small group of specialists to oversee
the large, distributed group that produced the code.
Currently that oversight doesn't extend to allowing some uses of eval operators
but not others.

The case for not attenuating

Below is a distillation of several people's attempt to come up with an argument for
the "do not attenuate" option that I was asked to evaluate.

WebAssembly.compile should not be considered "unsafe" by the criteria by that
CSP should be using to judge "eval" or "Function" unsafe. The thing that makes
the latter unsafe is not that they evaluate code in a Turing universal language.
Turing universality merely enables the evaluated code to compute any
computable function, which does imply the unsafety of non-termination but
nothing else. Rather, they are unsafe because of what the evaluated code has
access to: the outside world, such as the user and the network.

By contrast, WebAssembly.compile is confined. The evaluated code is not given
any abilities to cause any effects outside itself other than by explicit imports.
Thus, we don't need to distinguish intended uses of WebAssembly.compile from
unintended uses, as both are safe. More precisely, the unintended uses are
unsafe only in the sense that unintended uses of other safe things, like Math.sqrt,
is unsafe.

Response

It's true that WebAssembly.compile does not have any side-effects, assuming the
WebAssembly compiler is bug free. Others disagree on whether
preventing denial-of-service is within the scope for CSP, but I'm assuming it's
a non-goal for the sake of argument.

We still ought not trust developers to vet arguments to WebAssembly.compile.
If, per our security premises above, we don't trust developers to vet strings to eval
operators, including URLs that reach HTMLAElement.prototype.href, then
we ought not trust them to vet URLs that back ByteSources or strings that are
text decoded to ArrayBuffers.

We also ought not trust them to vet importsObj arguments passed to
WebAssembly.instantiate. It sounds like functions reachable from
importsObj can only be [Call]ed with numeric arguments and with
undefined [thisValue], but this is likely to be relaxed.
Passing the global object as an importsObj allows power.

"Turing universality" is security relevant.
If an exploit is possible by controlling the composition of two named functions,
F and G, then the probability of exploit is ≤ P(controls(F) ∧ controls(G)).
In other words, I might need to cause the equivalent of G(F(payload)).
I might jump through hoops to figure out how to confuse the code into
routing an output of a call to F that I control to a variable that is later used as input to
G by trying to control a bunch of boolean conditions in if statements.
An attacker may not succeed or may not bother if the nature of the code makes
this non-trivial.
If I have Turing universal access to F and G, then that composition is trivial.
A language for composing attacks is a tool that makes feasible some
previously infeasible exploits.

"Explicit imports" is also not a concept that we can rely on.
In unstrict code, controlling two strings lets me get to WebAssembly.compile, in code like

    [x,y]=["WebAssembly","compile"]     // Attacker caused
    this[x][y]  // Naive code

and the global object can be similarly accessed when [x,y]=["self","self"].

It's unlikely that any attacker in any given app will find a way to call WebAssembly.compile indirectly on a source they control, and indirectly WebAssembly.instantiate it with an indirectly accessed global object.
Framework convenience methods might make it easier though.
My point is that we should be careful before we depend on concepts like
"explicit callee" or "explicit argument" given the prevalence of reflective
operators like obj[k] and type reflection like obj.prototype and obj.constructor in real code.


We should not choose the "do not attenuate" option if we don't trust developers at large to
vet byte sources or importsObjs.


That doesn't mean that WebAssembly JavaScript APIs need to be incompatible with a strict Content-Security-Policy in the long term.

Shameless plug:
In our second security premise, we find a small trusted group, the Content-Security-Policy
header authors, that exercises oversight over the large distributed group of code authors.
I can't publish yet, but I'm working on a writeup to allow that small group of overseers to
whitelist some uses of eval operators that should extend to WebAssembly API calls.

@eholk
Copy link
Contributor Author

eholk commented Dec 5, 2017

Thanks for the clear explanation! This helps me understand the underlying issues a lot better.

Would you say it's sufficient to vet either the wasm bytes (such as by verifying they have a certain hash or came from a certain URL?) or the import object? Vetting the wasm bytes seems to put us in a better position than running a vetted JavaScript script because Wasm is further limited by what capabilities are included in the import object.

@mikesamuel
Copy link

Would you say it's sufficient to vet either the wasm bytes
(such as by verifying they have a certain hash or came from a certain URL?) or the import object?

Yes. I don't think you need to vet both.

Vetting the hash or the URL is more consistent with how CSP works now, and presumably with how <script type="module"> will eventually interact with CSP.

But vetting the bytes might make it hard to compose a compilation pipeline using WebAssemblies. I forget who, but someone pointed out that streaming byte sources might make it possible to have a WebAssembly transform a byte stream that feeds WebAssembly.compile, e.g. to implement a decompression algo not supported natively by the browser.

@eholk
Copy link
Contributor Author

eholk commented Dec 5, 2017

someone pointed out that streaming byte sources might make it possible to have a WebAssembly transform a byte stream that feeds WebAssembly.compile

We realized at the last CG meeting that one way to work around this is by using a hash script source. Basically, the site owner knows what the module should decompress too, so they can send the correct hash and we have to make sure the hash matches the hash of the raw bytes we're given. So this will still allow custom decompression algorithms, although it seems to impose a heavy cost of the developers to make sure to keep their hashes up to date.

@jfbastien
Copy link
Member

We realized at the last CG meeting that one way to work around this is by using a hash script source. Basically, the site owner knows what the module should decompress too, so they can send the correct hash and we have to make sure the hash matches the hash of the raw bytes we're given. So this will still allow custom decompression algorithms, although it seems to impose a heavy cost of the developers to make sure to keep their hashes up to date.

Are we talking CSP or SRI? I know we discussed both at the in-person CG meeting. You mean that you authenticate the post-modified .wasm, without authenticating the original .wasm nor the script modifying it? Seems like having SRI on original .wasm and modifying script would be better here, and that's complementary to CSP.

@erights
Copy link

erights commented Dec 5, 2017

(attn @wycats )

@mikesamuel
Copy link

@jfbastien Sorry if this is a dumb question, but I wasn't at that meeting. By "SRI," are you referring to

  • <script integrity=...> style attributes
  • CSP's require-sri-for verb
  • a way to thread SRI hashes through to WebAssembly.compile
  • something else?

IIUC, SRI is designed to address MITM and other compromised endpoint problems which I suppose would allow controlling which binaries load.

@jfbastien
Copy link
Member

@mikesamuel I believe all of these were included in the discussion, but we didn't get much of a conclusion. My point is mainly that CSP and SRI are complementary, and interact sometimes, so we shouldn't design a new checksum thing for WebAssembly but should rather follow what's already in the Web platform.

I think we should only invent if what already exists is clearly insufficient in a manner that's fundamentally required for WebAssembly's success. In particular, IIRC we've talked about not trying particularly hard to make the sync and async APIs powerful, in favor of putting effort towards the streaming API. If the streaming API is what we'd rather have people use then giving it the fancy features is a better way to spend our time. Only usecases which sync / async can meet, but fetch can't, should warrant more work on sync / async.

@eholk
Copy link
Contributor Author

eholk commented Dec 5, 2017 via email

@jfbastien
Copy link
Member

For the hash stuff, CSP already allows you to specify a hash or a nonce as
a script-src. As far as I know we should be able to use the same thing for
compiling Wasm bytes out of an ArrayBuffer.

From @mikesamuel's list that's number 2, right? Or is that number 3?

And is it desirable to forbid number 1?

@eholk
Copy link
Contributor Author

eholk commented Dec 6, 2017

Here's my understanding of first four-item list, just to make sure we're all on the same page:

  1. WebAssembly.compile, etc. are treated as eval() and thus only allowed when script-src contains 'unsafe-eval'.
  2. Add 'unsafe-wasm' (or 'wasm-eval', but we can bikeshed the name), which allows WebAssembly.compile without allowing eval().
  3. Restrict WebAssembly.compile, etc. to a specified set of sources for the Wasm bytes. This would include all of the mechanisms allowed for script-src, such as specifying by origin, hash, or nonce.
  4. Allow WebAssembly.compile, etc. without being restricted by CSP.

Assuming that's correct, the hashes and nonces are part of number 3.

Number 1 is the current state of the world for Chrome. We allow WebAssembly.compile and related functions precisely when JS eval() is allowed.

Chrome uses number 2 for Chrome apps and extensions only, but I think we should make 'wasm-eval' a proper standard.

@mikesamuel is arguing that we should not do number 4, and I think he has a fairly compelling argument.

As far as whether it's desirable to forbid number 1, I'm going to interpret "forbidding number 1" as "'unsafe-eval' should not enable WebAssembly on its own." I would have a slight preference for not forbidding number 1, that is, "'unsafe-eval' should enable WebAssembly" because this is the current behavior for Chrome (and I think WebKit too).

Note that these issues only apply if a CSP is given. When no policy is specified, then WebAssembly is allowed without restriction.

@eholk
Copy link
Contributor Author

eholk commented Mar 2, 2018

I'm closing this since WebAssembly/meetings#6 is merged now.

@eholk eholk closed this as completed Mar 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants