Пробив веба
Как я обошел 2FA и захватил учетки пользователей
Pentest Award
Этот текст получил первое место на премии Pentest Award 2024 в категории «Пробив web». Это соревнование ежегодно проводится компанией Awillix.
warning
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Перечисление пользователей
- Уровень опасности: средний
На сайте, который я тестировал, была возможность зайти в личный кабинет. А на странице входа нашлась возможность перебирать пользователей и смотреть ответ сервера: в случае если пользователь с введенным именем существует, сервер отвечает иначе.
Если указываем существующую учетку, получаем сообщение «Введен неверный пароль» либо «Превышено количество попыток входа с неверным паролем». Если же пользователя с таким логином нет, то в ответе сервера будет сообщение «Ваши данные не найдены в системе. Проверьте правильность указанных данных».


Я запустил автоматизированный подбор по словарю и нашел некоторое количество валидных логинов.
Спреинг паролей
- Уровень опасности: средний
Дальше я нашел возможность подбирать по словарю пароли найденных пользователей — опять же по ответу сервера на введенные данные. Это атака password spraying. Новые пароли я пробовал каждые 30 минут, так как после каждого следующего неверного ввода учетных данных период блокировки не увеличивался. В итоге удалось подобрать пароли для трех пользователей сервиса.
Обход 2FA
- Уровень опасности: критический
Сразу зайти в личный кабинет не удалось из‑за того, что сервис проверял второй фактор аутентификации и просил ввести код из SMS.

Изучив код этой страницы, я нашел интересный параметр — mode4
. Здесь он имеет значение sms
.

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


Здесь интереснее всего значение mode4=registration
, которое перенаправляет на страницу завершения регистрации в личном кабинете. Там пользователь задает себе логин и пароль, но при этом уже задал и подтвердил номер телефона.

Вводим и подтверждаем пароль одного из полученных ранее пользователей, и нам удается зайти в личный кабинет, минуя двухфакторную авторизацию. Также, если изменить логин на этой же странице, он сохраняется измененным.
Похищение учетки
- Уровень опасности: критический
Итак, мы вошли в систему без регистрации и SMS. Но на этом мы, конечно же, исследование не прекратим и продолжим копать.
Мое внимание привлекли cookie на странице смены пароля. Выяснилось, что содержимое страницы не зависит ни от каких значений, кроме UserStatus
. А это значение перманентно и уникально для каждого пользователя.

Что будет, если подменить UserStatus
одного пользователя аналогичным значением другого? Логин и пароль оставим прежними. После отправки запроса приходит ответ, что все прошло успешно. Спокойно извлекаем из него новые cookie, логин, пароль и UserToken
, но уже для сессии другого юзера — того, чей UserStatus
использовался. Также приходит редирект обратно на страницу входа. Если бы смена не была успешной, этим параметрам из cookie присваивались бы значения deleted
и тоже происходил бы редирект.
Теперь можем войти снова — используя новые логин и пароль. Нас опять попытается остановить 2FA, то есть запрос кода из SMS. Но мы уже знаем, что делать в этом случае, и без труда проникаем в личный кабинет пользователя, UserStatus
которого мы использовали.

Мы не знаем UserStatus
для других пользователей, но можем попробовать подобрать это значение. Его длина — 40 бит. Это 240 вариантов, то есть около 1,1 триллиона значений. Многовато для перебора!
Но если разобрать любое известное нам значение UserStatus
побитово, то можно увидеть, из чего оно состоит. Половина каждого байта определяет поле, а вторая половина — значение этого поля.

Теперь мы можем подбирать значения лишь 20 бит. Число вариантов сокращается до 220, то есть около 1,05 миллиона возможных значений переменной UserStatus
.
Результат эксплуатации: несколько захваченных аккаунтов, потенциальная возможность захвата всех пользовательских аккаунтов за чуть более чем миллион запросов.
Выводы
Итак, мы поняли, что есть возможность менять логины и пароли всех пользователей, UserStatus
которых содержит те же поля, что известные нам. Затем мы можем пройти аутентификацию от имени этих пользователей в обход 2FA. Во время тестирования мне удалось захватить несколько аккаунтов и установить, что можно получить доступ к любой учетной записи чуть более чем за миллион запросов.
Вот что я порекомендовал сделать заказчику, чтобы устранить уязвимости:
- Перечисление пользователей: унифицировать ответ сервера при вводе валидных и невалидных учетных данных.
- Спреинг паролей: каждый раз увеличивать время блокировки учетных записей после ввода неверных логина и пароля.
- Байпас 2FA: после того как пользователь прошел регистрацию, не редиректить его в личный кабинет, а перенаправлять на страницу ввода логина и пароля.
- Похищение учетных записей: использовать связку из нескольких параметров, которые уникально идентифицируют пользователя.