Содержание статьи
В один совершенно обычный день нам пришел запрос на пентест банковского приложения. Разобрались с бюрократией, обсудили все детали. «Итак, NDA подписан, скоуп и дедлайн согласованы, можно начинать», — подумали мы и сразу же приступили к работе. К концу проекта описать наш ступор словами было уже практически невозможно. Хорошая новость заключалась в том, что новые уязвимости обнаруживались очень быстро. Плохая в том, что их было много. После первой уязвимости мы подумали: «Бывает». После второй: «Не очень хорошо». После пятой стало очевидно, что слово «бывает» здесь уже несколько неуместно.
info
Название самого банка, параметры, заголовки, эндпоинты заменены альтернативными в целях конфиденциальности. Скриншоты мобильного приложения также были заменены сгенерированными с помощью ИИ изображениями с другим дизайном — по той же причине. Любые совпадения с названиями, интерфейсами, параметрами или другими деталями, встречающимися в других банках, случайны. Все приведенные в статье названия и технические данные были изменены и подобраны исключительно ради анонимизации исходной цели.
Валидация OTP
Когда пользователь пытается залогиниться в свой аккаунт в банковском приложении, ему нужно ввести ПИН (персональный идентификационный номер) и серию паспорта на начальной странице. После этого приложение перенаправит пользователя на новую activity, где ему потребуется ввести свой номер телефона для получения одноразового кода (OTP).
Слово «свой» в абзаце выше выделено не просто так. Каждый раз, когда пользователь логинится, он может ввести любой номер телефона, вне зависимости от того, привязан этот номер к его аккаунту или нет. OTP придет в любом случае, и после его ввода приложение перекинет пользователя на следующий этап.
Таким образом, злоумышленник, который знает ПИН и серию паспорта жертвы, может обойти эту валидацию, используя любой другой номер телефона.

После ввода ПИНа, номера телефона и отправки запроса, если такой клиент существует, API-сервер вернет следующий ответ:
HTTP/1.1 200 OK
Content-Type: application/json
{ "success": true,
"customerNumber": "123456"}Сохраним номер клиента, он нам понадобится в дальнейшем.
На следующей странице пользователя просят ввести номер телефона. Раз уж приложение никак не проверяет принадлежность этого номера, злоумышленник может указать любой телефон, на который поступит одноразовый код.

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

Если введен корректный код, открывается следующая страница.

Таким образом у нас получилось обойти проверку номера телефона и перейти на следующий этап верификации — KYC.
Корявый KYC
KYC — это, конечно, хорошо. Но было бы лучше, если бы он еще и работал корректно.
В ходе тестирования наша команда обнаружила, что если KYC пользователя провалится, например его лицо не похоже на фотографию в удостоверении личности, то сервер вернет нам 500 Internal Server Error. Если же все будет совпадать, то нам будет возвращен код 200 с подобной JSON-информацией:
HTTP/1.1 200 OK
Content-Type: application/json
{
"message": "success",
"data": {
"customerNumber": "555555"
}
}
Помнишь, я предлагал сохранить номер клиента, который возвращал нам сервер?
В этом контексте он тоже нам возвращается. После получения такого ответа нас перенаправляет на страницу, где мы можем установить ПИН‑код для приложения.
Здесь мы задались вопросом: а вдруг программа перенаправляет пользователя, только основываясь на содержимом возвращаемого сервером ответа? Поэтому, вернувшись к началу процесса KYC, мы умышленно провалили проверку, чтобы сервер вернул нам ответ 500 и мы могли протестировать HTTP Request Tampering.
К счастью для нас и к сожалению для банка, мобильное приложение проверяет только customerNumber и, увидев код 200 от API-сервера, перенаправляет нас на следующий этап. То есть KYC тоже можно без особого труда обойти.
Первым делом злоумышленник записывает у себя ответ сервера (код 200), при этом значение customerNumber нужно будет поменять на номер клиента жертвы. А этот номер, напомню, можно достать из ответа API-сервера, если знать ПИН и серию паспорта жертвы.
Затем злоумышленник умышленно проваливает идентификацию лица. Генерируется запрос, где фотография злодея (скорее всего, там будет не его настоящее лицо) отправляется на сервер в формате Base64 для проверки:
POST /api/v2/identity/liveness-test HTTP/1.1
Accept: application/json
Content-Type: application/json
{
"imageData": "/9j/4AAQSkZJRgABAQAAAQABAAD...<base64 картинка>...5ErkJggg=="
}
После этого сервер вернет злоумышленнику такой ответ:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
Злоумышленник, перехватив ответ до получения его приложением, полностью меняет структуру:
HTTP/1.1 200 OK
Content-Type: application/json
{
"message": "success",
"data": {
"customerNumber": "<тут указывается номер клиента>"
}
}
После того как мобильное приложение получит такой ответ, оно перенаправит нас на страницу настройки ПИН‑кода для доступа.

Когда ПИН‑код настроен, приложение возвращает пользователя обратно на главную страницу.

По CVV на брата
В большинстве банковских приложений при просмотре реквизитов банковской карты CVV обычно по умолчанию скрыт и, чтобы увидеть его, нужно нажать на соответствующее поле. В исследуемом нами приложении в момент нажатия на поле CVV на сервер отправляется вот такой запрос:
POST /api/v2/cards/cvv HTTP/1.1
Accept: application/json
Authorization: Bearer <redacted>
Content-Type: application/json
{
"cardNumber": "4930264148157430"
}
В качестве ответа сервер нам выдает следующее:
HTTP/1.1 200 OK
Content-Type: application/json
{
"message": "success",
"data": {
"cvv": "123"
}
}
На первый взгляд все выглядит вполне логично: приложение отправляет номер карты на сервер, сервер понимает, для какой именно карты необходимо получить CVV, проверяет авторизацию пользователя и возвращает заветные три циферки.
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
