Безопасность Telegram-бота: токены, вебхуки и контроль доступа
Продвинутое4 минРедакция Innovialabs
Коротко
Храните токен как пароль, проверяйте каждый запрос вебхука по секретному заголовку, сверяйте права по user_id на сервере и считайте любой ввод, включая callback data, недоверенным.
Коротко
Telegram-бот — это публичная точка входа, к которой может обратиться кто угодно. Большинство инцидентов происходит в пяти местах:
- Токен утекает, и ботом управляет кто-то другой.
- Вебхук принимает запросы, которые пришли не от Telegram.
- Админские команды защищены username или скрытой кнопкой вместо настоящей проверки.
- Ввод и callback data считаются надёжными, будто их сгенерировал сам бот.
- Данные пользователей хранятся дольше и шире, чем нужно.
Закройте эти пять пунктов — и бот будет защищён лучше большинства.
Токен: хранение, утечки и отзыв
Токен даёт полный контроль над ботом: чтение апдейтов, отправку сообщений, смену вебхука. Обращайтесь с ним как с паролем от базы данных.
- Держите его в переменных окружения или менеджере секретов, а не в репозитории. Добавьте
.envв.gitignoreи включите сканирование секретов в Git-хостинге. - Следите за логами: URL Bot API содержат токен (
/bot<token>/sendMessage). Его могут сохранить debug-логи HTTP-клиента, трекеры ошибок и логи прокси. - Используйте разных ботов для разработки и продакшена, чтобы тестовый токен на ноутбуке не имел доступа к реальным пользователям.
Если токен утёк, отзовите его в @BotFather (команда /revoke или настройки бота). Старый токен сразу перестаёт работать. Затем выкатите новый токен, заново установите вебхук с новым секретом и проверьте, что бот отправлял, пока токен был скомпрометирован.
Вебхук: убедитесь, что запрос от Telegram
URL вебхука — обычный HTTPS-эндпоинт. Если его угадали или он утёк, кто угодно может прислать поддельный апдейт, например «оплату» или сообщение от «админа».
При вызове setWebhook передайте secret_token. Telegram будет добавлять его в каждый запрос в заголовке X-Telegram-Bot-Api-Secret-Token, а ваш сервер отклоняет всё, где значение не совпадает.
import hmac
def is_from_telegram(headers: dict, expected: str) -> bool:
received = headers.get("X-Telegram-Bot-Api-Secret-Token", "")
return hmac.compare_digest(received, expected)
Что ещё стоит сделать:
- Используйте неочевидный путь вместо
/webhook, но не полагайтесь только на него. - Отвечайте быстро, а тяжёлую работу выносите в очередь, чтобы медленные запросы не превращались в повторные доставки.
- Фильтр по IP-диапазонам Telegram — дополнительный слой; диапазоны могут меняться, поэтому основной проверкой остаётся секретный заголовок.
Детали параметров — в документации setWebhook.
Контроль доступа к админ-функциям
Типичные ошибки: проверка по username (его можно сменить или занять), скрытая кнопка при открытой команде, доверие к чату, из которого пришло сообщение.
Как правильно:
- Сверяйте user_id со списком разрешённых в конфиге или базе — в каждом админском обработчике, а не только во «входной» команде.
- Держите проверку в одном месте: фильтр или middleware на весь админский роутер.
- В группах запрашивайте актуальный статус через
getChatMember, а не кешируйте права навсегда. Помните об анонимных админах: их сообщения приходят от имени группы, иfrom_user— не реальный человек. - Логируйте действия админов: кто, что и когда сделал.
Валидация ввода и callback data
Всё, что присылает пользователь, недоверенное: текст, файлы, контакты, геолокация. Проверяйте длину, формат и диапазоны до использования.
- Для SQL — только параметризованные запросы, никакой конкатенации строк.
- Экранируйте пользовательский текст перед отправкой с
parse_modeHTML или MarkdownV2, иначе сломается разметка или появится поддельная ссылка. - Проверяйте тип и размер файлов до обработки; не запускайте скачанные файлы.
- Ограничивайте частоту запросов на пользователя, чтобы один аккаунт не перегрузил бота или платный внешний API.
Отдельное внимание — callback data. Это до 64 байт, которые проходят через клиент, поэтому безопаснее считать, что их можно подделать. Кнопка с order:delete:1542 не доказывает, что заказ 1542 принадлежит этому пользователю.
- На каждый callback проверяйте на сервере, что объект принадлежит пользователю и действие допустимо в его текущем состоянии.
- Для чувствительных действий храните контекст на сервере, а в callback кладите только случайный короткий идентификатор.
- Делайте действия идемпотентными: повторное нажатие не должно дважды списывать деньги или создавать запись.
Хранение пользовательских данных
- Собирайте минимум: если хватает user_id, не просите номер телефона.
- Определите сроки хранения: удаляйте старые FSM-состояния, логи и историю диалогов. Ставьте TTL в Redis.
- Закройте Redis и базу от интернета, используйте пароли и шифрованные бэкапы.
- Не пишите персональные данные и токены в логи.
- Проверьте местное законодательство о персональных данных: в ряде стран, включая Узбекистан и Россию, есть требования к локализации данных.