23 March 2018

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

Контур corporate blogIT systems testingWeb services testingMobile applications testing
Продолжаем цикл статьей «Календарь тестировщика», в этом месяце поговорим о тестировании безопасности. Многие не знают с чего начать и пугаются сложностей. Иван Румак, тестировщик безопасности веб-приложений в Контуре, поделился основами в поиске уязвимостей. Новички найдут в статье базовые знания, а опытным тестировщикам будет полезен раздел про обход защиты от 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.


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


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



Список статей календаря:
Попробуй другой подход
Разумное парное тестирование
Обратная связь: как это бывает
Оптимизируй тесты
Прочти книгу
Тестирование аналитики
Тестировщик должен поймать баг, прочитать Канера и организовать движуху
Нагрузи сервис
Метрики на службе у QA
Протестируй безопасность
Узнай своего клиента
Разбери бэклог

Tags:тестирование веб-приложенийкалендарь тестировщикабезопасность веб-приложений
Hubs: Контур corporate blog IT systems testing Web services testing Mobile applications testing
+7
12.5k 91
Comments 4