XSS- и CSRF-атаки — разбираем уязвимости

Аватарка пользователя Елизавета Ржевская
Отредактировано

Рассказываем об уязвимостях #frontend-приложений XSS и CSRF, разбираем дефолтную политику браузера и пути её обхода или настройки

12К открытий14К показов

В контексте веб-безопасности есть два основных вектора атаки, которые необходимо учитывать: XSS и CSRF. Прежде чем рассказать о них, необходимо понимать дефолтную политику браузера насчёт взаимодействия между вкладками — точнее, взаимодействия между скриптами с разным origin’ами. Эта политика называется Same Origin Policy (SOP). Её центральное понятие — Origin (~ источник) скрипта/документа. Он определяется комбинацией трёх понятий: протокола (http/https, etc.), хоста (my-website.com) и порта (например, 443 для https и 80 для http).

Вот примеры, которые браузер будет интерпретировать как скрипты с одинаковым origin’ом. Если на открытой вкладке присутствует 3 скрипта, origin’ы которых отличаются подобным образом, то браузер будет воспринимать их как скрипты с одним origin’ом:

А такие origin’ы будут считаться разными:

https://melicious-website/home

Важная особенность SOP: скрипт с одним origin’ом может посылать запросы на другой origin. Однако не сможет прочитать ответ на него. Чтение ответа будет заблокировано на уровне браузера. Если требуется заблокировать в том числе отправку запросов (скажем, вы не хотите, чтобы скрипты, загруженные на вашем сайте с других origin’ов, могли делать запрос на origin сайта), это тоже возможно реализовать посредством указания Content Security Policy. Но дефолтная политика (SOP) работает именно так.

XSS- и CSRF-атаки — разбираем уязвимости 1

XSS / JS-Injections

Наиболее часто встречающейся атакой является Cross-Site Scripting («Межсайтовый скриптинг»), или более удачное, на мой взгляд, название — JS-инъекции (по аналогии с SQL-инъекциями).  Если в html-документ вставить строку, в которой будут использоваться элементы, похожие на html-теги, то браузер будет интерпретировать их как валидный html-код и исполнять его. Например, можно прописать с пустым src и добавить onerror=“alert(‘hacked’)” (так как src пустой, то браузер сразу перейдет к вызову onerror), внутри которого выполнить любой JS-код (имея доступ к localStorage и так далее).  Также можно поместить вредоносный скрипт внутрь запроса query params в следующем виде:

XSS- и CSRF-атаки — разбираем уязвимости 2

Предотвращение JS-инъекций

В современных фреймворках (вроде Angular/React) изначально действует санитизация, которую в случае необходимости (если вы хотите запустить inline-скрипт из строки и уверены, что там не содержится ничего вредоносного) можно отключить. В React’е это делается посредством атрибута, подчеркивающего опасность вставки inline-скриптов: 

XSS- и CSRF-атаки — разбираем уязвимости 3

В Angular’е можно за’inject’ить санитайзер и выбрать тот уровень санитизации, который подходит под ситуацию. Например, можно разрешить чтение стилей, но при этом экранировать любые скрипты.

XSS- и CSRF-атаки — разбираем уязвимости 4

Выглядит это так:

			this.safeString = this.domSanitizer.bypassSecurityTrustHtml(dangerousString);
		

Помимо санитизации существует Content Security Policy (CSP) Header. Через него можно явно задать, скрипты с каким origin’ом могут исполняться браузером на текущей странице. По дефолту Content Security Policy содержит значение *. То есть браузеру разрешено запускать скрипты с любым origin’ом, включая inline-скрипты без origin’а. Такие настройки считаются очень небезопасными, и рекомендуется всегда явно указывать, скрипты с каким origin’ом можно запустить на сайте.  Явное указание ограниченного набора origin’ов заодно предотвращает исполнение inline-скриптов. Чтобы разрешить их исполнение, требуется явно это прописывать.

Пример того, как может выглядеть CSP Header:

			Content-Security-Policy:
