The Jerusalem PostIsraeli man detained in India for carrying live cartridge of ammunition leftover from IDF serviceESPNHow ChatGPT and Lane Kiffin could have gotten LSU kicked out of the SECInquirerBukidnon school eyes safety upgrades after Grade 12 student’s deathPunchEXPLAINER: What every business should know about CAC annual returnsוואלהצה"ל: חוסל מחבל נוח'בה שפשט למעבר ארז ב-7 באוקטוברUN NewsAs freshwater reserves shrink, countries must prepare for a new water realityBollywood HungamaLuv Ranjan unveils first look of Pehla Pyaar Doosri Baar featuring six debutants, film to release on October 23Premium TimesNIDCOM says Nigerian detained in India refused opportunity to returnIl Fatto Quotidiano“Non so quanto tempo avrò”: Il calcio dominante di Amorim al Milan non si vede. San Siro fischia, ora l’allenatore è preoccupatoCapital FMNyamira University Set for First Intake as Construction ProgressesБи-би-си«Нарисованные победы». Как российские военные с помощью ИИ имитирует успехи на фронте7sur7Menaces de Trump: “Personne” ne dicte au Canada avec quels pays il peut conclure des accords, riposte Carney
The Daily Newsstand · Free, Always
Thursday, September 17, 2026

[Перевод] Telegram попросил нас молчать об XSS. Мы всё равно публикуем

Translate

Бот, которого не было в вашей группе, мог спрятать код в сообщении, ждать месяцами и сработать, когда кто‑то экспортирует переписку. Telegram исправил баг молча.

Сначала мы сообщили о баге вендору и молчали до выхода фикса. Тестировали только на своих аккаунтах. Это раскрытие после выхода патча.

Мы нашли хранимую XSS в Telegram Desktop и 3 июня 2026 года сообщили о ней. В июле Telegram выпустил фикс — и отказался одобрить нашу публикацию, хотя проблема уже была исправлена. Мы отказались от вознаграждения и попросили отдать эти деньги на благотворительность; мы также просили согласовать дату публикации. Этот текст — и есть раскрытие. Кода эксплойта здесь нет, живые системы мы не атаковали.

Утро понедельника

Давайте я опишу сцену.

9:47 утра. Финтех‑компания сдаёт инженерный чат комплаенсу — проверка дважды в год, рутина. Ведущий разработчик открывает Telegram Desktop, нажимает «Экспорт истории чата», выбирает HTML и открывает файл в Chrome.

Страница загружается: сообщения, отметки времени, имена, фрагменты кода, ветка, где кто‑то вставил AWS‑ключи «буквально на секунду».

Он не видит тег <script>, который уже сработал внутри кнопки. За четыреста миллисекунд, пока страница отрисовывалась, каждое сообщение на ней упаковали в JSON и отправили на сервер в другой стране.

Он видит обычный экспорт. Атакующий видит всё.

Сообщение с кодом пришло семь месяцев назад. Новый коллега переслал его, думая, что это ссылка на корпоративный блог. Бот, который его отправил, никогда не был в группе. Ему и не надо было.

Эта сцена — собирательная. Цепочку атаки из неё — пересылку, экспорт, открытие и утечку — мы показали в тестах.

Коротко

Telegram Desktop умеет выгружать чат в HTML‑файл, а этот файл открывается в браузере. Мы выяснили, что бот может спрятать невидимый JavaScript в inline‑кнопке — и этот код сработает, как только кто‑то откроет экспорт с такой кнопкой. Без второго клика. Без предупреждений.

Из открытой страницы скрипт может прочитать все сообщения, отправить их на сервер атакующего и переписать саму страницу.

Бот при этом никогда не вступает в чат. Достаточно одного пересланного сообщения.

POC

POC

Исправлено в Beta v6.9.4 / Stable v7.0.1. На момент публикации: advisory от Telegram нет, CVE не присвоен.

Одна пропущенная строка

Код экспорта живёт в файле export_output_html.cpp. Текст сообщений перед попаданием на страницу экранирует: функция SerializeString() превращает <, >, &, ", ' в безопасные HTML‑сущности, заменяет переводы строк и Unicode‑разделители на <br> и hex‑кодирует управляющие ASCII‑символы. Та же функция применяется к именам отправителей и другим полям.»

А к тексту кнопок её не применяли. Строка 1752 просто записывала его на страницу:

