Let me jump right into it:
Step 1 - We need to implement a "watch" function for browsers that don't support it
Object.prototype.watch = function (prop, handler) { var oldval = this[prop], newval = oldval, getter = function () { return newval; }, setter = function (val) { oldval = newval; return newval = handler.call(this, prop, oldval, val); }; if (delete this[prop]) { // can't watch constants if (Object.defineProperty) // ECMAScript 5 Object.defineProperty(this, prop, { get: getter, set: setter }); else if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) { // legacy Object.prototype.__defineGetter__.call(this, prop, getter); Object.prototype.__defineSetter__.call(this, prop, setter); } } };
Step 2 - Now that we have that in place, we can create our seal method
Object.prototype.seal = function() { for ( var e in this ) { this.watch( e, function(newVal,oldVal) { var caller = arguments.callee.caller; throw 'Attempt was name to alter a sealed object [' + this + '] from [' + oldVal + '] to [' + newVal + ']' + 'from [' + caller + ']'; }); }; };VIOLA!
That is it. This solution is currently working in FF3.5 and Chrome for Linux, I haven't gotten it to work at all in IE7 or 8 yet (Once again, thank you Microsoft) but I suspect once I throw this over the fence to some of the hardcore IE/Compatibility gurus at the office tomorrow, I will have the answer to that problem.
If you would like to see a working demo of this in action, you can checkout the demo page I put together for it at http://software.digital-ritual.net/js-secure-demo/.
I absolutely welcome any and all comments, and will test this out on more browsers as time permits tomorrow. Hopefully this could solve the problem, at least provided something doesn't stop the JavaScript RTE from running the Object.seal method on all objects that need to be sealed; but that is for load testing. For now this seems to be the best approach I can come up with.
Interestingly enough, while this doesn't work for IE - it also works on iPhone Safari (impressed)
ReplyDeleteThis is good news that it expands to the mobile platform as well! Can't wait to run it through some more browsers tomorrow and see what it does!
So how is this increasing the security of the JavaScript? If this is to ensure that the developer does not overwrite a sealed object - then I think the implementation is worth the effort. However, if you are trying to prevent XSS from affecting "sealed" objects... well that battle was discussed a long time ago; and the result was that it can't be done.
ReplyDeleteFor instance, if I wanted to alter a "sealed" object, the first thing I would do is alter the seal and watch methods so that the throw would never happen.
With a JavaScript API we have to assume the browser is compromised. Provide controls (such as those to prevent clientside XSS), possible integration with CSRF/JavaScript Hijacking server side protections (i.e. for AJAX).
If an attacker gets an XSS attack running in the browser there is nothing that can be done. Only option is to prevent this by providing tools - sealing objects will provide zero benefit, with the exception of ensuring developer compliance... But any developer worth their salt could get around this if they wanted to.
While this is not a fail-proof solution, it does offer some level of protection against basic XSS attacks. The key here is that even if an attacker were to alter the watch and seal methods, they would be altering the behavior of the methods any time they were called on an object after the point in time they modified the methods. The important thing about this approach is that the methods are already applied to the objects in question (as part of object construction) so in theory they would have to modify the code before it was executed by the browser in order to modify the behavior of an object sealed in this manner. While not impossible, doing so through a persisted XSS payload could prove to be a pretty difficult hack. This is *not* a huge deal as a standalone library, but as part of a bigger library (ESAPI4JS) it could help to mitigate client-side risk for compromised applications.
ReplyDeleteI think that any level of protection for clients against compromised applications is a worthwhile endeavor.
I completely disagree because this is something so trivial to bypass it is not of any use. If I want to overwrite a property in any protected class I simply add the following:
ReplyDeleteObject.prototype.watch = function(prop, handler) {};
someClient = new ProtectedClass();
Just put the above right after the first "alert(someclient.getValue());"
Adding "security" that is trivial to bypass does not add anything other then increasing the complexity of use.
--Jeremy
Ah, I hadn't thought about re-initializing the object - I had broken it by redefining the __setter__ and __getter__ method on the Object prototype which was the first problem I encountered. So that being said, perhaps the answer was not so clear cut. It would seem like this could still be useful if you were protecting an instantiated object, a Singleton if you will where the definition of the object was not available in the runtime, only the instance of the object itself. This still wouldn't solve the problem that I discovered tho. This could be a non-issue in FF (and perhaps some others) as there is actually a native watch function that can be used. However, this could still be undone by calling unwatch on the property that you want to change. It makes me wonder why there hasn't been a bigger push to the JS community to get something like this worked in to the language. Sealing an object seems like a relatively fundamental aspect of any language to me. If nothing else, being able to preserve the functionality and definition of an object would be invaluable to protecting clients of compromised applications.
ReplyDeleteI think time is best spent making an application XSS proof vs. adding hurdles for someone to jump after they've found an XSS exploit. In other words, time should be spent making encoding algorithms easy to use. Things such as JQuery's "text" property.
ReplyDeleteHowever, given this, if you wanted to look for a way to seal an object there may be something in ECMA Script 5: http://ejohn.org/blog/ecmascript-5-objects-and-properties/
--Jeremy
--Jeremy
This article is fascinating.
ReplyDelete