Автор баги: arkiix
Всего пять минут ожидания, один чужой номер телефона и обычная кнопка для получения ссылки — этого оказалось достаточно, чтобы обнаружить критическую уязвимость. Настолько минимальный сценарий неожиданно вскрыл проблему в логике обработки данных.
В этой статье разберем, как неправильный порядок операций между сохранением данных и их проверкой позволял подменить свой номер на чужой. Именно такая логическая ошибка в Telegram-боте превращала механизм авторизации в открытую дверь к чужим аккаунтам.
Механизм с изъяном
Во время пентеста мы изучали бота Заказчика, который должен был аутентифицировать пользователей через Telegram. Вместо привычных логинов и паролей здесь все держалось на номере телефона, привязанном к аккаунту в мессенджере.
Механика работы выглядела так: пользователь отправлял команду /start, а бот в ответ запрашивал номер телефона через специальную кнопку «Поделиться контактом». При нажатии открывался список контактов, из которого можно было отправить как собственный профиль, так и сохраненный контакт другого пользователя. В результате бот получал не только номер телефона, но и user_id владельца этого номера. Дальше он сверял id отправителя с id из контакта и, если все сходилось, бот формировал ссылку для авторизации, пользователь по ней переходил и успешно попадал в систему.
Telegram гарантирует подлинность контакта на уровне архитектуры. Когда пользователь отправляет контакт, его клиент передает только номер и имя. А вот user_id владельца контакта сверяется на стороне сервера. Подделать этот параметр невозможно. Так что, если в контакте есть user_id, значит, номер телефона гарантированно принадлежит пользователю с данным идентификатором.
На бумаге все выглядело надежно. Но, как это часто бывает, настоящие проблемы скрывались в мелочах, а именно, в том, как бот управлял состоянием сессии.
Когда порядок имеет значение
При анализе кода бота, в функции-обработчике контактов мы сразу обнаружили проблему. На первый взгляд все выглядело логично: бот получал контакт, сверял user_id, и только потом выдавал ссылку. Но баг лежал на поверхности и достаточно было посмотреть на порядок операций.

Handler получал контакт от пользователя и первым делом сохранял номер телефона в контекст сессии session.Phone. А только потом выполнялась основная проверка безопасности — сравнение user_id отправителя с user_id из контакта.
Засада была в том, что номер телефона попадал в сессию до валидации. Это всё равно что выдать человеку ключи от машины, а потом спросить, есть ли у него права.
Кнопка «Получить ссылку» и отсутствие проверок
Но как привести данный недостаток к реальному импакту? Мы действовали по классике: имея на руках ошибку записи в кэш, начали искать смежный функционал, который позволил бы её эксплуатировать.
Помог случай. Во время исследования срок действия ссылки, которую выдал бот, подошел к концу, и мы получили то самое сообщение с кнопкой «Получить ссылку». Именно это событие заставило нас обратить внимание на механизм обновления токенов.
Обработчик этой кнопки getTokenHandler брал из контекста сессии номер телефона и подписывал на его основе новую ссылку для авторизации. Он проверял только, что session.Phone не пустой, но не проверял, кому этот номер принадлежит. Просто доставал session.Phone и формировал ссылку.

И тут мы вспомнили критический нюанс обработки контактов. Telegram позволяет пользователю отправить не только свою карточку, но и контакт любого человека из списка контактов. При попытке отправить чужой контакт, бот, разумеется, сверял ID и выдавал сообщение об ошибке, указывая на несоответствие данных. Но из-за той самой логической ошибки (сначала сохранил, потом проверил), номер телефона жертвы успевал записаться в session.Phone ещё до того, как срабатывала защита и пользователю возвращалась ошибка.
В итоге сложилась идеальная картина для атаки: у нас была активная сессия, возможность подменить номер в памяти бота (просто отправив чужой контакт и проигнорировав ошибку валидации) и хендлер кнопки, который доверчиво формировал ссылку на вход, используя этот подмененный номер.
Пошаговый план захвата
Собрав все вместе, мы получили простой, но эффективный вектор атаки. Красота была в том, что не требовались никакие специальные инструменты и все делалось в обычном интерфейсе Telegram.
Алгоритм действий выглядел следующим образом:
- Открыли бота и запустили взаимодействие командой
/start. - Прошли авторизацию, выбрав свой реальный номер из списка контактов.
- Получили сообщение с кнопкой «Войти», но проигнорировали его.
- Сформировали контакт с номером телефона жертвы (заранее добавив его в контакты на устройстве).
- Подождали пару минут, пока бот не прислал уведомление «Время ссылки истекло» с новой кнопкой «Получить ссылку».
- В этот момент отправили боту через вложения подготовленный контакт жертвы.
- Получили ожидаемое сообщение об ошибке (т.к.
user_idне совпали), однако номер жертвы уже скрытно записался в память сессииsession.Phone. - Вернулись к предыдущему сообщению (тому, где говорилось об истечении времени) и нажали кнопку «Получить ссылку».

Бот сгенерировал новую ссылку, но уже на основе подмененного номера из контекста. В новом сообщении мы нажали «Войти» — и дело сделано!
Мы оказались в аккаунте жертвы. Получив полный доступ ко всем данным и возможность действовать от её имени, мы, по сути, реализовали классический Account Takeover.
Специфика платформы и выводы
Эта уязвимость хорошо показывает, почему порядок действий в коде так важен для безопасности, особенно в разработке ботов.
В Telegram-ботах (и на других диалоговых платформах) использование механизмов хранения контекста (FSM, сессий) — это стандартная и необходимая практика. В отличие от классических веб-сайтов, где промежуточные данные часто живут на фронтенде, боты вынуждены хранить все состояние пользователя (этапы диалога, введенные данные) на сервере.
Именно эта архитектурная особенность делает их уязвимыми к подобным логическим ошибкам. Вероятно, запись номера в контекст изначально не задумывалась как фиксация проверенного факта. Разработчик просто сохранял входящие данные «на лету», чтобы в случае успеха выдать сессию. Но появление «сбоку» функции восстановления ссылки, которая слепо доверилась этому контексту, привело к фатальным последствиям.
Самые интересные баги часто появляются не в сложной криптографии, а вот в такой «серой зоне» бизнес-логики, где одна переставленная строка или неявное доверие к кэшу компрометируют всю систему.
Пентест
Вы можете нанять одну из лучших команд хакеров в мире для проведения тестирования на проникновение.
Мы работаем как с крупными корпорациями, так и со стартапами, знаем про требования регуляторов, нужды бизнеса и реальные угрозы.