button.text.toUtf8()

Так любой HTML в поле text кнопки становился живой разметкой в экспортированной странице. Включая теги <script>.

Эта строка пришла из коммита 52c779bf в начале 2024 года и уехала в стабильный релиз v4.15.1 8 марта 2024-го. В продакшене она прожила примерно два года и четыре месяца.

Почему это опаснее обычного XSS

XSS — это когда данные на странице начинают интерпретироваться как код. «Хранимая» — когда вредоносные данные где‑то сохраняются и срабатывают позже. Обычно для этого атакующему нужен доступ к цели. Здесь — нет:

1. Доступ не нужен. Бота можно создать за пару минут. Bot API позволяет прикреплять к сообщениям inline‑кнопки, а поле text принимает любой Unicode — включая HTML‑теги. Клавиатура, состоящая только из URL‑кнопок, переживает пересылку (правило CopyMarkupToForward). Поэтому коллега или любой участник может принести отравленное сообщение в группу, которой бот никогда не касался. (По тому же правилу можно пересылать игры и некоторые SwitchInline‑кнопки. Нам они не понадобились.)

2. Он ждёт. Код лежит в истории сообщений спящим текстом. Он ничего не делает, пока кто‑то не экспортирует чат.

3. Одно сообщение доходит до многих. Одна пересылка в супергруппу на 200 000 человек может задеть каждого, чей экспорт включает это сообщение и кто откроет файл.

Как устроена атака. Часть 1: пересланное сообщение

Атакующий создаёт бота. Бот отправляет одно сообщение: текст — «посмотрите наш новый пост в блоге», в text кнопки — тридцать невидимых символов U+3164 и следом тег <script>, а URL кнопки ведёт на правдоподобный блог.

В Telegram кнопка выглядит пустой или почти пустой. Она при этом работает как ссылка, так что ничего не вызывает подозрений.

Сообщение, которое затем пересылается в группу, выглядит почти пустым.

Сообщение, которое затем пересылается в группу, выглядит почти пустым.

Кто‑то пересылает это сообщение в целевой чат. Telegram сохраняет кнопку, потому что она URL‑типа. Бот никогда не был участником группы. Через API у него ноль доступа к её сообщениям.

Курсор на Forward в интерфейсе Telegram.

Курсор на Forward в интерфейсе Telegram.

Пересланное сообщение в тестовой группе.

Пересланное сообщение в тестовой группе.

Семь месяцев спустя кто‑то экспортирует чат.

Telegram Desktop экспортирует переписку.

Telegram Desktop экспортирует переписку.

Видео ниже записано до выхода фикса. На нём вся цепочка целиком: страница превращается в фейковую форму верификации Telegram, а каждое сообщение на ней тихо уходит на сервер атакующего.

Как устроена атака. Часть 2: бот без доступа

Вот это нам показалось самым интересным.

В Telegram есть privacy mode для ботов. По умолчанию бот в группе не получает обычные сообщения и историю — только команды, ответы, сообщения, отправленные через бота, и служебные сообщения. Администраторы видят на боте пометку: «не имеет доступа к сообщениям». Поэтому группы спокойно добавляют служебных ботов: погода, опросы, напоминания.

В списке участников бот Saved помечен has no access to messages.

В списке участников бот Saved помечен has no access to messages.

HTML‑экспорт ломает эту границу. Когда вы экспортируете чат, Telegram записывает в файл и собственные сообщения бота — вместе с кнопками. Скрипт бота оказывается на странице и оттуда читает каждое показанное на ней сообщение. API никогда не отдавал боту эти данные. Их отдал файл экспорта.

Бот погоды с privacy mode не может читать ваши сообщения. Пока кто‑то не откроет экспорт с его кнопкой. Как только экспорт будет открыт в браузере — сообщения улетят атакующему.

CVSS называет это Scope Changed: изначально уязвимость — в локальном коде экспорта Telegram Desktop, а ущерб наступает в браузере, в другой среде.

Что забирает скрипт

Скрипт исполняется сразу при открытии файла:

Сообщения. Он читает DOM страницы (.message.default): каждое показанное сообщение с отправителем и временем.

Данные чата. Из шапки страницы: название, тип, число участников.

Всё остальное на странице. document.body.innerText. В нашем PoC стоит лимит 200 КБ — это тестовое решение, а не граница импакта.

Путь к файлу. location.href заодно раскрывает имя пользователя ОС и структуру папок.

