«Календарь тестировщика». Протестируй безопасность

15 ИЮНЯ 2022

------------------------------------------------------------------------

Наш телеграм-канал: t.me/@mailsgun_online

Youtube: youtube.com/@mailsgun_online

------------------------------------------------------------------------

Продолжаем цикл статьей «Календарь тестировщика», в этом месяце поговорим о тестировании безопасности. Многие не знают с чего начать и пугаются сложностей. Иван Румак, тестировщик безопасности веб-приложений в Контуре, поделился основами в поиске уязвимостей. Новички найдут в статье базовые знания, а опытным тестировщикам будет полезен раздел про обход защиты от CSRF.

В прошлом году Иван занял 4 место в программе поиска уязвимостей Mail.ru и вошел в призовые топ-100 соревнования Hack The World 2017.

В феврале я решил научить коллег-тестировщиков искать уязвимости и проверять релизы на баги безопасности. Из плана обучения я вынес в статью самые основы: с чего начать, что такое HTTP, а также сделал полный разбор одной уязвимости — как искать, защищаться и обходить защиту.

С чего начать?

С этой проблемой сталкиваются многие новички. Кто-то первым делом идет на OWASP — сообщество по безопасности веб-приложений. Самое полезное, что я вынес с OWASP — список наиболее опасных и распространенных уязвимостей веб-приложений. Осмысленное обучение началось, когда я начал детально изучать каждую из них и гуглить все незнакомые слова. Стало понятно, что изучать даже самые распространенные клиентские уязвимости (CSRF, XSS) крайне трудно без знания устройства протокола HTTP.

Поэтому учить других тестировщиков я начал именно с устройства этого протокола, форматов передачи данных по нему и настройки Burp. Это отладочная прокси, через которую браузер пропускает все HTTP-запросы. Там же их можно редактировать, анализировать, сканировать, отправлять снова.

Еще один отличный источник информации — чтение и воспроизведение публичных репортов других хакеров.

Есть сайты, агрегирующие раскрытые сообщения о багах безопасности, например, http://h1.nobbd.de/. Там вы узнаете, какие бывают уязвимости, как их находят и чинят. Важно пытаться воспроизводить баги самому, чтобы получить практический опыт. Для этого можно использовать площадку для отработки поиска уязвимостей, например, DVWA.

Про HTTP

Суперважно знать, как устроено взаимодействие пользователя с веб-приложением. Поэтому расскажу про HTTP-протокол, через который клиент взаимодействует с веб-сервером. В нем нас интересуют:

Методы. Для начала достаточно различать GET и POST. Указываются в первой строке запроса.

GET — получить содержимое с сайта.

GET / HTTP/1.1
Host: example.com

POST — что туда отправить.

POST /endpoint HTTP/1.1
Host: example.com
User-Agent: Apache-HttpClient/4.5.5 (Java/1.8.0_161)
Content-Type: application/x-www-form-urlencoded
param1=value1&param2=value2

URI: путь до файла или эндпоинта, указывается после слэша в первой строке запроса.

Хэдеры — заголовки: User-Agent, Content-Type, Host и т.д. Бывают стандартные (Accept, User-Agent и т.д.) и кастомные с произвольными названиями и значениями (например X-Auth-Token: 123).

Про Content-Type

Content-Type нужно указывать для запросов, где параметры передаются в теле. Этот хэдер говорит веб-серверу, в каком формате отправляется содержимое запроса в теле. Основные 3 Content-Type:

— application/json

Content-Type: application/json
{"param1":"value1","param2":"value2"}

— application/x-www-form-urlencoded

Content-Type: application/x-www-form-urlencoded
param1=value1&param2=value2

— text/plain

Content-Type: text/plain
anytext{"param":123}><<>><xml>

Параметры, передаваемые на сервер. Содержат имя и значение. Записываются либо после URI как ?param=value&param2=value2&param3=value3, либо в теле в том формате, который указан в Content-Type.

Cookie. Самый распространенный способ авторизации пользователя. Когда пользователь логинится в сервис, ему выдается уникальный ключ, который хранится в браузере и с которым он отправляет все дальнейшие HTTP-запросы к этому сервису. Используется для идентификации пользователей, чтобы клиент А не получил доступ к данным клиента Б.

