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...
3 Answers 3
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.
-
It doesn't work in Firefox -
TypeError: can't convert undefined to object
Izkata– Izkata2013年12月13日 03:36:06 +00:00Commented 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– Izkata2013年12月13日 03:37:41 +00:00Commented Dec 13, 2013 at 3:37 -
1Makes 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.Erik Reppen– Erik Reppen2013年12月13日 03:45:28 +00:00Commented Dec 13, 2013 at 3:45
-
Voyager is tied with DS9 for my favorite (for different reasons, they're so hard to compare) ;)Izkata– Izkata2013年12月13日 03:48:12 +00:00Commented Dec 13, 2013 at 3:48
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
:
- Why is "this" in an anonymous function undefined when using strict? explains what led to this.
- Getting a reference to the global object in an unknown environment in strict mode includes an actual quote that explains the change.
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);
-
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.Fuzzical Logic– Fuzzical Logic2013年12月15日 14:10:02 +00:00Commented 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 returnwindow
, but it does from the developer console. It's doing different things when it should be identical.Izkata– Izkata2013年12月15日 15:24:55 +00:00Commented 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 globalthis
from console for this reason.Fuzzical Logic– Fuzzical Logic2013年12月15日 18:30:49 +00:00Commented Dec 15, 2013 at 18:30 -
In other words, I was just adding extra information for your answer to be put into proper context.Fuzzical Logic– Fuzzical Logic2013年12月15日 18:32:59 +00:00Commented 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– Izkata2013年12月15日 21:14:35 +00:00Commented Dec 15, 2013 at 21:14
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:
If the function code is strict code, set the ThisBinding to thisArg.
Else if thisArg is
null
orundefined
, 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.
this
meaningwindow
in the global context is a bug that has been fixed, at least in Firefox)