simonw / datasette

An open source multi-tool for exploring and publishing data
https://datasette.io
Apache License 2.0
9.56k stars 691 forks source link

Consider using CSP to protect against future XSS #1362

Open simonw opened 3 years ago

simonw commented 3 years ago

The XSS in #1360 would have been a lot less damaging if Datasette used CSP to protect against such vulnerabilities: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

simonw commented 3 years ago

The easiest way to apply CSP is to remove all inline <script> blocks (Datasette has a few) and instead serve JavaScript as separate linked files.

It's possible to keep inline script blocks by calculating a hash of their content and adding a Content-Security-Policy: script-src 'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8=' to the policy. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src

This could be achieved with some Django template tricks, but it feels very risky - and done carelessly could end up calculating a hash of a reflected XSS attack!

The biggest challenge I see around here involves plugins and custom templates. Adopting CSP would require plugins to avoid using any inline scripts, instead keeping their entire implementations in .js files.

That's maybe not a bad thing, but it represents a big commitment. It would need to be adopted before Datasette 1.0.

simonw commented 3 years ago

The security benefit of forcing all JavaScript plugins to be written as CSP-friendly external scripts is very compelling though.

Other plugin-heavy ecosystems such as WordPress have suffered greatly from insecurely written plugins - could this be a huge security win for the Datasette ecosystem generally?

simonw commented 3 years ago

I think the best way to answer these questions is with some prototyping - of both Datasette and some of the existing JavaScript plugins.

I can start with a datasette-experimental-csp plugin that sets the header (and could even run an optional report URI mechanism).

simonw commented 3 years ago

Another consideration: testing that this works correctly could require adoption of a real browser test environment (probably Cypress or maybe Playwright) to execute tests that will fail if CSP is violated.

simonw commented 3 years ago

The other option for inline scripts is the CSP nonce:

Content-Security-Policy: script-src 'nonce-2726c7f26c'

Then:

<script nonce="2726c7f26c">
  var inline = 1;
</script>

Since an attacker can't guess what the nonce will be it prevents them from injecting their own script block - this seems easier to make available to plugins than a full hashing mechanism, just make {{ csp_nonce() }} available to the template.

That template function can then be smart enough to set a flag which Datasette uses to decide if the script-src 'nonce-2726c7f26c' policy should be sent or not.

Presumably this would also require adding Content-Security-Policy to the Vary header though, which will have a nasty effect on Cloudflare and Fastly and such like.

simonw commented 3 years ago

The reason Datasette uses small inline scripts right now is to avoid the overhead of an extra HTTP request for a JavaScript file - but these are both inherently cachable and perform much better under HTTP/2 so that's likely a false optimization.

simonw commented 3 years ago

This is from the current base.html template: https://github.com/simonw/datasette/blob/030deb4b25cda842ff7129ab7c18550c44dd8379/datasette/templates/base.html#L62-L66

Which includes this: https://github.com/simonw/datasette/blob/030deb4b25cda842ff7129ab7c18550c44dd8379/datasette/templates/_close_open_menus.html#L1-L16

The body_scripts bit is for this extra_body_script plugin hook, which is the thing that will be the most affected by implementing CSP: https://docs.datasette.io/en/stable/plugin_hooks.html#extra-body-script-template-database-table-columns-view-name-request-datasette

simonw commented 3 years ago

Mind you, since that plugin hook looks like this:

@hookimpl
def extra_body_script():
    return {
        "module": True,
        "script": "console.log('Your JavaScript goes here...')"
    }

Having it calculate a sha256 hash wouldn't be difficult.

dracos commented 3 years ago

Presumably this would also require adding Content-Security-Policy to the Vary header though, which will have a nasty effect on Cloudflare and Fastly and such like.

No, because Vary header is about request headers that cause the response to vary, not response headers.

simonw commented 3 years ago

No, because Vary header is about request headers that cause the response to vary, not response headers.

Hah, of course! Thanks for the correction. So the nonce mechanism would actually be pretty great here, especially for the extra_body_script() hook.

simonw commented 3 years ago

Twitter conversation: https://twitter.com/simonw/status/1401565566045806594

@dracos provided some really useful code examples there:

We generate it here: https://github.com/mysociety/fixmystreet/blob/e9fec4e567e7148ed128816e5770c2963be51af6/perllib/FixMyStreet/Cobrand/Default.pm#L89-L90 And use it e.g. https://github.com/mysociety/fixmystreet/blob/ba6788cd25d8f471a4e3308403607627b4d2f4f6/templates/web/base/common_header_tags.html or https://github.com/mysociety/fixmystreet/blob/cb4f2b96364d151988b5c664888468b25cc62240/templates/web/fixmystreet.com/header/css.html

simonw commented 3 years ago

I guess I can offer a disable_csp setting so that people with complex custom templates aren't completely blocked from using them with Datasette, but maybe it would be better not to offer that? Or to offer it as a datasette-insecure-csp plugin instead?

I like the idea of very actively encouraging CSP across all Datasette projects, but I'm nervous about making the software unusable for certain edge cases.

Maybe require CSP and wait for someone to complain?

simonw commented 2 years ago

Useful example: how Play framework does this https://www.playframework.com/documentation/2.8.1/CspFilter

simonw commented 2 years ago

Asked for tips on Twitter: https://twitter.com/simonw/status/1578561096520114176

simonw commented 2 years ago

This document is useful: https://csp.withgoogle.com/docs/strict-csp.html

simonw commented 2 years ago

Also this series: https://scotthelme.co.uk/tag/csp/ - via https://twitter.com/adamchainz/status/1578762884481368065

simonw commented 2 years ago

And a useful cheat sheet https://scotthelme.co.uk/csp-cheat-sheet/