The lab
here is how portswigger describes it
This lab uses AngularJS in an unusual way where the
$evalfunction is not available and you will be unable to use any strings in AngularJS.To solve the lab, perform a cross-site scripting attack that escapes the sandbox and executes the
alertfunction without using the$evalfunction.
this one throw us back to the hardest part, actually getting our code to execute against an angularjs setup that has been stripped of its usual escape routes on purpose
The idea#
this one needs some background before any of the payload makes sense
angularjs scans the html inside an ng-app region and treats anything in double curly braces as an expression to evaluate old versions of angularjs did not just run those expressions, they ran them through a sandbox, a layer that was supposed to stop your expression from reaching real javascript like the function constructor and the window object. the problem was that researchers broke that sandbox over and over, so angularjs eventually gave up and removed it entirely in version 1.6. this lab deliberately pins one of the older, sandboxed versions, so the sandbox is back and in our way
on top of that, the lab bolts on two extra locks, and both are named right in the description. the $eval function, which is the easy way to take a string and run it as an expression, is gone. and you are not allowed to use any string literals at all, meaning no quotes anywhere in your payload. that matters because almost every ready made angularjs sandbox escape you will find online is built out of quoted strings, so none of them work here:( that is the real challenge
so the shape of the solution has to be three things at once. we need a way to make strings without ever typing a quote, we need something other than $eval that will run an expression for us, and we need to knock the sandbox out so it stops blocking us. the payload is just those three ideas together i hope this all make some sense
Step 1 - Fingerprint angularjs and confirm the expression runs#
the first job on any suspected angularjs page is to confirm the framework is there and that our input reaches it. viewing the source shows the angularjs script loading and an ng-app on the page

Step 2 - Try the normal escapes and watch them fail#
the natural next move is to reach for a known sandbox escape, the kind that grabs the function constructor and runs string for example the usual constructor tricks that end in something like calling a quoted 'alert(1)'

so every one of them dies here, and for exactly the two reasons the lab warned us about. the payloads that lean on $eval fail because $eval does not exist on this version, and the payloads that pass a quoted string fail because string literals are blocked
running into both walls is actually the useful part, because it tells us precisely what our payload must avoid, no $eval and no quotes, and that narrows the design down to the unusual approach this lab wants it's kinda confusing
Step 3 - Build the three pieces without any quotes#
let us solve each of the three needs in turn
making a string with no quotes. calling toString() inside the expression hands us a string without ever typing one, and from a string we can reach .constructor, which is javascript's string builder. that builder has a fromCharCode method that turns numbers into characters, so we can spell out any text we like purely from numbers. the codes 120,61,97,108,101,114,116,40,49,41 decode as follows
120 x
61 =
97 a
108 l
101 e
114 r
116 t
40 (
49 1
41 )
so toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41) builds the string x=alert(1) no quotes involved
evaluator that is not $eval angular's orderBy filter takes an expression as its argument and evaluates it which makes it a perfect stand in for the $eval we are not allowed to touch. feed it our generated x=alert(1) and it will run it
turning the sandbox off. this is the key move. angular's sandbox checks expressions character by character, and it relies on the string method charAt to do that inspection. if we overwrite charAt so it no longer does its job, the sandbox's checks fall apart and it stops rejecting our code. the trick is toString().constructor.prototype.charAt=[].join, which reaches every string's charAt method and reassigns it to the array join method, quietly disabling the guard for the rest of the page
Step 4 - Assemble the payload and fire the alert#
put the three pieces together and this is the url.
/?search=1&toString().constructor.prototype.charAt%3d[].join;[1]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)=1
in the address bar the = after charAt is written as %3d so the browser does not mistake it for a query separator, but as an expression it reads as two statements. the first, toString().constructor.prototype.charAt=[].join, overwrites charAt and switches the sandbox off. the second, [1]|orderBy:toString().constructor.fromCharCode(...), pipes a throwaway array through the orderBy filter, and orderBy evaluates the argument we handed it, which is the freshly built x=alert(1). with the sandbox already disabled by the first statement, angular no longer blocks that assignment, so running it calls alert(1).
to put it in one line, we manufactured our code as a string without quotes, we borrowed orderBy to run it since $eval was taken away, and we bribed the sandbox into silence by rewriting charAt first. loading the url pops the alert

with this, the lab is solved!
