4

Disregard your usual paradigms for a mere moment here....

I've seen a few sites who have used the following logic:

a=[];
b=a["sort"];
c=(b)();
c["alert"](1); // intended output: Alert displaying "1"...

supposedly to access (I'm guessing here) the primitive of the sort function to gain access to the window object, and then using that to access "alert"...

I've tried executing this ages ago, and it worked a charm... But some twelve months or so later, I tried to run it again... and it's failed to execute, and given the following error:

TypeError: can't convert undefined to object

My question is, why wouldn't this logic work? It seems quite sound... Or is it?

And yes, I know that I could just do alert(1) and be done with it, but I'm trying to get an understanding of alternate ways of coding... I work in IT security and I know certain people have ways of using XSS to find alternative ways to perform generic functions to compromise systems...

5
  • 2
    That code seems a bit too clever for its own good. Dec 12, 2013 at 23:55
  • I know, but it's based off the following link: adamcecc.blogspot.com/2011/01/javascript.html I once tried the completely obfuscated version of this code, some few years back, and it amazingly worked... now, nothing... I'm assuming browsers have since cleaned up a little to prevent the window object to be accessed by alternative means... I know JavaScript is loosely typed, but could this potentially still be done? I have a system on this end which relies on text input in certain areas on Firefox, I guess i want to be certain that it can't be broken into in this manner... Dec 12, 2013 at 23:58
  • That would be my guess as well. Dec 12, 2013 at 23:58
  • What environment is it not working in? Works fine in a Chrome console. Dec 13, 2013 at 3:10
  • Damnit, I just read something yesterday that explained why it no longer works. But it was on my work computer, so it's not in my browser history. Let's see if I can find it... (Short version is yes, this meaning window in the global context is a bug that has been fixed, at least in Firefox)
    – Izkata
    Dec 13, 2013 at 3:39

3 Answers 3

1

I'm not sure why it wouldn't work in a given environment but it works in a Chrome console.

Let's walk through it:

a=[];

a is defined without a var declaration so it implicitly becomes a member of the global object which in browsers is the window object. This wouldn't work in strict mode (look for 'use strict'; - could be double-quoted), which would throw an error rather than do the implicit global thing, which yes, is one of few JS design warts that I actually agree was a bad idea with no redeeming qualities whatsoever. But strict mode will defeat the whole thing right here.

b=a["sort"];
//b=a.sort; // to make it even more clear. bracket notation is for obfuscation purposes

We assign the sort method of arrays to the var b. Called as b(), it's no longer called as a property/method of the array.

c=(b)();
//c=b(); // no need for (b) since b is already an evaluated function you can call

So, b is the array method sort but now it's just a loose function. What does that generic func do? It takes the function it's passed as an arg to sort whatever is represented by the 'this' keyword, sorts it in place and then returns 'this'. Since b is now just a loose function, 'this' should be the window object. In order to break at this point, passing undefined would have to break the sort method (which might make the most sense given your error) or the sort method would have to be behaving as if it had been permanently applied to Array.prototype as if the function bind method had been used (see "bind" on MDN).

Note: I use words like "seem" because sort returns as a [native code] method in Chrome which means it could be doing all kinds of things in the details as long as it technically fulfills ECMA spec which all browser vendors have been taking seriously since IE5.

c["alert"](1); // intended output: Alert displaying "1"...
//c.alert(1); //works too.
//alert(1); //and yes the same exact thing but easier to obfuscate from the top one

At this point, nothing magic is happening. We have the global object. We fire alert from it as a property of the global object.

4
  • It doesn't work in Firefox - TypeError: can't convert undefined to object
    – Izkata
    Dec 13, 2013 at 3:36
  • IMO that makes sense. Not being bound to anything, you get the same result with this: Array.prototype.sort.call(undefined)
    – Izkata
    Dec 13, 2013 at 3:37
  • 1
    Makes sense that Moz would be there first. Thanks. I'll try to narrow down later but have spousily duties involving some Voyager episodes. Laugh if you must. Post-season 3 is pretty badass actually. Dec 13, 2013 at 3:45
  • Voyager is tied with DS9 for my favorite (for different reasons, they're so hard to compare) ;)
    – Izkata
    Dec 13, 2013 at 3:48
0

So the thing I referenced in my comment above is the Javascript Garden, with its note about EcmaScript 5 in the sidebar. From there I was able to actually do some searches and find better references.

Basically, this has a couple different ways that it can be set. The one that matters here, I believe, is down under "Common Pitfalls":

Foo.method = function() {
    function test() {
        // this is set to the global object
    }
    test();
}

and "Assigning Methods":

var test = someObject.methodTest;
test();

Due to the first case, test now acts like a plain function call; therefore, this inside it will no longer refer to someObject.

So when you're retrieving sort off of the Array object, you're turning it into an "aliased" function, where this no longer points at the array. When strict mode is turned off/in older browsers/EcmaScript versions, that was window, due to being in the global scope. However when in strict mode, that is now undefined:

That said, Chrome's Developer Console is doing something weird, which makes it incorrect to test there. Your example code "succeeds" by returning window, however, if I copy that to a file and run it (with or without "use strict";), I get this in Chrome:

Uncaught TypeError: Array.prototype.sort called on null or undefined

And this comes from Firefox, either from a page or from within Firebug:

TypeError: can't convert undefined to object

Both of which are expected if this is now undefined, because it's essentially the same as doing:

Array.prototype.sort.call(undefined);

whereas because of the context under "Common Pitfalls" mentioned above, it used to do:

Array.prototype.sort.call(window);
7
  • Chrome's console is actually working correctly, as it is all globally eval'd code. (You can use a traceStack() or printStackTrace() to see it). 'use strict' can only be applied to a specific script or function and not a global environment. According to the spec, any global code will return the global (window) for this. Firefox's console is not globally eval'd, but eval'd "semi-globally" for lack of a better term. This is also, hence, working correctly. Dec 15, 2013 at 14:10
  • @FuzzicalLogic You misunderstand. The OP's code, when globally run from inside an html file, errors out because this doesn't return window, but it does from the developer console. It's doing different things when it should be identical.
    – Izkata
    Dec 15, 2013 at 15:24
  • Please do not misunderstand. I was not correcting, merely clarifying what should be proper expectation. It will not necessarily behave identically in a console vs a file especially when trying to utilize 'use strict'. A global this means different things in different pseudo-global environments. A console may also only be put into strict mode with either a) a special directive, or b) a console option. In nearly all other respects, one can depend upon the console to behave correctly, but one should never rely on verifying a global this from console for this reason. Dec 15, 2013 at 18:30
  • In other words, I was just adding extra information for your answer to be put into proper context. Dec 15, 2013 at 18:32
  • Firebug uses eval with a string, that's as global as you can get. But my point is that Chrome in the developer console is doing something I cannot duplicate from an HTML file, and unless I'm missing something seems to be doing the opposite of what you suggest.
    – Izkata
    Dec 15, 2013 at 21:14
