Add support for serializing top-level arrays to JSON
Fix #170, #248, #510, #673, #1177
This commit is contained in:
parent
f17e6061fc
commit
daceb3e3a0
5 changed files with 114 additions and 115 deletions
|
|
@ -95,81 +95,12 @@ the form validation framework, which does not exist in Flask.
|
|||
JSON Security
|
||||
-------------
|
||||
|
||||
.. admonition:: ECMAScript 5 Changes
|
||||
In Flask 0.10 and lower, :func:`~flask.jsonify` did not serialize top-level
|
||||
arrays to JSON. This was because of a security vulnerability in ECMAScript 4.
|
||||
|
||||
Starting with ECMAScript 5 the behavior of literals changed. Now they
|
||||
are not constructed with the constructor of ``Array`` and others, but
|
||||
with the builtin constructor of ``Array`` which closes this particular
|
||||
attack vector.
|
||||
|
||||
JSON itself is a high-level serialization format, so there is barely
|
||||
anything that could cause security problems, right? You can't declare
|
||||
recursive structures that could cause problems and the only thing that
|
||||
could possibly break are very large responses that can cause some kind of
|
||||
denial of service at the receiver's side.
|
||||
|
||||
However there is a catch. Due to how browsers work the CSRF issue comes
|
||||
up with JSON unfortunately. Fortunately there is also a weird part of the
|
||||
JavaScript specification that can be used to solve that problem easily and
|
||||
Flask is kinda doing that for you by preventing you from doing dangerous
|
||||
stuff. Unfortunately that protection is only there for
|
||||
:func:`~flask.jsonify` so you are still at risk when using other ways to
|
||||
generate JSON.
|
||||
|
||||
So what is the issue and how to avoid it? The problem are arrays at
|
||||
top-level in JSON. Imagine you send the following data out in a JSON
|
||||
request. Say that's exporting the names and email addresses of all your
|
||||
friends for a part of the user interface that is written in JavaScript.
|
||||
Not very uncommon:
|
||||
|
||||
.. sourcecode:: javascript
|
||||
|
||||
[
|
||||
{"username": "admin",
|
||||
"email": "admin@localhost"}
|
||||
]
|
||||
|
||||
And it is doing that of course only as long as you are logged in and only
|
||||
for you. And it is doing that for all ``GET`` requests to a certain URL,
|
||||
say the URL for that request is
|
||||
``http://example.com/api/get_friends.json``.
|
||||
|
||||
So now what happens if a clever hacker is embedding this to his website
|
||||
and social engineers a victim to visiting his site:
|
||||
|
||||
.. sourcecode:: html
|
||||
|
||||
<script type=text/javascript>
|
||||
var captured = [];
|
||||
var oldArray = Array;
|
||||
function Array() {
|
||||
var obj = this, id = 0, capture = function(value) {
|
||||
obj.__defineSetter__(id++, capture);
|
||||
if (value)
|
||||
captured.push(value);
|
||||
};
|
||||
capture();
|
||||
}
|
||||
</script>
|
||||
<script type=text/javascript
|
||||
src=http://example.com/api/get_friends.json></script>
|
||||
<script type=text/javascript>
|
||||
Array = oldArray;
|
||||
// now we have all the data in the captured array.
|
||||
</script>
|
||||
|
||||
If you know a bit of JavaScript internals you might know that it's
|
||||
possible to patch constructors and register callbacks for setters. An
|
||||
attacker can use this (like above) to get all the data you exported in
|
||||
your JSON file. The browser will totally ignore the :mimetype:`application/json`
|
||||
mimetype if :mimetype:`text/javascript` is defined as content type in the script
|
||||
tag and evaluate that as JavaScript. Because top-level array elements are
|
||||
allowed (albeit useless) and we hooked in our own constructor, after that
|
||||
page loaded the data from the JSON response is in the `captured` array.
|
||||
|
||||
Because it is a syntax error in JavaScript to have an object literal
|
||||
(``{...}``) toplevel an attacker could not just do a request to an
|
||||
external URL with the script tag to load up the data. So what Flask does
|
||||
is to only allow objects as toplevel elements when using
|
||||
:func:`~flask.jsonify`. Make sure to do the same when using an ordinary
|
||||
JSON generate function.
|
||||
ECMAScript 5 closed this vulnerability, so only extremely old browsers are
|
||||
still vulnerable. All of these browsers have `other more serious
|
||||
vulnerabilities
|
||||
<https://github.com/mitsuhiko/flask/issues/248#issuecomment-59934857>`_, so
|
||||
this behavior was changed and :func:`~flask.jsonify` now supports serializing
|
||||
arrays.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue