Back in March/April of this year there was a lot of hub-bub concerning the discovery of a JSON data leak, or sorts. What it boils down to is “JavaScript is incredibly flexible, even to the degree of letting you redefine basic objects, like Array or Object itself.”
For example, here’s an exploit that works in Firefox 2, Opera 9, and Safari 3. It goes about redefining the global Array object then making it such that whenever a property value is set (even when the array is constructed!) the value is alerted out. In theory, a malicious script could use this technique to swipe data transmitted in JSON (via JSONP or even via an XHR+eval) and send it back to another server.
// From Joe Walker function Array() { var obj = this; var ind = 0; var getNext = function(x) { obj[ind++] setter = getNext; if (x) alert("Data stolen from array: " + x.toString()); }; this[ind++] setter = getNext; } var a = ["private stuff"]; // alert("Data stolen from array: private stuff");
Around the time of that commotion, a bug was filed in the Mozilla bug tracker that begin to explore ways of fixing this issue. It was eventually decided that this was a specification issue and that global objects should not be able to be redefined, due to the inherent problems that they can cause. You can read more about this change, which will be a part of ECMAScript 4/JavaScript 2 in Section 1.4 of the ECMAScript 4 Incompatibilities paper [PDF].
To set about testing this new change, and bringing it into practice sooner rather than later, the Mozilla team implemented and committed a fix to be a part of Firefox 3 (and thusly, JavaScript 1.8). Well, that change landed last week and after a couple minor fires were put out, it made it into the final release of Firefox 3, Beta 2.
If you want to see the change in action, go and download a Firefox nightly and put something like this in the console:
function Array(){ alert("hello, I found something of yours!"); } // ERROR: redeclaration of const Array
You’ll note that you now get the above error. This will also be the same for the following global objects:
- Array
- Boolean
- Date
- Error (Update just committed in time for FF 3.0b2)
- Math
- Number
- Object
- RegExp
- String
Thus, if you attempt to redeclare any of those global objects (like I did above) you’ll get the same error. Note that extending properties or prototypes of those objects have remained unchanged (they still work just fine) and this is a change that really shouldn’t effect anyone (save for the malicious types!).
As always, should you spot something tricky, please feel free to file a follow-up bug to the original one (or if you need help localizing it and reproducing it, let me know).
Robin (December 10, 2007 at 2:41 am)
Interesting. Were there any legit use cases for the original behaviour that this change might affect?
Sebastian Redl (December 10, 2007 at 4:06 am)
I don’t get the original problem. The poster’s point is that an attacker could redefine the array object to execute an action whenever an array element is set, right? So, how did the attacker get the privilege to execute JS in the first place?
It reminds me of the series of security non-bugs Raymond Chen posts about under the heading, “It rather involved being on the other side of this hatchway.”
If an attacker can redefine the Array object, there’s so many other things he can do.
Not that preventing redefinition of the built-in objects isn’t a good idea.
Sebastian Redl (December 10, 2007 at 4:14 am)
Err, never mind. I get it now.
Interesting. But I think the real problem is the CSRF attack. The JSON thing is harmless.
John Resig (December 10, 2007 at 9:10 am)
@Robin: None have really been located in the past 8 months, or so. The closest we’ve found is on MSN.com they were redefining the global Error object – but after talking with them they realized that it was a mistake and are going to change it.
@Sebastian: Absolutely, the XSS issue here is definitely larger, but up until this point it was assumed that you could easily transmit JSON data in relative security; it was really more of a wake-up call than anything else. Plus, it’s helped to tighten up the JavaScript language, in this respect, which is good.
Andrea Giammarchi (December 10, 2007 at 3:32 pm)
Good news!
However, with FireFox 2 and Safari 3 (I don’t know about 2) you can use a trick like this one:
const Native = (function(){
const NArray = Array,
NBoolean = Boolean,
NDate = Date,
NError = Error,
NMath = Math,
NNumber = Number,
NObject = Object,
NRegExp = RegExp,
NString = String;
return function(Native){
switch(Native){
case Array:return NArray;
case Boolean:return NBoolean;
case Date:return NDate;
case Error:return NError;
case Math:return NMath;
case Number:return NNumber;
case Object:return NObject;
case RegExp:return NRegExp;
case String:return NString;
};
};
})();
// Example:
Native = {};
alert(Native); // function Native() ...
alert(Array); // function Array(){[native code]}
Array = function(){}; // malicious redeclaration ... OK
alert(Array); // function malicious() ...
alert(Native(Array)); // function Array(){[native code]}
If You use them before external interactions You could simply use something like this:
Array = Native(Array);
before code evaluation.
As always, Internet Explorer doesn’t accept constants declaration, the uniq workaround I created many weeks ago is just for scalar values, as PHP does, using VBScript for IE.
kourge (December 10, 2007 at 9:22 pm)
I’ve seen quite a few techniques to restore native objects to its pristine state. One involves creating an iframe and “stealing” its native object to overwrite the current native object right before JSON data is populated. Andrea’s method is similar to this, essentially sealing a function object (with const) that returns frozen copies of native objects, then overwriting the current ones with the preserved ones.
I myself have once tried to make native objects sealed with const, but failed, because Firefox doesn’t allow redeclaration of a variable with const. However, while doing so, I found a clean and simple way to wipe native objects to their clean state that would neither need stealing an iframe’s nor preserving a frozen copy yourself.
Simply use
delete
. For example:delete Array;
delete Object;
Under Firefox, that’ll wipe Array and Object to their clean, pristine state.
Anyway, after all this, it’s nice to see native objects getting consted. It’s something that can only be achieved by patching Spidermonkey.
Anup (December 11, 2007 at 11:56 am)
John, this looks good. I wonder, should the XHR object also be “protected” like those global objects you listed?
Andrew Dupont (December 11, 2007 at 1:57 pm)
@kourge: Does the “delete” trick work in IE/JScript? Ordinarily JScript doesn’t allow deletion of anything in the
window
scope.kourge (December 11, 2007 at 11:17 pm)
@Andrew:
Yes, normally in IE/JScript if you try to delete anything in the
window
scope it’ll return false and whatever you tried to delete will still exist, but in this case, it’ll actually work, and whatever global object you deleted becomesundefined
. Yet another weird behavior in IE.So using the “delete” trick won’t throw errors in IE, but IE doesn’t support getters and setters anyway :D
Andrea Giammarchi (December 12, 2007 at 4:57 am)
@kourge, delete is not a good idea if you add some prototype. That’s why I prefere “frozen” constructor instead of deleted one :-)
Michael Geary (December 15, 2007 at 3:24 am)
Oh rats. This is going to break my code in our Zvents calendar widgets that replaces the Date constructor. I do that so I can allow a variety of other argument formats for the constructor in addition to the standard Date constructor arguments, and to make the “new” optional. For example, I allow any of these Date arguments (and many more):
Date(‘Jan 31, 2007’)
Date(‘2007-01-31’)
Date(‘2007-01-31 12:30:45’)
This works fine in every current browser.
Obviously I can fix it by giving my Date replacement a name of its own and leaving the original Date alone, but it will be a bit of a nuisance to track down all the code that uses the augmented Date().
Sometimes you get the bear, sometimes the bear gets you…
Brad Cable (December 19, 2007 at 10:46 am)
@Sebastian:
Could you explain what made you “get” it? I can’t see what the problem is here either, you could still transmit the data without hooking into the Native objects. The only thing I can see happening is if there is only one instance of each Native object for the entire browser, which I don’t think is the case.
I, too, am in Michael’s position. My web proxy relies on hooking into many native functions so that I don’t have to parse as much Javascript code directly, and easily forge many Javascript calls that may cause privacy leaks for the user.
fearphage (December 19, 2007 at 11:16 pm)
How’d you get this exploit to work in Opera? I’ve tried the stable (9.24 – 9.25) and the beta (9.5) and I can’t get the alert to happen. I can easily overwrite the native functions but implicit creation doesn’t seem to use the native constructors.
The following alerts “1,2”:
javascript:Array = undefined; alert([1,2])
The following alerts “[object Object]”:
javascript:Object = undefined; alert({a: 1, b: 2})
I tried the test your way above and using __defineSetter__. I could always overwrite the native constructor but could never get the information out with implicit construction. How did you accomplish this feat?
Steven Levithan (December 20, 2007 at 8:21 pm)
Another example of non-malicious code that this will break is the XRegExp.overrideNative method in my XRegExp library.
Nuck Chorris (March 19, 2008 at 6:50 pm)
Technically, in Firefox, wouldn’t an easy way to restore the code to its native pristine state would be to us the XPCNativeWrappers (I think thats them), like what Greasemonkey uses?
Jeremy Nicoll (May 19, 2008 at 9:25 am)
I, too, would like a bit better explanation of how this is an issue – can other sites access the data through an frame that the original site owner put up in the first place? This is about the only potential problem that I can see.
wakacje nad morzem (May 23, 2008 at 11:55 am)
I heve read your article I have the same opinion I have already saw that my site
Domki nad jeziorem
looks the same in Firefox and Opera. THXMarty Hall (May 29, 2008 at 4:32 pm)
Does this change mean that I can no longer use Prototype (http://prototypejs.org/) and Scriptaculous (http://script.aculo.us/)? After all, Prototype redefines zillions of global objects, and Scriptaculous is built on top of Prototype.
Most of the non-GWT Ajax code that I write uses Prototype or Scriptaculous…
Tom (May 31, 2008 at 12:39 pm)
@Marty: Prototype doesn’t redefine objects, it extends their prototypes. See John’s statement at the end of the post: “Note that extending properties or prototypes of those objects have remained unchanged (they still work just fine).”