Когда пользователь нажимает кнопочки на UI, например, «Сохранить», он отправляет на веб-сервер такие HTTP-запросы, содержащие метод, URI, параметры, свои куки. Отловить отправленные вами запросы для исследования можно в браузере (в Хроме F12 -> Network), а отправлять через какие-нибудь API клиенты, например, Restlet Client. Либо воспользоваться отладочной прокси (Burp, Fiddler).

Зная устройство HTTP-протокола, можно начинать изучение конкретных уязвимостей. В качестве примера я приведу уязвимость CSRF — с нее полезно начинать новичкам.

Про уязвимость CSRF

CSRF (Cross site request forgery) — возможность заставить пользователя отправить произвольный HTTP-запрос к уязвимому ресурсу. Уязвимость CSRF является «клиентской» — с ее помощью можно атаковать только других пользователей, но не сервер и внутреннюю инфраструктуру.

Под угрозой сервисы, которые не проверяют откуда пришел запрос: с их сайта или с постороннего домена. Отправляется такой запрос с помощью тега form через атрибут onload — т.е. отправится сразу, когда какой-нибудь элемент на странице прогрузится.

Тег <form>. Этот тег в html-страничке отправляет GET или POST запросы к любому ресурсу.

Пример:

<form name=form1 action=”https://example.com/sendmoney” method=”POST”>
<input type=hidden name=”amount” value=”9999”>
</form>

Атрибуты:
name=”form1” — имя формы
action=”https://example.com/test” — куда отправить запрос
method=”POST”, method=”GET” — какой метод использовать
enctype=”application/x-www-form-urlencoded” — с каким Content-Type отправить запрос. Если не указывать этот атрибут, по-умолчанию отправится как application/x-www-form-urlencoded.

Чтобы указать параметры пользуйтесь тегом <input>.

Его атрибуты:
type=”hidden” — тип, почти всегда лучше использовать hidden. Если нужно загрузить файл, то использовать type=”file”, если нужна кнопка, которая отправит запрос в форме: type=”submit”.
name=”sendmoney” — имя параметра
value=”9999” — значение параметра

Пример страницы:

<html><body>
<form name=form1 action=”https://example.com/changepassword” method=”POST”>
<input type=hidden name=”newpassword” value=”123456”></form>
<body onload=”document.form1.submit()”> <!-- отправляет form1 -->
</body></html>

При посещении такой страницы пользователь без своего ведома отправит примерно такой запрос:

POST /changepassword HTTP/1.1
Host: example.com
Content-Length: 18
Origin: https://evil.com
Content-Type: application/x-www-form-urlencoded
Accept: text/html, */*
Cookie: auth.cookie.from.example.com=verysecret
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Referer: https://evil.com
newpassword=123456

Если на сервере нет проверки, откуда пришёл HTTP-запрос, то он обработает его как обычный. Т.е. пользователь сайта evil.com, отправит HTTP-запрос с кукой auth.cookie.from.example.com=verysecret, которую подставит браузер, и в контексте текущей сессии на example.com его пароль поменяется на 123456.

Есть тонкости отправки запроса из HTML-страницы:

1) Отправка запроса через тег form ограничивается только стандартными хэдерами. CSRF нельзя применить, если сессионный токен в приложении передается не через куки, а через авторизационный хэдер в каждом запросе, например, Authorization.

GET /userdata HTTP/1.1
Host: example.com
Accept: text/html, */*
Authorization: APIKEY123123123123123123
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Referer: https://evil.com

2) Content-Type поддельного POST запроса может быть только application/x-www-form-urlencoded, multipart/form-data или text/plain. Опять же из-за ограничений тега form.

3) Тег form позволяет отправлять только GET/POST запросы. PUT/PATCH/DELETE/MKCOL и прочие не пропускаются.

Как искать CSRF

Мониторить запросы, которые идут на сервер при работе в вашем приложении. Выбрать запросы на изменение чего-либо и попробовать отправить их через тег form из какого-нибудь файла csrftest.html.

Если сервер принял такой запрос от csrftest.html как обычный и что-то изменил, то можно заводить баг.

Последовательность работы:

  1. Прокликивай приложение и лови запросы в консоли браузера или отладочной проксе.
  2. Если что-то изменилось через GET-запрос, попробуй повторить его с html страницы как <img src=”весь путь с параметрами”>.
  3. Если изменения произошли через POST-запрос и защиты от CSRF нет, повторить его в теге form.
  4. Если есть защита, попробуй ее обойти.

Защита от CSRF

