В феврале я решил научить коллег-тестировщиков искать уязвимости и проверять релизы на баги безопасности. Из плана обучения я вынес в статью самые основы: с чего начать, что такое HTTP, а также сделал полный разбор одной уязвимости — как искать, защищаться и обходить защиту. С этой проблемой сталкиваются многие новички. Кто-то первым делом идет на OWASP — сообщество по безопасности веб-приложений. Самое полезное, что я вынес с OWASP — список наиболее опасных и распространенных уязвимостей веб-приложений. Осмысленное обучение началось, когда я начал детально изучать каждую из них и гуглить все незнакомые слова. Стало понятно, что изучать даже самые распространенные клиентские уязвимости (CSRF, XSS) крайне трудно без знания устройства протокола HTTP. Поэтому учить других тестировщиков я начал именно с устройства этого протокола, форматов передачи данных по нему и настройки Burp. Это отладочная прокси, через которую браузер пропускает все HTTP-запросы. Там же их можно редактировать, анализировать, сканировать, отправлять снова. Еще один отличный источник информации — чтение и воспроизведение публичных репортов других хакеров.  Есть сайты, агрегирующие раскрытые сообщения о багах безопасности, например, http://h1.nobbd.de/. Там вы узнаете, какие бывают уязвимости, как их находят и чинят. Важно пытаться воспроизводить баги самому, чтобы получить практический опыт. Для этого можно использовать площадку для отработки поиска уязвимостей, например, DVWA. Суперважно знать, как устроено взаимодействие пользователя с веб-приложением. Поэтому расскажу про HTTP-протокол, через который клиент взаимодействует с веб-сервером. В нем нас интересуют: Методы. Для начала достаточно различать GET и POST. Указываются в первой строке запроса. GET — получить содержимое с сайта. POST — что туда отправить.  URI: путь до файла или эндпоинта, указывается после слэша в первой строке запроса. Хэдеры — заголовки: User-Agent, Content-Type, Host и т.д. Бывают стандартные (Accept, User-Agent и т.д.) и кастомные с произвольными названиями и значениями (например X-Auth-Token: 123).  Content-Type нужно указывать для запросов, где параметры передаются в теле. Этот хэдер говорит веб-серверу, в каком формате отправляется содержимое запроса в теле. Основные 3 Content-Type:  — application/json — application/x-www-form-urlencoded — text/plain Параметры, передаваемые на сервер. Содержат имя и значение. Записываются либо после URI как ?param=value&param2=value2&param3=value3, либо в теле в том формате, который указан в Content-Type. Cookie. Самый распространенный способ авторизации пользователя. Когда пользователь логинится в сервис, ему выдается уникальный ключ, который хранится в браузере и с которым он отправляет все дальнейшие HTTP-запросы к этому сервису. Используется для идентификации пользователей, чтобы клиент А не получил доступ к данным клиента Б. Когда пользователь нажимает кнопочки на UI, например, «Сохранить», он отправляет на веб-сервер такие HTTP-запросы, содержащие метод, URI, параметры, свои куки. Отловить отправленные вами запросы для исследования можно в браузере (в Хроме F12 -> Network), а отправлять через какие-нибудь API клиенты, например, Restlet Client. Либо воспользоваться отладочной прокси (Burp, Fiddler).  Зная устройство HTTP-протокола, можно начинать изучение конкретных уязвимостей. В качестве примера я приведу уязвимость CSRF — с нее полезно начинать новичкам. CSRF (Cross site request forgery) — возможность заставить пользователя отправить произвольный HTTP-запрос к уязвимому ресурсу. Уязвимость CSRF является «клиентской» — с ее помощью можно атаковать только других пользователей, но не сервер и внутреннюю инфраструктуру.  Под угрозой сервисы, которые не проверяют откуда пришел запрос: с их сайта или с постороннего домена. Отправляется такой запрос с помощью тега form через атрибут onload — т.е. отправится сразу, когда какой-нибудь элемент на странице прогрузится.

Тег <form>. Этот тег в html-страничке отправляет GET или POST запросы к любому ресурсу. Пример: Атрибуты:
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” — значение параметра Пример страницы: При посещении такой страницы пользователь без своего ведома отправит примерно такой запрос: Если на сервере нет проверки, откуда пришёл HTTP-запрос, то он обработает его как обычный. Т.е. пользователь сайта evil.com, отправит HTTP-запрос с кукой auth.cookie.from.example.com=verysecret, которую подставит браузер, и в контексте текущей сессии на example.com его пароль поменяется на 123456.  Есть тонкости отправки запроса из HTML-страницы: 1) Отправка запроса через тег form ограничивается только стандартными хэдерами. CSRF нельзя применить, если сессионный токен в приложении передается не через куки, а через авторизационный хэдер в каждом запросе, например, Authorization. 2) Content-Type поддельного POST запроса может быть только application/x-www-form-urlencoded, multipart/form-data или text/plain. Опять же из-за ограничений тега form. 3) Тег form позволяет отправлять только GET/POST запросы. PUT/PATCH/DELETE/MKCOL и прочие не пропускаются. Мониторить запросы, которые идут на сервер при работе в вашем приложении. Выбрать запросы на изменение чего-либо и попробовать отправить их через тег form из какого-нибудь файла csrftest.html.

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

Как проверить, что запрос из формы пришел с вашего сайта? 1) Каждый запрос, совершенный на сайте, передает уникальный токен в куках и в кастомном хэдере CSRFToken. Когда запрос получен, прежде чем что-то изменять этим запросом, проверяют совпадение значения хэдера с тем, что хранится в куках. Минус такой защиты — для GET-запросов этот хэдер почти всегда необязателен. Если в приложении у какого-то эндпоинта, что-то изменяющего (напр. /changepass), можно перенести параметры из тела POSTа в урл и совершить запрос как GET (HEAD и OPTIONS, кстати, тоже могут так работать), при этом запрос отработает как полноценный POST, то такую защиту можно обойти вот так:  <img src=”https://example.com/changepass?newpassword=123456”> 2) То же самое, что и прошлый пункт, только токен в куках сравнивается с токеном в параметре.  Даже если запрос к этому эндпоинту можно совершить как 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
Протестируй безопасность
Узнай своего клиента
Разбери бэклог Редактор блога