Сегодня мы не будем говорить о zero‑day, RCE или продвинутых APT. Мы поговорим о гораздо более опасной проблеме — о ложном чувстве безопасности.
В 2026 году многие организации считают, что «у нас включена двухфакторная аутентификация — значит, мы защищены». Но что, если 2FA — это просто фасад? Что, если за этой «бронированной дверью» стоит замок с четырёхзначным кодом и надпись: «угадывай сколько хочешь»? Именно такая уязвимость была обнаружена в одном из российских банков — и она позволяла полностью обойти 2FA, зная лишь номер телефона клиента.
Здравствуйте! Я аналитик SOC и параллельно занимаюсь пентестом. Недавно мне довелось участвовать в авторизованном black‑box пентесте российского банка — как внешнего, так и внутреннего периметра по стандартам ЦБ.
Инфраструктура была большая, около 4000 компов и 4500 тысячи пользователей. В ходе внешнего тестирования (15 сабдоменнов) были выявлены разные уязвимости: от классических Stored XSS и IDOR до неочевидных API‑мисконфигураций. Во внутренней инфраструктуре также обнаружились интересные векторы — от неправильно настроенных сервисов до нестандартных методов эскалации привилегий. Однако самым критичным находкой во внешнем пентесте стал обход механизма двухфакторной аутентификации (2FA) по SMS в личном кабинете клиента, предназначенном для подачи финансовых заявок.
Немного об импакте, который может получить злой хакер/мошенник
Это полноправный инструмент для оформления финансовых заявок, и доступ к нему открывает перед злоумышленником широкий спектр возможностей. Оформление проектного финансирования и кредитной линии. Все эти действия могут быть выполнены без дополнительного подтверждения — ведь 2FA уже пройдено. Таким образом, атакующий не просто «подсматривает» чужие данные — он активно использует доверие банка к легитимному пользователю для проведения мошеннических операций. В личном кабинете есть раздел с документами пользователя, его истории заявок, отчетность, реквизиты. И что самое страшное — это то, что на сайте есть номер телефона технической поддержки и он привязан к ЛК, мы можем с этого профиля подтверждать или отклонять выдачу денежных средств.


Что пошло не так? Секрет в четырёх цифрах
Когда я впервые зашёл на страницу входа в личный кабинет, все выглядело стандартно. Была возможность авторизоваться по номеру телефона. На первый взгляд удобно. Но с точки зрения безопасности — тревожный сигнал. Почему тревожный? Дело в том, что код подтверждения состоял из 4-х цифр, и тут вступает в игру простая арифметика:

Код состоит из 4 десятичных цифр: от 0000 до 9999.
Всего 10 000 возможных комбинаций.
При отсутствии ограничений на количество попыток — это полный брут за считанные секунды.
Таким образом, энтропия 2FA настолько низкая, что защищает он разве что от ручного угадывания.
От первого входа — к первой догадке
Первым делом я зарегистрировал тестовый аккаунт (ЕСТЕСТВЕННО с согласия банка, в рамках пентеста) и попытался войти в личный кабинет. Ввёл номер телефона — и буквально через пару секунд на него пришло SMS-сообщение вида:
Ваш код подтверждения: 5621 Банк «Рога и Копыта»

4 цифры, и в голову закрались три мысли:
Есть ли лимиты на ввод?
Как проверяется код на стороне сервера?
Можно ли автоматизировать?
Для ответа на вопросы я полез в Burp, начал шерстить логику работы. Там я обнаружил, что вся логика аутентификации оказалась сосредоточена всего на двух API-вызовах:
1. Пользователь нажимает «Получить код»:

Сервер отвечает:
{"repeat":180}
Что означает: «SMS отправлена. Повторно запросить можно только через 180 секунд».
2. Проверка кода — точка входа для атаки
После получения SMS пользователь вводит код, и браузер отправляет:
POST /api/*****/login HTTP/1.1 Host: **** Content-Type: application/json {"code":"0548","phone":"+7(111) 222-22-22"}
И тут у нас вся суть уязвимости:
При неверном коде → HTTP 400
{"message":"Номер телефона или код некорректный"}
При верном коде → HTTP 200 и установка сессионных кук (JSESSIONID, PHPSESSID и др.).
Не было никаких других ограничений, ни счётчика попыток, ни блокировки сессии, ни капчи, НИЧЕГО. Для сервера каждый запрос был как первый. А значит, можно отправить 10 000 таких запросов подряд – и он честно ответит на каждый.
Создание эксплоита
Если сервер не против общения, то почему бы не позадавать ему разные запросы 10 000 раз подряд?
Хотя основной PoC был реализован на JavaScript, чтобы работать внутри браузерной сессии, сама атака — чисто HTTP-базированная, а значит, её можно воспроизвести любым инструментом, умеющим отправлять POST-запросы.
Почему именно JavaScript в браузере?
Использует те же куки, что и пользователь (
JSESSIONID,PHPSESSID),Обходит CORS и SameSite-политики,
Демонстрирует уязвимость вживую на сайте банка — без прокси, без Burp, без сторонних тулзов.
Полный скрипт по понятным причинам публиковать не буду, но и замудренного там ничего нет.
1. Отправка запроса на проверку кода
await fetch('https://roga_and_copyta/*****/api/*****/login', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({code: codeStr, phone: phone}) });
Сервер честно отвечает 200 при успехе — и этого достаточно.
2. Многопоточность через асинхронные функции
for (let i = 0; i < 20; i++) { turboThread(i, start, end); // 20 "потоков" }
Каждый перебирает свой диапазон кодов параллельно.
3. Определение успеха
if (response.status === 200) { foundCode = codeStr; // Визуальное оповещение + ввод кода в поле }
А можно ли на Python?
Конечно! Изначально скрипт и был на Python, для демонстрации заказчику был переписан на JS. Неважно, на каком языке вы его напишете — главное, что сервер отвечает на каждый запрос. А когда он делает это 10 000 раз подряд без единого «стоп слова» — то 2FA превращается в иллюзию.
Корень проблемы: CWE-307 и ошибка проектирования
Уязвимость классифицируется как:
CWE-307: Improper Restriction of Excessive Authentication Attempts
CVSS 3.1: 9.1 (Critical)
Вектор: Network
Сложность: Low
Требуемые привилегии: None
Взаимодействие с пользователем: None
Обход 2FA → полный доступ к ЛК
Почему так вышло? Три фатальные ошибки:
Удобство > Безопасность:
Полагаю, что разработчики боялись «раздражать» клиента блокировками при опечатке — и убрали все механизмы защиты. (теория)Непонимание угрозы автоматизации:
Считали, что «4 цифры — это и так сложно угадать», забыв, что человек не угадывает — скрипт перебирает.Отсутствие тестирования на перебор:
Ни один из сценариев «многократный ввод кода» не был покрыт тестами безопасности.
Как это исправить? Рекомендации
Безопасность — это не «галочка про 2FA», а многослойная защита. Что нужно сделать:
Жесткие лимиты попыток. Максимум 3–5 попыток на один код, после чего уничтожение кода и необходимость нового.
CAPTCHA после нескольких ошибок. Простая капча уже остановит 90% скриптов.
Переход на 6-значные коды. Такое количество комбинаций уже займет часы, а не 35 секунд.
И понятное дело мониторинг.
Заключение
Это история больше не про взлом банка, а история про иллюзию безопасности. Радует то, что банки уделяют внимание пентестам для поиска таких простых дыр. Все уязвимости закрыты, это был очень интересный опыт.