Сообщения из открытого документа в выводе контролируемого теста: текст, отправители и время.

Сообщения из открытого документа в выводе контролируемого теста: текст, отправители и время.

Ещё наш PoC показал, что страницу можно заменить целиком — на фейковую форму «Необходима верификация» с брендингом Telegram. В демо поля пароля были read‑only, и на 127.0.0.1 уходил только тестовый email. Это была наша страховка, а не ограничение техники: тот же контроль над страницей можно использовать для фишинга.

Есть и другое применения описанному вектору. Скрипт может отредактировать то, что вы видите: изменить даты, имена отправителей, текст сообщений, спрятать, переставить или вставить сообщения, подменить контекст. История на серверах Telegram и файл на диске остаются нетронутыми. Но если экспорт чата используется как доказательство — в проверке или в суде, — тому, что на экране, верить уже нельзя.

Одно сообщение — много экспортов

Результат в тестовой панели. Видна запись с 14 сообщениями и четырьмя отправителями.

Результат в тестовой панели. Видна запись с 14 сообщениями и четырьмя отправителями.

Пересылка — это то, что даёт масштаб. В группах с отключённой защитой контента сообщение может переслать любой участник: публичная группа на 10 000 человек, инженерный чат на 2 400, трейдинговая группа на 200 000 — каждая копия несёт свой код. Каналы — отдельная история: чтобы там что‑то опубликовать, нужны права администратора или свой человек, так что это таргетированный вектор, а не бесплатное массовое распространение. И каждая отравленная страница экспорта срабатывает сама по себе; страница без кода ничего не делает.

Код живёт в истории до удаления сообщения. Те, кто вступил позже, тоже могут его получить — если им видна предыдущая история и сообщение попадает в диапазон их экспорта.

И в отличие от вложения, это сложно сигнатурно затектить. Это просто текст в базе Telegram. URL скрипта можно заблокировать, а само сообщение — нет. Оно превращается в код только тогда, когда происходит не связанное с ним обычное действие — экспорт чата. Между отправкой и срабатыванием могут пройти месяцы или годы. Атакующий оставляет сообщение и ждёт.

Фикс

Фикс — это один вызов функции, и он содержится в коммите 8457d13a, “Fix escaping in HTML export of keyboards” (John Preston; написан 30 июня, закоммичен 2 июля 2026):

- block.append(button.text.toUtf8());
+ block.append(SerializeString(button.text.toUtf8()));

- ? ("return ShowTextCopied('" + content + "');").toUtf8()
+ ? ("return ShowTextCopied('"
+         + QString(content)
+             .replace('\\', u"\\\\"_q)
+             .replace('\'', u"\\'"_q)
+         + "');").toUtf8()

Тем же коммитом закрыт сопутствующий вектор: инъекция JavaScript в атрибут onclick="return ShowTextCopied('…')" у кнопок копирования. Теперь content экранирует обратные слэши и одинарные кавычки, прежде чем стать частью JS‑строки.

Насколько это серьёзно? CVSS 3.1 даёт 8.2 (High): CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N. Мы сознательно выбрали Attack Vector Local, а не Network: уязвимая часть — локальный код экспорта, а сетевой путь доставки кода обрабатывает данные корректно. Если поменять эту метрику, получилось бы 9.3, но нам важнее оценка, которую можно защитить. Ущерб от этого не меняется: чувствительные данные уходят из открытого документа, а запись можно переписать.

Если вы пользуетесь Telegram Desktop: обновитесь. Первый релиз с фиксом — Beta v6.9.4 (3 июля 2026), затем стабильная версия v7.0.1 (14 июля 2026). Обновление не перезаписывает старые файлы. Если вы экспортировали чаты в HTML раньше, переэкспортируйте их после обновления — или открывайте старые экспорты с отключённым JavaScript.

Что ответил Telegram

В нашу демонстрационную группу также вступил представитель службы безопасности Telegram под именем TS Sec. Он написал, что попробовал экспорт в другой группе, и спросил, получили ли мы что‑нибудь. Мы обсуждали демонстрацию прямо в чате — ещё до последующей переписки о публикации.

TS Sec обсуждает тест экспорта в демонстрационной группе.

TS Sec обсуждает тест экспорта в демонстрационной группе.

1 июня 2026 — находим баг.

3 июня — сообщаем о нём на security@telegram.org с PoC, видео и оценкой CVSS.

