# Telegram попросил не публиковать сведения об XSS. Мы раскрываем детали после исправления
Хранимая XSS-уязвимость в Telegram Desktop позволяла спрятать JavaScript в inline-кнопке сообщения. Вредоносный код мог месяцами оставаться незаметным, а затем запускаться при открытии HTML-экспорта переписки. При этом боту не требовалось состоять в чате: достаточно было, чтобы кто-то переслал туда подготовленное сообщение.
Мы сообщили о проблеме Telegram 3 июня 2026 года. В июле компания выпустила исправление, однако не одобрила публикацию материала. Поскольку уязвимость уже была закрыта, мы отказались от вознаграждения и предложили направить его на благотворительность. Также просили согласовать дату раскрытия. Этот текст - итоговое описание проблемы. Рабочий эксплойт и данные атакуемых систем не публикуются.
## Как выглядела атака
Представим инженерный чат финтех-компании. Раз в полгода его экспортируют для внутренней проверки. Сотрудник открывает Telegram Desktop, выбирает "Экспорт истории чата", сохраняет переписку в HTML и запускает получившийся файл в браузере.
В архиве могут находиться имена сотрудников, сообщения, фрагменты исходного кода, внутренние обсуждения и случайно опубликованные секреты - например AWS-ключ, который кто-то отправил "на минуту".
Пользователь видит обычную страницу с перепиской. Однако в одном из сообщений уже может находиться вредоносная inline-кнопка. Во время формирования HTML встроенный скрипт выполняется автоматически, без дополнительного нажатия. Он получает доступ к содержимому открытой страницы, собирает сообщения и может отправить их на внешний сервер.
Сам отправитель в этот момент ничего необычного не замечает. Для него экспорт выглядит штатно, а злоумышленник получает данные из всей выгруженной переписки.
## Почему бот мог не состоять в группе
Сценарий начинался с создания обычного Telegram-бота. Bot API позволяет добавлять к сообщениям inline-клавиатуры, а текст кнопки может содержать произвольные символы Unicode. В частности, в него можно было поместить HTML-конструкцию.
Клавиатура, состоящая только из URL-кнопок, сохранялась при пересылке благодаря механизму `CopyMarkupToForward`. Поэтому бот мог отправить сообщение в личный чат или другой канал, после чего любой пользователь пересылал его в нужную группу. Боту не требовалось получать доступ к истории, видеть участников или самостоятельно вступать в обсуждение.
Полученное сообщение становилось своеобразным контейнером для вредоносной разметки. Пока его просто читали в Telegram, код не выполнялся. Опасность возникала позже - при экспорте переписки в HTML.
## Техническая причина
В Telegram Desktop сообщения перед сохранением в HTML проходят экранирование. Функция `SerializeString()` преобразует специальные символы `<`, `>`, `&`, кавычки и апострофы в безопасные HTML-сущности. Она также обрабатывает переводы строк, Unicode-разделители и управляющие ASCII-символы.
Такая же защита применялась к именам отправителей и другим полям. Но текст inline-кнопок обрабатывался иначе. В файле `export_output_html.cpp` соответствующее значение напрямую записывалось в формируемую страницу. Поэтому HTML в поле `text` кнопки оставался активной разметкой.
Если в этом поле находился тег `