0

Let's look at the ECMAScript 5 specification for Array.prototype.sort:

Let obj be the result of calling ToObject passing the this value as the argument.

And when we examine ToObject, we see a familiar entry in the table:

Argument Type    Result
=============    ======
Undefined        Throw a TypeError exception.
...
Object           The result is the input argument (no conversion).

This suggests that Chrome's invocation of b() has a this value that is not undefined, but other browsers do pass a this of undefined, resulting in a TypeError.

First we must examine how this is set when we perform a function call (which EMCAScript refers to as a CallExpression):

  • If Type(ref) is Reference, then
    • If IsPropertyReference(ref) is true, then
      • Let thisValue be GetBase(ref).
    • Else, the base of ref is an Environment Record
      • Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).
  • Else, Type(ref) is not Reference.
    • Let thisValue be undefined.

Because ImplicitThisValue of a function's Environment Record always returns undefined, the only case in which this is not undefined is during property access, e.g., foo.bar(). Therefore, we can conclude that the execution of a "bare" function invocation like b() will always use a thisValue of undefined.

But in the Chrome console, this isn't undefined for b(). To understand how this could happen (and how it could differ between browsers), we need to examine strict mode behavior in Section 10.4.3, Entering Function Code:

The following steps are performed when control enters the execution context for function code contained in function object F, a caller provided thisArg, and a caller provided argumentsList:

  1. If the function code is strict code, set the ThisBinding to thisArg.

  2. Else if thisArg is null or undefined, set the ThisBinding to the global object.

where "ThisBinding" is "The value associated with the this keyword within ECMAScript code associated with this execution context."

Thus, non-strict code replaces an null or undefined thisArg with the global object, window. Strict mode, on the other hand, does not convert null or undefined to the global object when used for this. (sort returns the sorted this object, so c = b() simply stores the returned the global object into c.)

Therefore, we can conclude that Firefox's implementation of the Array.prototype.sort function runs in strict mode, while Chrome's implementation does not.

You can see further evidence of this by how sort handles non-object this values. If oyu run typeof []["sort"].apply(5) in various browsers, Chrome will output "number", while Firefox outputs "object", demonstrating the object autoboxing that occurs only in non-strict mode. Firefox's strict sort function does not box the 5 and returns the raw primitive.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.