30 июня — написан фикс. Публичная дата коммита — 2 июля.

1 июля — Telegram подтверждает отчёт и предлагает вознаграждение $500. Мы отказываемся и просим отдать деньги на благотворительность. Там же просим согласовать дату публикации и предлагаем молчать до выхода фикса.

1 июля — Telegram отвечает, что не может одобрить публичное раскрытие.

3 / 14 июля — Beta v6.9.4 и стабильная версия v7.0.1 выходят с фиксом.

Вот главное из их ответа: «We also have considered the possibility of a public disclosure but we cannot approve it as disclosing even the already addressed issues could put more Telegram users at risk in the future. For instance, if information about a vulnerability is made public, malicious actors may attempt to exploit it thereby causing financial harm to Telegram users.»

Перевод: «Мы также рассматривали возможность публичного раскрытия, но не можем его одобрить: раскрытие даже уже исправленных уязвимостей может в будущем подвергнуть риску больше пользователей Telegram. Например, если информация об уязвимости станет публичной, злоумышленники могут попытаться её эксплуатировать, причинив финансовый ущерб пользователям Telegram».

Переписка с Telegram Support, 01.07.2026 — отказ от баунти и запрет на post-fix раскрытие.

Переписка с Telegram Support, 01.07.2026 — отказ от баунти и запрет на post‑fix раскрытие.

Публичная политика баг‑баунти говорит о другом: если о проблеме рассказали публично до того, как её исправили, отчёт не получит вознаграждение. «Vulnerabilities that are disclosed to the public or to third parties before they are addressed are not eligible for our bug bounty program». Про то, что публикация после фикса требует одобрения Telegram, там нет ничего. NDA мы не подписывали.

Перечитайте их ответ: «even the already addressed issues» — «даже уже исправленных». Это не «подождите релиза». Фикс был написан за день до этого ответа. Это был отказ одобрить публикацию после фикса.

Почему мы всё равно публикуем

Три причины.

Люди. Старые файлы экспорта по‑прежнему опасны, а тихий патч никого не предупреждает — ни о переэкспорте чатов, ни о том, что старые файлы стоит открывать осторожнее. Именно из advisory пользователи узнают о проблеме.

Необновлённые клиенты. Фикс помогает только тем, кто обновляется, и он не переписывает файлы, которые уже лежат на диске.

Норма отрасли. Advisories и записи CVE дают защитникам понять, затронуты ли они, помогают специалистам по инцидентам знать, что искать, и делают возможной независимую проверку. Коммит с фиксом публичен; пояснения вокруг него — нет.

Для сравнения: Signal ведёт публичную страницу с описанием сообщённых проблем и именами исследователей. Chrome публикует release notes с номерами CVE и указанием исследователей. Apple добавляет номера CVE, когда может. Ни advisory от Telegram, ни публичной записи CVE по этой уязвимости до сих пор нет — при том, что коммит с фиксом лежит в открытом репозитории, и любой может его прочитать — но пояснений к нему нет.

Безопасность — это не отсутствие багов. Баги есть у всех. Это то, что вендор делает дальше: публикует advisory, присваивает CVE, даёт исследователям публиковать свои находки. Когда компания, построившая бренд на безопасности, говорит «нет» публичной документации уже исправленного бага, уместен вопрос: это защита пользователей или бренда?

Как мы тестировали

Мы сообщили о баге через официальную программу баг‑баунти Telegram, security@telegram.org. Всё тестировалось на наших собственных аккаунтах и в наших тестовых группах. Сетевой трафик PoC не выходил за пределы 127.0.0.1 — кроме стандартных вызовов Bot API, мы не трогали продакшн Telegram. От вознаграждения $500 мы отказались и попросили отдать его на благотворительность. Публикуем после выхода фикса и после того, как проверили его на релизных сборках. Рабочего эксплойта против живых систем в этой статье нет.

Скриншоты — из трёх отдельных контролируемых демонстраций, это не один непрерывный прогон. Все аккаунты и группы — наши. Баг исправлен.

Спасибо, что дочитали. Можете переслать статью коллеге.

Только обычной ссылкой. К пустым кнопкам у нас теперь вопросы.

Также хотим выразить благодарность Telegram за фикс. И отдельно — за заголовок этой статьи.

Денис Ростилов и Александр Ростилов, ExPatch Vulnerability Research

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.