Proposal to add a secure JSON encoding template tag

417 views
Skip to first unread message

David Evans

unread,
May 13, 2014, 6:03:43 AM5/13/14
to django-d...@googlegroups.com
There was some discussion previously (see https://code.djangoproject.com/ticket/17419) of adding a JSON encoding filter to Django. This was rejected as being impossible (or very difficult) to do securely. However the requirement to embed JSON in an HTML page is quite a common one, and it's easy to get wrong and create XSS vulnerabilities. We should make it easy for people to do the right thing.

I propose a ``json`` tag (implementation here) which outputs the entire script element as well as the JSON data. By enforcing the context in which in the JSON is output, it's possible to escape it securely.

It would have two basic modes of operation. The first, and recommended, one would look like this:

{% json data id="initial-data" %}

and would produce HTML like this:

<script type="application/json" id="initial-data">
  {"foo": "bar"}
</script>

The resulting data would be accessed in JavaScript like this:

var el = document.getElementById('initial-data');
var initialData = JSON.parse(el.textContent || el.innerText);

This is compatible with a strict Content Security Policy which prohibits all in-page script execution and maintains a clean separation between passive data and executable code.

The second mode of operation would look like this:

{% json data var="initialData" %}

and would produce HTML like this:

<script type="application/javascript">
  var initialData = {"foo": "bar"};
</script>

This isn't compatible with strict CSP but it is perhaps simpler and more familiar to many developers, and not fundamentally insecure, so it should still be supported.

Of course, the key issue is whether this can be done securely. In the gist below is a proposed implementation with links to the sources I've used to ensure I'm escaping things correctly: 

If people are happy with it then I can create a proper pull request with docs etc.

Thanks,

Dave

Michael Mior

unread,
May 14, 2014, 9:57:06 AM5/14/14
to django-d...@googlegroups.com
I've used django-jsonify (https://pypi.python.org/pypi/django-jsonify/) in the the past for this successfully. I'm not certain of the security of the code since unfortunately I didn't have the time to do a proper audit, but it seemed to handle some common cases.

--
Michael Mior

David Evans

unread,
May 15, 2014, 5:58:29 AM5/15/14
to django-d...@googlegroups.com
Thanks. This is a good example of the problem actually as that library is vulnerable to XSS. If someone can get the string "]]></script>" into the JSON they break out of the CDATA block and the script element and can then execute arbitrary code.

Chris Beaven

unread,
May 24, 2014, 1:37:42 AM5/24/14
to django-d...@googlegroups.com
I like the idea, I've been using a custom script that does the first mode of this tag nearly exactly the same way (with the same security escaping).

Not the biggest fan of the second mode of operation since like you say, it's not compatible with strict CSP. Why not just encourage people to do it the correct way? Having a separate mode of operation for the same tag necessary, perhaps it'd be less controversial just getting the first mode in?

A related side note, it's good practice for JS templates to live within script blocks too. I have {% scriptblock %}{% endscriptblock %} that is sits next to {% jsonblock %} in my custom library since it uses the same escaping methods.

David Evans

unread,
Jun 10, 2014, 1:31:29 PM6/10/14
to django-d...@googlegroups.com
Thanks Chris. Yes, I do share your feeling that the declare-a-global-var way of doing things is bit icky. My reason for wanting to support it was that in order to use the "proper" method you need to be able to parse JSON and that means that if you want to support older browsers you've now got a dependency on some external JSON parsing library.

Also, although I called them different "modes" of operation, they're really just minor syntactic variants from the point of view of the template tag so it seemed a bit of shame to force the user down one route when the other could be so easily supported. I didn't want a situation where people ended up using another, insecure method because the official method forced them to do things in a way that seemed too complicated.

That said, I'd be very open to just supporting the CSP-compatible method if that was the general view.


--
You received this message because you are subscribed to a topic in the Google Groups "Django developers" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/RNMs5YbKeRY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/1d3f399a-dbc3-4f2f-b9e3-01c370384573%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Robert Grant

unread,
Aug 11, 2014, 8:18:22 AM8/11/14
to django-d...@googlegroups.com
Could you have a hybrid that took this:

          {% json data id="initial-data" var="variable_name" %}

And did this:


<script type="application/json" id="initial-data">
  {"foo": "bar"}
</script>
          <script type="application/javascript">
  var totally_random_uuid = document.getElementById('initial-data');
  var variable_name = JSON.parse(totally_random_uuid.textContent || totally_random_uuid.innerText);
     </script>

Maybe omitting the second script block if no var is supplied, and generating a random UUID for the id for the data block if no id is supplied?
Reply all
Reply to author
Forward
0 new messages