Чтобы защититься от непроизвольной отправки пользователем кросс-доменных запросов через тег form, убедись, что такой запрос приходит не с постороннего сайта.

Как проверить, что запрос из формы пришел с вашего сайта?

1) Каждый запрос, совершенный на сайте, передает уникальный токен в куках и в кастомном хэдере CSRFToken. Когда запрос получен, прежде чем что-то изменять этим запросом, проверяют совпадение значения хэдера с тем, что хранится в куках.

POST /changepassword HTTP/1.1
Host: example.com
CSRFToken: dadfaae9-c625-4bdf-8804-c7977d96954f
Cookie: session=123123123123; CSRFToken=dadfaae9-c625-4bdf-8804-c7977d96954f
Content-Type: application/x-www-form-urlencoded
Content-Length: 61
newpass=123456

Минус такой защиты — для GET-запросов этот хэдер почти всегда необязателен. Если в приложении у какого-то эндпоинта, что-то изменяющего (напр. /changepass), можно перенести параметры из тела POSTа в урл и совершить запрос как GET (HEAD и OPTIONS, кстати, тоже могут так работать), при этом запрос отработает как полноценный POST, то такую защиту можно обойти вот так:

<img src=”https://example.com/changepass?newpassword=123456”>

2) То же самое, что и прошлый пункт, только токен в куках сравнивается с токеном в параметре.

POST /changepassword HTTP/1.1
Host: example.com
Cookie: session=123123123123; CSRFToken=dadfaae9-c625-4bdf-8804-c7977d96954f
Content-Type: application/x-www-form-urlencoded
Content-Length: 61
newpass=123456&CSRFToken=dadfaae9-c625-4bdf-8804-c7977d96954f

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

Можно попробовать обойти, если токен генерируется из двух частей: статической (например, хэш от айди пользователя) и динамической (хэш от даты получения токена). Тогда можно отправить токен только со статической частью. Или отправить запрос без токенов вообще, у Фейсбука был такой баг (https://amolnaik4.blogspot.ru/2012/08/facebook-csrf-worth-usd-5000.html).

3) Content-Type каждого запроса должен быть отличен от поддерживаемых тегом form (urlencoded, text/plain, multipart/form-data).

Это хороший способ защиты API от CSRF, если авторизация пользователя возможна и по кукам, и по кастомному хэдеру, и по параметру в урле.

Если в корне сайта присутствует плохо настроенный crossdomain.xml, то через Flash можно заставить пользователя отправить запрос с любым Content-Type. Вот детальная статья про Flash.

4) Same Site Cookie. Флаг на куках, который ставится при аутентификации вместе с httponly и не позволяет браузеру отправлять ваши куки с левых сайтов, к которому эти куки никак не относятся. Круто, но не все браузеры поддерживают этот флаг. То же самое про проверку Origin — классно, работает, но не все браузеры правильно посылают Origin при кросс-доменных запросах.

Прямо сейчас проверь:

— В твоем приложении у POST-запросов есть защита от CSRF.

— Чувствительные действия (смена пароля, создание доп. пользователя в организации, отправка денег), которые отправляются как POST, в случае, если защита построена на токене в кастомном хэдере и токене в куках, не могут быть переделаны на GET с параметрами в урле из тела и что-то менять в системе (200 OK, 201 CREATED…). Аналогично с переводом PUT/PATCH в POST или GET.

— Если чувствительные действия отправляются с “Content-Type: application/json” и защита от CSRF построена только на этом, то попробовать отправить запрос с телом в форматах application/x-www-form-urlencoded, multipart/form-data, text/plain. При успехе повторить с левого сайта в интернете через <form>.

— В корневом каталоге сайта нет плохо настроенного crossdomain.xml, файла для кросс-доменных запросов с использованием Flash. (example.com/crossdomain.xml). «Плохо» — это когда любые кросс-доменные запросы могут быть отправлены с любого сайта на ваш, в crossdomain.xml явно описано, с какого домена и какие запросы можно совершать.

Итог

Теперь вы знаете про какие-то основы тестирования безопасности и где можно брать информацию для углубления в тему, можете проверять свои проекты на CSRF и разбираетесь в HTTP.

Что дальше? Изучай новые виды уязвимостей, ищи их у себя в проекте, устраняй их. Вместе мы сделаем интернет более безопасным!

Полезные ссылки

Source: habr1

Ну и напоследок напоминаю вам о нашем новом проекте vkserfing bot.