2

I'm aware that there is a Cross site forgery attack that can be performed on a request that returns an array by overloading the Array constructor. For example, suppose I have a site with a URL:

foo.com/getJson

that returns:

['Puff the Dragon', 'Credit Card #'] 

This would normally be Javascript eval'd by my own site after an XHR request, but another site can sniff this data by including something like:

<script>
function Array() {
  var arr = this;
  var i = 0;
  var next = function(val) {
    arr[i++] setter = next;
    document.write(val);
  };
  this[i++] setter = next;
}
</script>
<script src="http://foo.com/getJson"></script>

My question is, can the same thing be done when the request returns a Javascript object? i.e.

{ name: 'Puff the Dragon', cc: 'Credit Card #' }

I couldn't figure out a way to do this, but maybe I'm missing something. I know there are better solutions to protect my site, like using the while(1) hack or requiring an auth token in the URL, but I'm trying to figure out if this sort of security hole exists.

Community
  • 1
  • 1
nas
  • 937
  • 1
  • 8
  • 21
  • Never eval your json. It's risky. Use a json parser such as json2.js – spender Jul 03 '10 at 03:26
  • 1
    I agree that unless I have trusted content in my JSON, I shouldn't eval it without using a parser. That wasn't my question though. – nas Jul 03 '10 at 03:29
  • ...and that wasn't my answer ;) – spender Jul 03 '10 at 03:31
  • @spender: json2.js does try to sanitize inputs to .parse(), but uses eval() in the end. – Dave Ward Jul 03 '10 at 03:59
  • Modern browsers have efficient native JSON parsers that support the same interface as json2.js. This is specified by ECMAScript 5. – Matthew Flaschen Jul 03 '10 at 04:02
  • I'm really confused about the "eval is evil" mantra. I swear most people repeat this without thinking about it. If you created `foo.com/getJson` and all content therein is under your control and contains no third-party content, what possible harm is there in using `eval()` on it. Parsing what you already know to be safe is pointless and wasteful. If you trust the data, `eval()` can be used. If you don't trust the data, it can't. – Andrew Jul 03 '10 at 04:52
  • Actually, for browsers with native JSON, JSON.parse is usually faster than eval (http://www.vinylfox.com/json-decoding-speed-comparison/). So it seems it's eval that's pointless and wasteful, even ignoring security. – Matthew Flaschen Jul 03 '10 at 05:06
  • And what about browsers without native JSON right now? More than half the page views my company gets is from browsers that don't support native JSON parsing. Again, my point is, the OP's use of `eval()` in this case poses absolutely no risk because there is no taint. – Andrew Jul 03 '10 at 05:19
  • 1
    @Dave Ward: json2.js checks for a native implementation before falling back on 'eval'. – Stefan Kendall Jul 03 '10 at 05:28
  • @Stefan: That's true, but if every browser had a native JSON.parse implementation then we wouldn't be talking about json2.js in the first place. Suggesting json2.js itself as a way to avoid eval() is misleading. – Dave Ward Jul 03 '10 at 06:28
  • @Dave Ward: Sure, but it's the best you can do easily. I think it's an acceptable risk to allow IE6/7/8 to use unsafe evaluations and leave the good browsers secure. – Stefan Kendall Jul 04 '10 at 02:01
  • @Stefan: I agree. I've suggested using json2.js for a while now: http://encosia.com/2009/07/07/improving-jquery-json-performance-and-security/ and http://encosia.com/2009/04/07/using-complex-types-to-make-calling-services-less-complex/ for example. I'm just trying to point out that json2.js is *not* the magic eval() alternative that people often suggest. It's still using eval() for the majority of web traffic today. – Dave Ward Jul 04 '10 at 02:28
  • If you are 100% confident that the content you are eval'ing is safe (you generated it entirely yourself, or you've already escaped any user generated content on the server), eval is fine to use. Be careful though! This question was about CSRF, not XSS, but it's nice to see people worried about XSS too :) – nas Jul 04 '10 at 02:33

3 Answers3

2

The sources I've seen, such as Haacked and Hackademix, specifically indicate that root objects are safe (presumably in all major browsers). This is because a script can not start with an object literal. By default, ASP.NET wraps both objects and arrays with a d prefix, but I think this is just to simplify the client library.

Matthew Flaschen
  • 255,933
  • 45
  • 489
  • 528
1

It looks like from the Ecmascript spec, the JSON object shouldn't be treated as a valid Javascript program:

"Note that an ExpressionStatement cannot start with an opening curly brace because that might make it ambiguous with a Block.

So assuming that all browser implement this correctly, a response like { name: 'Puff the Dragon', cc: 'Credit Card #' } won't be executed as valid Javascript. However expressions like ({name: 'Puff the Dragon', cc: 'Credit Card #' }) and {['Puff the Dragon', 'Credit Card #']} will.

nas
  • 937
  • 1
  • 8
  • 21
  • Note that `({name: 'Puff the Dragon', cc: 'Credit Card #' })` is not valid JSON (even if you use double quotes). Parentheses are not allowed. – Matthew Flaschen Jul 03 '10 at 04:11
0

You could use the same technique for Object. It wouldn't affect the prototype chain, so it wouldn't be inherited by all objects. But you could, for example, log all new objects getting created with this:

function Object() {
    var obj = this;
    if (window.objectarray === undefined) {
        window.objectarray = [];
    }
    window.objectarray.push(this);
    return this;
}

Any time code on your page uses new Object(), it would get written to window.objectarray -- even if it were created in a private scope. So, for example, look at this code:

var Account = function() {
    var createToken = function() {
        var objToken = new Object();
        objToken.timestamp = new Date().getTime();
        objToken.securestring = "abc123";
        return objToken.timestamp + objToken.securestring;
    }
    var objPrivate = new Object();
    objPrivate.bankaccount="123-456789";
    objPrivate.token = createToken();
};
var myAccount = new Account();

In this case, if you create a new account with new Account(), a token will be created using private properties (and maybe methods) and nothing about myAccount is left hanging outside in public. But both 'objectToken' and objPrivate will be logged to window.objectarray.

Andrew
  • 12,936
  • 13
  • 52
  • 102
  • This isn't about user-generated executable code or XSS. It's about a script tag on a third-party site using an array constructor to access a logged in user's data. This is CSRF, not XSS. – Matthew Flaschen Jul 03 '10 at 04:55
  • 2
    As to your assertion that the Array constructor won't be invoked for array literals, this may be true in recent browsers, but several older browsers exhibit this problem, like Firefox2, Opera9, and Safari3. http://ejohn.org/blog/re-securing-json/ – nas Jul 03 '10 at 05:10
  • I disagree that this question does not involve XSS, but I'll trim out the parts that reference it. – Andrew Jul 03 '10 at 05:20
  • @nstoertz -- Neat. I can't test for those any more. It still surprises me that object literals don't invoke the Object constructor in these later browsers. – Andrew Jul 03 '10 at 05:23