#17419: Add a JSON template filter
-----------------------------------+-------------------------------------
Reporter: lau | Owner: aaugustin
Type: New feature | Status: new
Component: Template system | Version: master
Severity: Normal | Resolution:
Keywords: json template tag | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+-------------------------------------
Changes (by amacneil):
* cc: adrian.macneil@… (added)
Comment:
I think it's very important to add some form of `json` filter. Right now
there is a situation where developers are putting the output of
`json.dumps()` directly into templates and marking it as safe, which
causes an instant XSS vulnerability (I have seen this in the wild, hence
why I came across this ticket). This is a fairly complicated security
issue, so Django should make it easy for users to do the right thing.
To summarize previous comments, there are two different contexts where you
may wish to use JSON in templates:
1. Inside HTML (e.g. `data-*` attributes)
2. Inside CDATA (e.g. `var foo = {};` inside a `<script>` tag)
The problem here is that each context requires different escaping rules,
and there is no easy way for Django to tell which context you are in.
To escape for context 1 (inside html attributes) is actually fairly easy -
run `json.dumps()` on the object, then apply regular html escaping:
{{{
foo = {'foo': 'bar'}
json_str = json.dumps(foo)
}}}
{{{
<div data-foo="{{ json_str }}">
}}}
{{{
<div data-foo="{"foo":"bar"}">
}}}
To escape for context 2 (inside script tags), the problem is that HTML
escaping will produce invalid javascript:
{{{
<script>
var foo = {{ json_str }};
</script>
}}}
{{{
<script>
var foo = {"foo":"bar"}; // causes JS parse error
</script>
}}}
Currently people are solving this by marking the JSON string as safe,
incorrectly assuming that the output is safe inside a script tag. However,
as has been mentioned above, some valid JSON has special meaning inside
script tags:
{{{
foo = {'foo': 'bar </script> attack'}
json_str = json.dumps(foo)
}}}
{{{
<script>
var foo = {{ json_str|safe }};
</script>
}}}
{{{
<script>
var foo = {"foo":"bar </script> attack"};
</script>
}}}
The problem here is that HTML has no knowledge of parsing JS, so treats
the first `</script>` as the end of the script tag, and allows the
attacker to insert arbitrary HTML or JS afterwards.
This can be solved simply and effectively by replacing unsafe HTML
characters with unicode escape sequences. The characters which should be
replaced are `&`, `<`, `>`, `\u2028`, and `\u2029` (the last two are
treated as newlines by some javascript engines, which may allow an
attacker to begin a new javascript instruction - I haven't seen them
mentioned in this thread yet). These 5 characters are also replaced by the
[http://api.rubyonrails.org/classes/ERB/Util.html#method-c-json_escape
json_escape] filter in Rails, which has a similar purpose. Safe output
will look like this:
{{{
<script>
var foo = {"foo":"bar \u003c/script\u003e attack"};
</script>
}}}
Replacing these characters is easy to do, and valid in both contexts (html
and script tags), so they should always be replaced by any Django `json`
filter. I have created a simple implementation of a json filter which
replaces these characters here:
https://gist.github.com/amacneil/5af7cd0e934f5465b695
The last remaining issue is whether the output of this filter should be
marked as safe. The alternatives are:
* Automatically mark `json` filter output as safe. It will work as
expected inside script tags, but any use inside html attributes will
result in invalid HTML and potential XSS attacks.
* Do not mark output as safe. JSON will be correctly escaped inside html
attributes, and use inside script tags will result in malformed JS. Safe
use inside script tags can be achieved by using `{{ data|json|safe }}`.
Developers must be made aware that the `safe` filter should ONLY be used
when in the context of a script tag, and not for general HTML.
Neither of these is a great option, because it relies on the developer
knowing/remembering that output is safe in one context, but unsafe in
another. However, of the two I would probably prefer the second option
(JSON output is unsafe by default, and must be manually marked as safe for
use inside script tags), only because it is more safe by default, and
requires explicit thought to force the output as safe, which should ring
alarm bells and cause extra caution.
A final alternative may be to create another filter, named something like
`scriptjson`, which has the same effect as `json`, but which marks the
output as safe. Documentation could state that this filter should ONLY be
used inside `<script>` tags, and that the regular `json` filter should be
used everywhere else. Functionally this would be identical to using
`json|safe`, but it is more semantically obvious what the author is trying
to do, and it would be easier to document correct usage.
--
Ticket URL: <https://code.djangoproject.com/ticket/17419#comment:27>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/061.2c20812d848dcee5e4d55b1b446b97f2%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.