default-src 'self';
img-src *;
media-src media1.com media2.com;
script-src userscripts.example.com
		

Такие настройки означают, что изображения могут подгружаться с любого источника (*), media только с двух источников (media1.com & media2.com). А скриптам позволено запускаться, если они имеют только один origin (userscripts.example.com). default-src ‘self’  позволяет запускать скрипты с текущего origin’а, но не его поддоменов.

Посредством CSP-заголовка можно очень тонко настраивать исполнение скриптов на сайте.

content-security-policy Header и access-control-allow-origin немного похожи. CSP позволяет обходить SOP, а access-control-allow-origin позволяет получать разрешение на CORS. То есть отвечать (response) на запросы скриптов с другим origin’ом.

В начале статьи я упоминал, что SOP позволяет скриптам с одним origin’ом делать запросы на другой origin, однако SOP заблокирует ответ. Посредством access-control-allow-origin можно получить доступ к этому ответу, если на сервере явно прописан origin, который имеет право получать ответы.

Если кратко и утрированно, то SCP — это про SOP и frontend-часть системы. А access-control-allow-otigin — про CORS и backend-часть.

CSRF (XSRF)

XSS- и CSRF-атаки — разбираем уязвимости 5

Второй по распространённости тип уязвимости — это Cross-Site Request ForgeryМежсетевая подделка запроса»). Этот вектор атаки использует склонность браузера неявно добавлять куки к любому запросу. Например, вы авторизовались на сайте, где используются куки, и в какой-то момент некий скрипт делает запрос на origin этого банка — то есть открыли почтовый клиент и перешли по вредоносной ссылке. Браузер автоматически добавит куки к запросу, даже если сам запрос происходит с другой вкладки.

Из-за SOP этот скрипт не будет иметь доступа к response. Но он сможет выполнять запросы на правах авторизованного пользователя. Например, сделать запрос на перевод денег или просто на удаление любой возможной информации.

Для предотвращения этой атаки в первую очередь рекомендуется использовать явные системы аутентификации. Например, session token / jwt в header или session token в body param. Также, если на сайте используется какая-то форма, то рекомендуется реализовывать anti-csrf token’ы. Их суть в том, что сервер в response отдаёт два случайных токена. Один предназначен для куков, другой прячется в скрытом поле формы. Когда клиент submit’ит форму, то должен отправить оба токена обратно на сервер. В случае, если они отличаются от того, что сервер выпустил изначально, сервер блокирует запрос.

Integrity

XSS- и CSRF-атаки — разбираем уязвимости 6

В контексте информационной безопасности существует понятие CIA triad, объединяющее три аспекта:

  • конфиденциальность; доступ к информации должен иметь только тот, кому она предназначается;
  • доступность: информация должна быть доступна в нужном объеме и в нужное время;
  • связность: то, насколько части системы могут доверять передаваемым друг другу сообщениям, и не произошло ли в процессе передачи нарушения данных.

В web’е это можно представить в виде отношений между frontend’ом и backend’ом. В html есть специальный атрибут integrity, позволяющий запускать скрипт только в том случае, если он не отличается от того, что изначально подразумевали его разработчики.

Предположим, что вы используете cdn (content delivery network) для оптимизации скорости доставки приложения, однако вы хотите быть уверены, что приложение, которое вы сбилдили сами, и то, которое получаете из cdn, — одно и то же. В этом случае можно в процессе прод-билда приложения сгенерировать integrity-хеш, который указывается следующим образом:

			<script src="https://example.com/example-framework.js"
integrity=“sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC”>
		

И когда браузер попытается загрузить скрипт из cdn, то, используя указанный алгоритм (в данном примере sha384) сгенерирует новый хеш на основе загруженного скрипта, произведет сравнение и позволит его запустить только в том случае, если хеши не отличаются.

В случае Angular’а включить генерацию таких хешей можно с помощью флага

—subresource-integrity.

Следите за новыми постами
Следите за новыми постами по любимым темам
12К открытий14К показов