Hey,
I'd like to discuss how Django might encourage and ease the use of CSP in
the frontend. As Django is used more and more to drive complex web
applications, I think this is of interest. (I tried to keep this short, but
failed...).
# CSP background
CSP (Content Security Policy) is an elaborate mechanism which allows to
attach a policy to a webpage, restricting the things it can do and the
origins it can interact with. It is mainly meant to aid in the prevention
of various code injection attacks. It is usually sent in the
`Content-Security-Policy` header in the response to the HTML request.
Please see here for more details:
https://developer.mozilla.org/en-US/docs/Web/HTTP/CSPCurrently, in order to have a secure policy you must write big
white-lists, which does not lend itself well to automation by a framework.
For example, here is the policy GitHub sends, which is relatively tame as
far as these go:
default-src 'none';
base-uri 'self';
child-src
render.githubusercontent.com;
connect-src 'self'
uploads.github.com status.github.com collector.githubapp.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-user-asset-79cafe.s3.amazonaws.com wss://
live.github.com;
font-src
assets-cdn.github.com;
form-action 'self'
github.com gist.github.com; frame-ancestors 'none';
img-src 'self' data:
assets-cdn.github.com identicons.github.com collector.githubapp.com github-cloud.s3.amazonaws.com *.
githubusercontent.com;
media-src 'none';
script-src
assets-cdn.github.com;
style-src 'unsafe-inline'
assets-cdn.github.comThe django-csp project (
https://github.com/mozilla/django-csp) helps with
generating these in Django. However, maintaining, deploying and monitoring
it is quite a major effort, depending on the complexity of the site.
# The strict-dynamic directive
Because of this, the upcoming version of the CSP standard (Level 3,
https://www.w3.org/TR/CSP/) includes a new `script-src` directive called
`strict-dynamic`. It has its own website:
https://csp.withgoogle.com/docs/index.htmlThe idea here is that you send a policy like this:
script-src 'nonce-{random}' 'strict-dynamic';
where `{random}` is some token that is randomly generated in each request,
e.g. `hM6RAC4rgxYFk01gbbfcKw==`. Next, each script included in the page
must be decorated with a `nonce` attribute, using the nonce sent in the
header:
<script nonce="hM6RAC4rgxYFk01gbbfcKw==" src="/path/to/script.js"></script>
<script nonce="hM6RAC4rgxYFk01gbbfcKw==">foo()</script>
What the `strict-dynamic` means, is that any script trusted with the nonce
is transitively trusted to load its own scripts dynamically. Thus, most of
the XSS protection remains, however the maintenance burden drops
significantly, and it becomes much more amenable to automation in a
framework.
# Browser support and backward compatibility
`strict-dynamic` is already supported in recent versions of Firefox and
Chrome, and is "under consideration" by Edge:
https://blogs.windows.com/msedgedev/2017/01/10/edge-csp-2/The CSP Level 3 spec is still a draft, but from what I could gather they
are aiming to finalize it in "Q3 2017":
https://rawgit.com/w3c/webappsec/master/admin/webappsec-charter-2017.htmlWhat about browsers which does not support `strict-dynamic`, but only
support older CSP versions? With the policy I gave above, everything will
be blocked (if nonces are not supported), or transitive scripts will be
blocked (if nonces are supported). To workaround this, `strict-dynamic`
has the behavior of disabling/ignoring other directives if it is
understood. So we can send a policy like this instead:
script-src 'nonce-{random}' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https: http:;
In browsers that support CSP3, it will be interpreted as
script-src 'nonce-{random}' 'strict-dynamic' 'unsafe-eval'
In browsers that support CSP2:
script-src 'nonce-{random}' 'unsafe-eval' https: http:;
In browsers that support CSP1:
script-src 'unsafe-inline' 'unsafe-eval' https: http:;
In all cases it should work out.
Note however, that the following things cannot be used and must be
replaced by modern equivalents:
- `javascript:` URIs.
- Inline event handlers, e.g. `onclick="foo()"`.
For real-world usage, according to
https://security.googleblog.com/2016/09/reshaping-web-defenses-with-strict.htmlGoogle are already using `strict-dynamic` in several of their
applications, e.g. see the header sent by
https://photos.google.com/.
# Interaction with existing white-list based policies
White-list based policies generated by e.g. django-csp are still useful,
as they provide a different range of protections.
It is possible to send multiple policies, in which case they are all
enforced. Therefore, `strict-dynamic` can be treated as just an additive
measure.
Since Django does not support sending multiple headers with the same key,
it is possible instead to ', '.join() them, as is standard in HTTP.
# Integration with Django
I wrote a Django app as a POC which adds such a policy automatically. It
is very trivial and consists of:
- A `csp_nonce` template tag, to be used like this:
<script {% csp_nonce %} src="{% static 'path/to/script.js' %}"></script>
<script {% csp_nonce %}>foo()</script>
- A middleware which generates a fresh nonce on each request and injects
the header.
This worked great in my testing with a complex web app, involving multiple
3rd party scripts (e.g. recaptcha, analytics, etc.). (I should note I did
not deploy it, though).
The only problem I encountered was with django-debug-toolbar, which
injects its own javascript, but of course without the nonce. Hence, their
JS is blocked. In this sense, it would be nice if this were integrated in
Django's SecurityMiddleware, so that Django apps can add `{% csp_token %}`
freely. And of course, be more visible to Django developers.
Thanks,
Ran