Кратко. Контролируемый прокси мог одним ответом
407 Proxy Authentication Requiredзаставить Telegram Desktop и QtNetwork без предупреждения вовлечь системную NTLM-аутентификацию Windows. В лаборатории мы подтвердили relay до RCE с правами SYSTEM, отдельное раскрытие до 65 КБ heap-памяти за обмен и соответствие последующих патчей техническим деталям нашего конфиденциального отчёта.
«Наш опыт сформирован нашей миссией — защищать наших пользователей в условиях авторитарных режимов».
— Павел Дуров, 5 сентября 2024 года. Перевод ExPatch; оригинал.

Мы согласны с этими словами. Более того — мы им поверили.
Для человека, живущего в условиях цензуры, прокси Telegram — не просто пункт в настройках приложения. Иногда это последняя узкая тропа к независимой информации и свободному интернету. Но тропа к свободе превращается в ловушку, если способна незаметно раскрыть учётные данные, содержимое памяти или передать злоумышленнику контроль над устройством.
Обнаружив именно такую опасность, мы могли сначала обратиться в Qt, зафиксировать свой приоритет и ждать обычного цикла исправления. Мы поступили иначе. В ночь перед отправкой отчёта мы не спали: перепроверяли цепочку атаки, собирали технические доказательства и подробно описывали каждый этап, чтобы инженеры Telegram могли немедленно воспроизвести проблему. Уже 6 июня мы передали Telegram полный технический отчёт, явно обозначив его как конфиденциальный и запретив распространение без разрешения ExPatch LLC. Telegram контролировал приложение и мог быстрее всех перекрыть опасный путь — поэтому безопасность пользователей мы поставили выше сна, удобства и возможности сначала закрепить свой приоритет у Qt.
Telegram получил возможность защитить пользователей первым — и воспользовался ею. Однако вскоре соответствующее исправление появилось в Qt, тогда как нас не уведомили о координации, не подключили к взаимодействию с upstream, а имена исследователей и ExPatch так и не появились в публичной атрибуции. Была ли это обычная upstream-координация или нарушение доверия, благодаря которому Telegram вообще получил эту фору? В этой статье мы представим документы, код и хронологию, позволяющие читателю ответить на этот вопрос самостоятельно.
Это было не первое наше исследование Telegram Desktop. Ранее мы обнаружили и сообщили команде Telegram об XSS-уязвимости в функции экспорта чатов. Завершив ту работу, мы обратили внимание на другой компонент приложения — встроенную поддержку прокси, которой пользователи вынуждены доверять не только свои сообщения, но и безопасность устройства и корпоративной инфраструктуры.
Значение этой функции определил сам Telegram. В официальной публикации возможность настройки Proxy была представлена в разделе Free Speech и прямо названа anti-censorship tool. Команда Telegram тогда заявила: «Мы считаем своей ответственностью предоставлять технологии, защищающие право пользователей на приватность и свободу слова во всём мире» (источник (https://telegram.org/blog/admin-revolution), перевод ExPatch). Таким образом, Proxy — не второстепенная настройка приложения, а технология, которую Telegram публично сделал частью своей миссии по защите пользователей.
Основной анализ исходного кода проводился на Telegram Desktop 6.8.4 с Qt 6.11.1. Лабораторные демонстрации утечки памяти и полной RCE-цепочки, приведённые ниже, выполнялись на официальном немодифицированном клиенте Telegram Desktop 6.8.2.
От обработки прокси к NTLM
Исследование началось с анализа архитектуры прокси-подсистемы. Мы установили, что обработку соединений через HTTP- и SOCKS5-прокси Telegram Desktop делегирует библиотеке Qt (https://www.qt.io/). На этом этапе нас интересовала не конкретная библиотека или компания, а сама граница доверия: что произойдёт, если прокси-сервер окажется враждебным и получит возможность управлять ответами во время установления соединения? Именно этот сценарий стал отправной точкой дальнейшего аудита.
Почти месяц мы исследовали этот механизм с разных сторон: проверяли границы доверия, переходы между состояниями и возможные способы навязать клиенту неожиданное поведение. Большинство направлений постепенно упиралось в стену, и поверхность атаки казалась почти исчерпанной. Переломный момент наступил, когда мы обнаружили неожиданное поведение Qt: прямо во время обмена с прокси-сервером библиотека принимала требование переключить аутентификацию на NTLM. Современное прокси-соединение могло незаметно перейти на устаревший механизм, десятилетиями связанный с атаками класса NTLM Relay. Именно этот переход полностью изменил направление исследования.
Эта находка резко расширила модель угроз. До этого исследование ограничивалось логикой установления соединения через HTTP- и SOCKS5-прокси. Теперь же в цепочку включалась интегрированная аутентификация Windows: враждебный прокси-сервер мог заставить Qt инициировать NTLM-обмен от имени пользователя. Вопрос изменился — теперь требовалось понять, что именно получит атакующий после того, как приложение автоматически вовлечёт системные учётные данные в управляемый им обмен.
На верхнем уровне цепочка начинается предельно просто. Telegram Desktop устанавливает соединение через настроенный HTTP-прокси, а Qt отправляет ему запрос CONNECT. Враждебный прокси отвечает кодом 407 Proxy Authentication Required и указывает поддерживаемую схему: Proxy-Authenticate: NTLM. Qt воспринимает этот ответ как штатное требование аутентификации, автоматически запускает NTLM и отправляет первое сообщение рукопожатия — NTLM Type 1 (Negotiate). Всё это происходит без явного запроса согласия пользователя.
Следующим стал ключевой вопрос: какие проверки выполняет Qt перед тем, как доверить NTLM системные учётные данные пользователя? Поскольку атаки класса NTLM Relay известны уже несколько десятилетий, мы ожидали увидеть механизм, связывающий аутентификацию с конкретным прокси-сервером и не позволяющий повторно использовать тот же обмен на другом сервисе. Однако анализ реализации не выявил такой защиты: Qt не подтверждал ожидаемый SPN, не обеспечивал привязку к защищённому каналу через EPA и не требовал проверки целостности обмена посредством MIC. В результате контролирующий прокси атакующий мог не взламывать NTLM и не извлекать пароль — достаточно было переслать сообщения аутентификации другому сервису в реальном времени.
Это полностью взорвало нам мозг: давно забытый функционал NTLM оказался не спрятан за устаревшей корпоративной конфигурацией — его можно было затриггерить прямо посреди обычного обмена с прокси-сервером. После подключения контролируемого HTTP-прокси атакующему требовался всего один штатно выглядящий ответ: 407 Proxy Authentication Required с заголовком Proxy-Authenticate: NTLM. Никакой сложной цепочки для запуска механизма не требовалось — один заголовок, и Qt автоматически вовлекал системные учётные данные пользователя в NTLM-обмен.
Как Qt запускает NTLM без подтверждения
Чтобы понять, почему одного ответа прокси достаточно для запуска NTLM, мы проследили весь путь выполнения — от обработки ответа 407 до формирования сообщения аутентификации. Цепочка состоит из трёх уровней.
Уровень 1 — Qt принимает ответ 407
Telegram Desktop настраивает прокси через QTcpSocket::setProxy. Затем в qhttpsocketengine.cpp:562–599 Qt обрабатывает ответ прокси на запрос CONNECT. При получении статуса 407 Proxy Authentication Required управление передаётся встроенному механизму аутентификации:
} else if (statusCode == 407) { // :562 if (d->authenticator.isNull()) d->authenticator.detach(); priv = QAuthenticatorPrivate::getPrivate( d->authenticator ); const auto headers = d->reply->header(); priv->parseHttpResponse( headers, true, // isProxy=true d->proxy.hostName() ); // :567 }
На этом уровне NTLM ещё не выбран. Важно другое: Qt передаёт все полученные от прокси заголовки в parseHttpResponse() с параметром isProxy=true. Следовательно, дальнейший выбор схемы аутентификации зависит от содержимого контролируемого атакующим заголовка Proxy-Authenticate. Следующий уровень показывает, как значение NTLM превращается из текста в HTTP-ответе в команду запустить системную аутентификацию.
Уровень 2 — Qt выбирает схему аутентификации
Решение о переходе на NTLM принимается внутри QAuthenticatorPrivate::parseHttpResponse() в qauthenticator.cpp:466–558. Поскольку функция была вызвана с параметром isProxy=true, Qt анализирует именно контролируемый прокси-сервером заголовок Proxy-Authenticate:
const auto search = isProxy ? QHttpHeaders::WellKnownHeader::ProxyAuthenticate : QHttpHeaders::WellKnownHeader::WWWAuthenticate; // isProxy=true → Proxy-Authenticate // isProxy=false → WWW-Authenticate for (const auto ¤t : headers.values(search)) { if (method < Basic && str.startsWith("basic"_L1, Qt::CaseInsensitive)) { method = Basic; } else if (method < Ntlm && str.startsWith("ntlm"_L1, Qt::CaseInsensitive)) { method = Ntlm; // :488–490 headerVal = QByteArrayView(current).mid(5); } else if (method < DigestMd5 && str.startsWith("digest"_L1, Qt::CaseInsensitive)) { // ... } else if (method < Negotiate && str.startsWith("negotiate"_L1, Qt::CaseInsensitive)) { // ... } } // ... case Ntlm: case Negotiate: // work is done in calculateResponse() break;
Именно здесь значение NTLM из контролируемого атакующим HTTP-заголовка становится внутренним состоянием method = Ntlm. Условие method < Ntlm позволяет NTLM заменить схемы с меньшим приоритетом, включая None и Basic.
Дополнительная проблема заключается в том, что перед разбором очередного ответа значение method сбрасывается. Поэтому прокси может потребовать NTLM не только в первом ответе, но и при последующем 407, изменив схему уже в процессе установления соединения. После выбора Ntlm функция не выполняет дополнительных проверок доверия к прокси — дальнейшее формирование системного ответа откладывается до вызова calculateResponse().
Уровень 3 — Qt начинает NTLM-обмен без уведомления приложения
После выбора схемы управление возвращается в qhttpsocketengine.cpp:574–599:
if (priv->phase == QAuthenticatorPrivate::Done || (priv->phase == QAuthenticatorPrivate::Start && (priv->method == QAuthenticatorPrivate::Ntlm || priv->method == QAuthenticatorPrivate::Negotiate))) { if (priv->phase == QAuthenticatorPrivate::Start) priv->phase = QAuthenticatorPrivate::Phase1; // :578–580 if ((priv->method != QAuthenticatorPrivate::Ntlm && priv->method != QAuthenticatorPrivate::Negotiate) || credentialsWasSent) { proxyAuthenticationRequired( d->proxy, &d->authenticator ); // :596–597 } }
Ключевым оказалось условие перед вызовом proxyAuthenticationRequired(). При обычной схеме Qt отправляет этот сигнал, позволяя приложению запросить учётные данные или отказаться от аутентификации. Но при первом переходе на NTLM или Negotiate, пока credentialsWasSent == false, всё условие становится ложным — сигнал не отправляется.
В результате Telegram Desktop не получает возможности подтвердить переход, предупредить пользователя или заблокировать его. Qt самостоятельно переводит аутентификатор в Phase1, вызывает calculateResponse(), обращается к Windows SSPI за системными SSO-учётными данными и отправляет прокси сообщение NTLM Type 1 (Negotiate). Именно поэтому переход происходит молча — не только для пользователя, но и для самого приложения.
Почему этот переход позволяет провести NTLM Relay? Критическая проблема заключается не в уязвимости самого SSPI, а в том, как Qt использует его внутри автоматической прокси-аутентификации. Если имя пользователя в QAuthenticator не задано, Qt не останавливает соединение и не запрашивает учётные данные у приложения. Вместо этого библиотека обращается к Windows SSPI и получает стандартные учётные данные текущей logon-сессии. В доменной среде это означает, что контролируемый атакующим прокси может без ведома пользователя инициировать отправку NTLM-токенов его корпоративной учётной записи.
Этап 1 — calculateResponse() выбирает системную аутентификацию
Первое подтверждение находится в qauthenticator.cpp:585–592. Когда прокси требует NTLM, а challenge ещё не получен, Qt начинает первую фазу рукопожатия:
case QAuthenticatorPrivate::Ntlm: if (challenge.isEmpty()) { #if QT_CONFIG(sspi) QByteArray phase1Token; if (user.isEmpty()) { // Only pull from system if no user was // specified in authenticator phase1Token = qSspiStartup( this, method, host ); } // ... #endif } // ... break;
Условие challenge.isEmpty() означает, что Qt формирует первое сообщение NTLM-обмена. Затем проверяется user.isEmpty(): если приложение не указало имя пользователя, выполнение не прерывается и управление не возвращается Telegram Desktop. Вместо этого вызывается qSspiStartup().
Комментарий разработчиков Qt прямо описывает это поведение: Only pull from system if no user was specified in authenticator — если пользователь не был явно указан в аутентификаторе, учётные данные необходимо получить из системы. Именно здесь контролируемое прокси-сервером требование NTLM соединяется с учётной записью текущего пользователя Windows.
Этап 2 — Получение дескриптора системных учётных данных в qSspiStartup() (qauthenticator.cpp:1566–1607)
SEC_WINNT_AUTH_IDENTITY auth; auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; bool useAuth = false; if (method == QAuthenticatorPrivate::Negotiate && !ctx->user.isEmpty()) { // Явные учётные данные используются только // для Negotiate с непустым user useAuth = true; } SECURITY_STATUS secStatus = pSecurityFunctionTable->AcquireCredentialsHandle( nullptr, L"NTLM", SECPKG_CRED_OUTBOUND, nullptr, useAuth ? &auth : nullptr, nullptr, nullptr, &ctx->sspiWindowsHandles->credHandle, &expiry );
Переменная useAuth становится истинной только для метода Negotiate, когда имя пользователя было задано явно. В пути NTLM функция передаёт в AcquireCredentialsHandle() значение nullptr вместо структуры SEC_WINNT_AUTH_IDENTITY. Согласно модели SSPI, это означает использование стандартных исходящих учётных данных текущего контекста безопасности Windows. В доменной среде ими обычно становятся учётные данные вошедшего в систему корпоративного пользователя.
При этом Qt не получает пароль или NTLM-хеш в открытом виде. SSPI возвращает непрозрачный credential handle, с помощью которого далее формируются сообщения NTLM-аутентификации. Это важное уточнение: уязвимость позволяет атакующему перенаправить готовую аутентификацию пользователя.
Этап 3 — NTLM-контекст не привязывается к ожидаемому прокси
Полученный credential handle передаётся в qSspiContinue(), где Qt вызывает Windows SSPI для продолжения рукопожатия:
InitializeSecurityContext( &ctx->sspiWindowsHandles->credHandle, ..., ISC_REQ_ALLOCATE_MEMORY, // единственный запрошенный флаг ... );
В этом вызове Qt запрашивает только автоматическое выделение памяти для выходного токена. При этом отсутствуют три механизма, способные затруднить перенаправление аутентификации:
флаг ISC_REQ_INTEGRITY не установлен, поэтому Qt не требует защиты целостности создаваемого контекста;
буфер SECBUFFER_CHANNEL_BINDINGS не передаётся, а значит NTLM-обмен не связывается с конкретным защищённым каналом через EPA/Channel Binding;
для прямого метода NTLM значение targetNameW остаётся пустым, поэтому контекст не привязывается к ожидаемому сервису посредством SPN.
В совокупности это означает, что сформированный SSPI-контекст не связан с тем прокси-сервером, которому, как полагает пользователь, он аутентифицируется. В протестированной конфигурации Qt генерировал сообщения NTLM Type 1 и NTLM Type 3 с ответом доменного пользователя, которые контролирующий прокси мог в реальном времени перенаправить другому корпоративному сервису.
Воздействие и поверхность атаки
Итог оказался пугающе масштабным: потенциальной точкой входа становилась практически любая корпоративная Windows-система, на которой пользователь вошёл под доменной учётной записью. Речь идёт не об экзотической конфигурации, а об огромном классе корпоративных устройств по всему миру.
Если имя пользователя для прокси не было указано, Qt автоматически обращался к SSPI, получал дескриптор учётных данных текущей Windows-сессии и формировал от их имени сообщения NTLM Type 1 и NTLM Type 3. Атакующему не требовалось знать пароль или извлекать NTLM-хеш: он перенаправлял готовый NTLMv2-ответ на выбранный корпоративный сервис, который воспринимал соединение как аутентифицированное самой жертвой.
Что это даёт? На уязвимой relay-цели — полный карт-бланш в пределах прав доменного пользователя. В зависимости от выбранного сервиса и привилегий жертвы последствия могли начинаться с доступа к корпоративным данным и заканчиваться удалённым выполнением кода или компрометацией доменной инфраструктуры. И всё это запускалось одним ответом 407 Proxy Authentication Required.
Соберём все этапы в единую цепочку. Прокси атакующего не взламывает NTLM и не извлекает пароль пользователя — он действует как посредник, пересылая сообщения аутентификации между Telegram Desktop и выбранным корпоративным сервисом. Windows SSPI формирует корректный NTLMv2-ответ от имени доменного пользователя, но получает его соединение, контролируемое атакующим. Если целевой сервис принимает NTLM и не применяет relay-защиту, он аутентифицирует эту сессию как сессию жертвы. Полная последовательность обмена показана ниже.

Двухэтапная эскалация NTLM → Negotiate (P1): два NTLMv2-ответа за один сеанс
Даже явное указание логина и пароля для прокси не защищало пользователя. Контролируемый сервер мог провести две последовательные аутентификации в рамках одного соединения:
1. 407 + Proxy-Authenticate: NTLM │ └─► Qt использует явно заданные учётные данные прокси и формирует NTLMv2-ответ №1 2. Прокси отклоняет первую аутентификацию │ └─► qhttpsocketengine сбрасывает authenticator user становится пустым 3. 407 + Proxy-Authenticate: Negotiate │ └─► qSspiStartup() запрашивает стандартные credentials SSPI Negotiate переключается на NTLM Qt формирует NTLMv2-ответ №2 от имени доменного пользователя Windows
Таким образом, атакующий получал за один сеанс два различных NTLMv2-ответа: первый — на основе явно заданных учётных данных прокси, второй — на основе системной учётной записи Windows. Оба ответа оказывались под контролем атакующего, а ответ доменного пользователя мог быть немедленно перенаправлен выбранному корпоративному сервису в рамках NTLM Relay.
Этот сценарий существенно расширяет класс затронутых пользователей. Уязвимым оказывался не только клиент с пустым полем username: отклонив первую аутентификацию и навязав повторный обмен через Negotiate, прокси мог открыть путь к системным credentials даже после того, как пользователь явно указал отдельный логин и пароль для прокси.
Вектор SOCKS5-in-tunnel (V3): внедрение 407 внутрь активного туннеля
Вектор V3 не требовал перехода по tg://proxy-ссылке. Он затрагивал доменные Windows-системы, на которых корпоративный прокси централизованно настраивался через GPO, WPAD или PAC-файл.
Ключевое отличие V3 от базового вектора заключалось в точке внедрения ответа 407. После установления SOCKS5-туннеля контролируемый прокси видел незашифрованный HTTP-запрос Telegram Desktop — включая заголовки, URL и тело MTProto POST — и мог подменить ответ сервера:
HTTP/1.1 407 Proxy Authentication Required Proxy-Authenticate: NTLM
Qt интерпретировал этот ответ как штатное требование прокси-аутентификации и автоматически запускал SSPI-путь.

Вектор V3: контролируемый SOCKS5-прокси внедряет 407 Proxy Authentication Required внутрь активного туннеля, после чего Qt автоматически получает через SSPI NTLMv2-ответ доменного пользователя.
В этом сценарии атакующему не требовалось настраивать прокси-аутентификацию или убеждать пользователя вводить отдельный пароль. SOCKS5-прокси уже присутствовал в системе и применялся без непосредственного участия пользователя.
В лабораторной среде с Windows 10, присоединённой к домену, одна тестовая сессия Telegram Desktop сформировала более 230 сообщений NTLM Type 3. Повторные попытки Qt многократно увеличивали количество доступного атакующему аутентификационного материала и возможностей для его перенаправления.
В отличие от базового сценария с tg://proxy, этот вектор был направлен прежде всего на корпоративных пользователей, чьи прокси-настройки распространялись централизованно. Пользователь мог даже не знать, что его трафик проходит через инфраструктуру, способную инициировать NTLM-аутентификацию от его имени.
Корень уязвимости находится в QtNetwork
Дальнейший анализ показал, что Telegram Desktop был лишь одним из способов достижения уязвимого кода. Первопричина находилась в общей реализации HTTP-аутентификации модуля QtNetwork — qauthenticator.cpp — и не ограничивалась ответами 407 Proxy Authentication Required.
Тот же путь можно было запустить без прокси. Если контролируемый атакующим HTTP-сервер отвечал на обычный незашифрованный запрос кодом 401 Unauthorized и заголовком:
WWW-Authenticate: NTLM
Qt воспринимал его как штатное требование аутентификации. На присоединённой к домену Windows-системе, при отсутствии явно заданных credentials, библиотека без диалога с пользователем обращалась к SSPI и начинала NTLM-обмен от имени текущей учётной записи.
Таким образом, прокси был не обязательным условием эксплуатации, а лишь особенно опасной точкой входа в Telegram Desktop. Уязвимый механизм находился глубже — в общем HTTP-клиентском стеке Qt, которым пользуется множество приложений.
Потенциальная поверхность атаки за пределами Telegram
Получив сообщения NTLM от Qt-клиента, сервер атакующего мог перенаправить рукопожатие на другой сервис, принимающий NTLM: Exchange, AD CS, SMB или LDAP. При отсутствии необходимых relay-защит целевой сервис открывал контролируемую атакующим сессию от имени доменного пользователя.
Уязвимый код использовался двумя основными путями:
QNetworkAccessManager — при обработке ответа 401 WWW-Authenticate: NTLM от HTTP-сервера;
QTcpSocket через прокси — при обработке ответа 407 Proxy-Authenticate: NTLM.
Для запуска первого пути было достаточно, чтобы Qt-приложение выполнило доступный атакующему или перехватываемый незашифрованный HTTP-запрос. Наличие отдельного прокси при этом не требовалось.
Уязвимость не ограничивалась теоретической общей функцией QtNetwork. После обнаружения первопричины мы воспроизвели тот же путь атаки в нескольких независимых Qt-приложениях: контролируемый ответ 401 или 407 запускал автоматическое обращение к SSPI и приводил к формированию NTLM Type 3 от имени текущего пользователя Windows. Эти тесты подтвердили, что Telegram Desktop был не единственным уязвимым продуктом, а лишь первой исследованной точкой входа в значительно более широкую поверхность атаки. Ниже приведены как приложения, на которых цепочка была подтверждена практически, так и дополнительные продукты, в которых мы обнаружили достижимый уязвимый путь QtNetwork.
Где ещё достигается уязвимый код
Мы не ограничились анализом исходного кода Qt и Telegram Desktop. Это была уже не теоретическая экстраполяция по общему API: тот же уязвимый примитив мы практически воспроизвели в qBittorrent, Nextcloud Desktop и Electrum.
Практически подтверждённые приложения
qBittorrent 4.6.3 с Qt 6.4.2. Контролируемый прокси переключил приложение с Basic на NTLM ответом 407 Proxy Authentication Required — без уведомления пользователя. Полный NTLM-handshake завершился формированием NtChallengeResponse размером
65 076 байт, из которых около 64 876 байт приходилось на посторонние данные из heap.
Nextcloud Desktop 3.11.0 с Qt 5.15.13. За одну тестовую сессию приложение выполнило четыре последовательных NTLM-обмена. Каждый ответ достигал 65 076 байт, поэтому суммарно за одну сессию раскрывалось около 260 КБ памяти. В дампах
находились полные HTTP-запросы, WebDAV- и API-маршруты Nextcloud, служебные заголовки и NTLM-токены предыдущих обменов.
Electrum 4.7.2 с Qt 6.11.0. Здесь обнаружился отдельный прямой путь, не требовавший прокси. Контролируемое описание Bitcoin- или LNURL-счёта отображалось QML-компонентом как rich text, а внедрённый <img src="http://…"> заставлял QNetworkAccessManager выполнить запрос к серверу атакующего. В ответ на 401 WWW-Authenticate: NTLM Qt автоматически отправлял неподписанный NTLM Type 1, причём Electrum не подключал обработчик authenticationRequired, способный остановить этот обмен. Достижимость уязвимого кода и автоматическое начало NTLM-аутентификации были подтверждены экспериментально; завершение Type 3 через SSPI и последующий relay требуют Windows-машины в доменном окружении.
Прямой HTTP-вектор — 401 WWW-Authenticate: NTLM
В этом сценарии прокси не требуется. Достаточно, чтобы Qt-приложение выполнило HTTP-запрос к контролируемому серверу, который ответит требованием NTLM-аутентификации. Помимо подтверждённого в Electrum пути, такая поверхность присутствует в клиентах WebDAV, RSS, внутренних API, картографических сервисов и репозиториев обновлений. К ней относятся Nextcloud и ownCloud Desktop, KDE/KIO, QGIS, RSSGuard, QuiteRSS и другие приложения, передающие HTTP-запросы через QNetworkAccessManager.
Прокси-вектор — 407 Proxy-Authenticate: NTLM
Этот путь был практически подтверждён в Telegram Desktop, qBittorrent и Nextcloud Desktop. Та же поверхность возникает в Mumble и других Qt-приложениях при использовании HTTP- или SOCKS5-прокси. В корпоративной среде она дополнительно охватывает приложения, работающие через прокси, централизованно распространяемый посредством GPO, WPAD или PAC-файла, включая Qt Creator, Wireshark и KeePassXC.
Таким образом, речь шла не об изолированной ошибке Telegram Desktop и не о предположении, основанном только на чтении исходного кода. Один дефект QtNetwork был практически воспроизведён в нескольких независимых продуктах, разных версиях Qt и двух точках входа — через ответы 401 и 407. Telegram оставался главным объектом нашего исследования, однако фактический радиус воздействия обнаруженной первопричины оказался значительно шире одного приложения.
Второй примитив: раскрытие heap-памяти
Масштаб проблемы оказался значительно шире Telegram, однако именно Telegram оставался главной целью нашего исследования. Мы решили не останавливаться на принудительной NTLM-аутентификации и relay-атаке. Если недоверенный сервер мог без участия пользователя активировать редко используемый код аутентификации и управлять содержимым NTLM Type 2, следовало проверить, насколько безопасно Qt разбирает остальные контролируемые атакующим поля.
Именно там мы обнаружили второй независимый примитив — чтение неинициализированной heap-памяти в qExtractServerTime():
static QByteArray qExtractServerTime(const QByteArray &targetInfoBuff) { QByteArray timeArray; QDataStream ds(targetInfoBuff); ds.setByteOrder(QDataStream::LittleEndian); quint16 avId, avLen; ds >> avId; ds >> avLen; while (avId != 0) { if (avId == AVTIMESTAMP) { timeArray.resize(avLen); // длина контролируется сервером ds.readRawData(timeArray.data(), avLen); // результат короткого чтения не проверяется } // ... } }
В NTLM Type 2 сервер управляет содержимым targetInfo, включая идентификатор MsvAvTimestamp и 16-битное поле avLen. Мы указывали длину 65 000 байт, но передавали только восьмибайтовое значение timestamp. Qt выделял буфер заявленного размера, записывал в него восемь доступных байт и не проверял, что readRawData() выполнил короткое чтение. Оставшиеся 64 992 байта сохраняли прежнее содержимое heap. Затем Qt включал весь буфер — вместе с неинициализированной областью — в NTLMv2-ответ и отправлял его серверу атакующего.
Это превратило обычный NTLM-обмен в удалённый и повторяемый примитив чтения памяти. В полученных дампах мы подтвердили утечку указателей кода и метаданных heap, достаточных для обхода ASLR, сетевых адресов, названий VPN-клиентов и адаптеров, HTTP-заголовков, а также предыдущих NTLM-токенов. За 100 повторных обменов, занимавших около пяти минут, прокси мог получить приблизительно 6,5 МБ памяти процесса Telegram Desktop.

Механизм утечки: Qt выделяет 65 000 байт по контролируемому avLen, записывает только восемь байт timestamp и включает оставшиеся 64 992 байта heap-памяти в NTLM Type 3.
Что находилось в утекшей памяти
Чтобы оценить практическую ценность примитива, мы развернули контролируемый прокси, извлекли неинициализированную область из NTLMv2-ответов и исследовали результаты повторных обменов.

По отдельности эти фрагменты могли выглядеть случайным мусором. Вместе они образовывали подробный профиль устройства и его владельца: имя пользователя, рабочий домен и hostname из NTLM, используемый VPN-клиент, виртуализацию, внутреннюю адресацию и корпоративную сетевую инфраструктуру.
Но последствия не ограничивались деанонимизацией. Уязвимость без разбора передавала содержимое повторно используемых heap-буферов. В ходе контролируемого heap-grooming мы заранее помещали в них тестовые пароли и маркеры API-секретов, а затем обнаруживали эти значения в NTLMv2-ответе, отправленном прокси. По тому же механизму могли раскрываться API-ключи, токены сессий, cookie, заголовки Authorization и другие секреты, недавно находившиеся в памяти процесса. В последующих утечках также появлялись ранее сформированные NTLM Type 3.
Важно: пароль доменного пользователя не извлекался непосредственно из NTLM. Открытые пароли могли утекать как содержимое повторно использованного буфера, независимо от самого NTLM-протокола.
Примитив был повторяемым. Если первый ответ не содержал нужных данных, атакующий мог инициировать следующий NTLM-цикл, не нарушая видимую работу прокси. В нашем эксперименте 100 обменов примерно за пять минут раскрыли около 6,5 МБ памяти Telegram Desktop. Обнаруженные указатели кода и метаданные heap также позволяли обходить ASLR и подготавливать эксплуатацию других ошибок повреждения памяти.
Особенно тревожно, что каналом утечки становилась функция, предназначенная помогать пользователю обходить ограничения и сохранять доступ к информации. Прокси продолжал работать, Telegram не показывал предупреждений, а пользователь не видел, что оператору вредоносного прокси последовательно передаются фрагменты памяти — включая пароли, API-секреты и данные, способные раскрыть его личность или предоставить доступ к связанным системам.
Видеодемонстрация
PoC 01: раскрытие heap-памяти и NTLM relay через Telegram Desktop и QtNetwork.
Полная RCE-цепочка: лабораторное подтверждение
Чтобы проверить максимальный практический ущерб, мы построили изолированный стенд с доменом Active Directory, Windows Server 2022 и Enterprise CA с доступным AD CS Web Enrollment.
На стороне жертвы использовался официальный Telegram Desktop 6.8.2 без каких-либо изменений исходного кода, исполняемого файла, Qt или механизмов аутентификации. Клиент работал в штатной конфигурации. Единственным действием пользователя было добавление прокси предусмотренным Telegram способом. Вся атакующая логика находилась на стороне контролируемого прокси и лабораторной инфраструктуры.
После подключения Telegram к прокси цепочка выполнялась автоматически:

Полная лабораторная цепочка: от добавления tg://proxy до получения SYSTEM на узлах, где скомпрометированная учётная запись обладала административными правами.
Видеодемонстрация
PoC 02: полная лабораторная цепочка NTLM relay через AD CS до удалённого выполнения кода с правами SYSTEM.
На стенде все 9 из 9 попыток relay к AD CS завершились успешной аутентификацией и выпуском сертификата. Поскольку тестовая учётная запись обладала правами Domain Admin, мы получили NT AUTHORITY\SYSTEM как на рабочей станции, так и на контроллере домена. Полная цепочка занимала около 30–35 секунд и не требовала подбора или восстановления пароля.
Уровень последующего доступа определялся правами скомпрометированной учётной записи: обычный пользователь не превращался автоматически в администратора домена. Однако relay учётной записи администратора, привилегированного сотрудника или сервисного аккаунта мог непосредственно привести к удалённому выполнению кода и компрометации соответствующих систем. Наш эксперимент подтвердил верхнюю границу воздействия: полный захват домена через штатный, немодифицированный Telegram Desktop после добавления контролируемого прокси.
Выданный сертификат создавал отдельный механизм сохранения доступа. Смена пароля не отзывала его автоматически: при сроке действия сертификата в один–два года атакующий мог продолжать проходить аутентификацию до истечения срока, явного отзыва сертификата или блокировки учётной записи.
Почему это уязвимость Telegram Desktop, а не только Qt
Сводить вопрос к тому, в каком исходном файле находилась ошибка, — значит игнорировать всю цепочку эксплуатации. Библиотечная первопричина действительно находилась в QtNetwork. Но атака начиналась не в абстрактном приложении Qt: она начиналась в официальном Telegram Desktop, после добавления прокси штатным механизмом tg://proxy.
Именно Telegram Desktop делал дефект практически достижимым:
принимал и сохранял предоставленный пользователем прокси;
передавал его в Qt без запрета NTLM и Negotiate;
не предоставлял пользователю возможности увидеть или отклонить выбранную прокси-сервером схему;
позволял недоверенному прокси активировать SSPI и получить NTLMv2-ответ;
передавал через этот же канал фрагменты памяти процесса Telegram Desktop.
Это не просто присутствие уязвимой зависимости. Уберите любой из ключевых продуктовых элементов — tg://proxy, автоматическое сохранение прокси или отсутствие запрета NTLM — и описанная цепочка через Telegram прекращается. Следовательно, решения Telegram были необходимой частью эксплуатации, а не случайным окружением библиотечной ошибки.
Правила bug bounty Telegram также не поддерживают попытку вынести такой отчёт за пределы scope. В них прямо сказано:
“Generally, any Telegram-owned or operated app, web service, domain, server and protocol that either handles or stores private user data is in scope.” Telegram Bug Bounty Program (https://core.telegram.org/bug-bounty)
Telegram Desktop является официальным приложением Telegram и обрабатывает частные данные пользователей. В нашем случае через него раскрывались NTLMv2-ответы, сведения, позволяющие идентифицировать пользователя, и содержимое памяти самого процесса. При наличии необходимых корпоративных условий подтверждённая relay-цепочка приводила к выполнению кода с правами скомпрометированной учётной записи.
Поэтому здесь нет противоречия: Qt отвечал за дефект библиотеки, а Telegram — за уязвимый путь внутри поставляемого им продукта. Исправление upstream было необходимо, но оно не отменяло ни принадлежность Telegram Desktop к заявленному scope, ни ответственность за защиту пользователей официального клиента.
Ответственное раскрытие и хронология
Сорок дней исследования
Изначально нашей целью был Telegram Desktop. Но чем глубже мы разбирали обработку прокси-аутентификации, тем дальше исследование уходило за границы самого приложения — сначала в QtNetwork, затем в SSPI и инфраструктуру корпоративной аутентификации. Так поиск одной уязвимости Telegram превратился в анализ целого класса ошибок, затрагивавшего экосистему Qt.
Мы потратили на эту работу более сорока дней: изучали исходный код, тестировали разные версии Qt, проводили фаззинг, строили доменный стенд и проверяли цепочки эксплуатации на штатном Telegram Desktop. К этому моменту у нас было два независимых результата. Первый — практически подтверждённая NTLM relay-цепочка, которая в лабораторном домене приводила к SYSTEM. Второй — повторяемое чтение до 65 КБ heap-памяти, раскрывавшее секреты и указатели, пригодные для обхода ASLR.
Эти результаты важно не смешивать. Relay уже обеспечивал RCE в подходящем корпоративном окружении. Для отдельной универсальной клиентской RCE-цепочки, не зависящей от Active Directory и прав доменной учётной записи, нам требовался дополнительный примитив записи или управления памятью.
К концу исследования мы были физически истощены. Нам хотелось получить от Telegram техническую обратную связь, понять, правильно ли команда оценивает серьёзность находок, и затем продолжить поиск недостающего примитива. Одновременно мы готовили отдельное обращение в Qt.
Но ждать означало оставлять пользователей Telegram под угрозой. Поэтому мы решили сначала дать Telegram возможность немедленно перекрыть опасный путь на уровне приложения. Не прерывая работу и не ложась спать, мы собрали полный 21-страничный отчёт: с анализом исходного кода, PoC, видео, утечкой памяти, деанонимизацией и лабораторно подтверждённой RCE-цепочкой. Затем обозначили документ как конфиденциальный и передали его Telegram.
6 июня: полный отчёт передан Telegram
После ночи непрерывной проверки и подготовки материалов 6 июня 2026 года мы направили Telegram не краткое описание и не предварительную гипотезу, а законченный 21-страничный технический отчёт EXPATCH-2026-TG-002. В переданный пакет входили анализ исходного кода, условия эксплуатации, PoC, видеодемонстрация, результаты лабораторных испытаний и полная RCE-цепочка.
Отдельный раздел отчёта уже тогда описывал утечку памяти в qExtractServerTime(): контролируемое сервером поле MsvAvTimestamp.avLen, увеличение QByteArray через timeArray.resize(avLen), неполное чтение readRawData() и последующую отправку непрочитанного содержимого heap внутри NTLMv2-ответа. На странице 20 мы предложили конкретное исправление: проверять avLen по оставшемуся размеру буфера и принимать timestamp только ожидаемой длины — восемь байт.

На этой странице зафиксирован весь путь данных: сервер объявляет AVTIMESTAMP длиной 65 000 байт, Qt заполняет только восемь байт выделенного буфера, а затем включает оставшиеся 64 992 байта в отправляемый NTLM Type 3. Там же приведены фрагмент уязвимого кода и категории данных, обнаруженных во время практического тестирования.

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

Существование PoC на дату обращения отдельно подтверждается карточкой архива poc.zip в Google Drive. Для файла размером 151 МБ указаны две отметки: Created — Jun 6, 2026 и Modified — Jun 6, 2026. Дата Opened — Sep 17, 2026 относится только к последующему открытию файла и не изменяет первоначальную датировку.

Само по себе это изображение не доказывает скачивание архива получателем. Однако вместе со скриншотом письма, содержащим ссылку на poc.zip, оно подтверждает, что полный PoC существовал и был доступен Telegram уже в день обращения.
В совокупности эти материалы показывают, что Telegram получил не абстрактное сообщение об NTLM. Ему был передан полный пакет исследования: датированный технический отчёт, точное описание heap leak, воспроизводимый PoC, видео, готовые меры исправления и сведения об авторах.
Хронологию дополнительно подтверждают метаданные исходного PDF. Его обложка датирована 5 июня, а поле CreationDate содержит время 6 июня 2026 года, 04:04:01 UTC — примерно за шесть часов до отправки показанного письма. SHA-256 файла:
c2c88a4db92a9fb06cd64a2efec3e337714a0ecdc32526c6a65b4dc477173f6c
На обложке документ был прямо обозначен как CONFIDENTIAL и передан в рамках Coordinated Responsible Disclosure с требованием не распространять его без разрешения ExPatch LLC. Мы обратились к Telegram раньше, чем завершили отдельное обращение в Qt, поскольку Telegram мог немедленно перекрыть наиболее опасный путь на уровне собственного приложения.

7 июня: Telegram получил полностью работоспособный exploit
Первоначальным отчётом раскрытие не ограничилось. Уже 7 июня мы направили Telegram дополнительное письмо с усовершенствованным sspi_rce_v3.py — 694-строчным zero-password end-to-end exploit — и видеозаписью полной атаки. Это была не концептуальная демонстрация и не набор разрозненных техник: все необходимые учётные данные автоматически перенаправлялись либо извлекались из результатов relay-цепочки.
В письме были отдельно описаны последствия для обычного доменного пользователя: выпуск клиентского сертификата через AD CS, доступ к Exchange OWA и ADFS, перемещение на компьютеры, где жертва обладала локальными административными правами, а при небезопасной конфигурации шаблонов сертификатов — повышение привилегий. Для сценария с Domain Admin демонстрация заканчивалась получением NT AUTHORITY\SYSTEM на рабочей станции и контроллере домена; дальнейшие действия могли привести к полной компрометации домена.
Видеозапись последовательно показывала вывод контролируемого прокси, NTLM relay, выпуск сертификата, PKINIT-аутентификацию и появление командной строки на рабочем столе жертвы. Telegram получил не только описание возможного ущерба, но и полностью работоспособную реализацию, воспроизводившую всю цепочку на изолированном стенде против штатного, немодифицированного Telegram Desktop.

Дополнительное раскрытие от 7 июня: сценарии эксплуатации, 694-строчный sspi_rce_v3.py, видеозапись полной цепочки и прикреплённый архив poc2.zip. В письме также зафиксировано обязательство ExPatch не публиковать PoC и видео до выпуска исправлений.

В совокупности два скриншота связывают дату, содержимое и факт передачи: 7 июня Telegram располагал анализом первопричины, эксплуатационным кодом и видеодоказательством полной цепочки. Иными словами, речь шла уже не о гипотетическом риске, который ещё требовалось подтвердить, а о воспроизводимой компрометации, продемонстрированной от первого сетевого ответа до выполнения команд на целевой системе.
8 июня: подтверждение расследования и два точечных патча
8 июня в 05:08 Telegram Support подтвердил получение материалов и сообщил:
“The team is investigating your research, and we will contact you soon.”

В тот же день публичная история используемого Telegram Desktop репозитория зависимостей датирует появление двух новых коммитов:
в 13:23:04 UTC — патч для Qt 5 (https://github.com/desktop-app/patches/commit/d92000b4ad84cf8c0fbd161eb32f345ca470da6b);
в 13:27:01 UTC — патч для Qt 6 (https://github.com/desktop-app/patches/commit/3129a4798e9b69033b5a0a854d4c660b8c719e22).
Автором обоих изменений указан John Preston. По времени Нью-Йорка это 09:23 и 09:27 — примерно через четыре часа после ответа Telegram.
Особенно важно не только время появления изменений, но и их содержание. Патч для Qt 6 был добавлен новым файлом qtbase_6.11.1/0038-disable-proxy-ntlm.patch и одновременно закрывал две независимые проблемы, описанные в нашем отчёте: принудительную NTLM/Negotiate-аутентификацию через прокси и утечку памяти в qExtractServerTime().

Изменение не ограничивалось общей рекомендацией «усилить NTLM». В нём появилась проверка остатка буфера avLen > end - p, требование avLen == 8, возврат и запись ровно восьми байт, а также отдельная функция qNtlmBufferFits() для проверки границ targetName и targetInfo. Это те же поля, условия и защитные меры, которые были названы в нашем отчёте.
Патч также охватывал оба сетевых пути Qt, использовавшихся Telegram: QHttpNetworkConnection и QHttpSocketEngine. При обнаружении NTLM или Negotiate аутентификатор сбрасывался, соединение завершалось с ProxyAuthenticationRequiredError, а NTLMv2-ответ больше не отправлялся прокси.
Совпадение затрагивало не только общую тему, но и конкретную функцию qExtractServerTime(), атакующее поле MsvAvTimestamp.avLen, ошибочное поведение при коротком чтении, точную допустимую длину timestamp и предложенный нами запрет NTLM/ Negotiate для прокси. Всё это появилось в одном новом патче, история которого датирована днём подтверждения расследования.
Коммитные даты сами по себе не устанавливают точный момент публичной публикации патчей или их доставки пользователям. Однако история репозитория показывает, что 8 июня — через два дня после передачи отчёта и примерно через четыре часа после ответа Telegram — были подготовлены точечные исправления именно тех двух независимых проблем, которые содержались в EXPATCH-2026-TG-002.
15–16 июня: предложение награды и подтверждение исправлений
15 июня Telegram поблагодарил нас за отчёт и предложил награду в размере $1000:
“We would like to award you a bounty of $1000 for your findings regarding NTLM via proxy settings.”

К этому моменту патч Telegram уже включал две независимые меры: запрет NTLM/Negotiate для прокси и исправление утечки памяти в qExtractServerTime(). Поэтому формулировка письма не позволяла понять, оценивалась ли вся совокупность переданных сценариев или только proxy-часть исследования.
Реакция одного из исследователей на перспективу дальнейшего сотрудничества была менее дипломатичной:
«Как говорится: сри в одну руку, мечтай в другую — посмотри, какая быстрее наполнится».

Объектом шутки была не сумма награды, а разрыв между предложением продолжить сотрудничество и тем, как исследователь воспринимал сложившиеся отношения. Шутка — шуткой, но деньги не были для нас главным вопросом. Мы попросили направить предложенную сумму на благотворительность и прямо уточнили, является ли решение окончательным для всех описанных уязвимостей либо только для NTLM disclosure через настройки прокси.
16 июня в 05:07 Telegram сообщил, что решение о награде окончательное, и предложил оформить соглашение для обработки выплаты или пожертвования.

В 05:56 мы сообщили, что не намерены подписывать соглашение, и зафиксировали собственную позицию по responsible disclosure: технический отчёт будет опубликован после устранения уязвимостей либо по истечении 90 дней с даты первоначального обращения. Мы также попросили Telegram уведомить нас, когда проблемы будут исправлены или иным образом нейтрализованы.

В 08:53 Telegram направил отдельное техническое уточнение:
“Telegram Desktop v6.9.2 contains fixes for all reported scenarios, with one exception.”

По словам Telegram, NTLM был отключён для прокси, явно добавленных внутри Telegram Desktop, но оставлен для системных прокси, поскольку такая конфигурация широко используется в корпоративных сетях. Это исключение точно соответствует разделению, которое появилось при доработке патча 10–11 июня: прокси из tg://proxy и настроек приложения блокировались, тогда как системному прокси разрешалось использовать NTLM.
Это письмо стало одним из наиболее сильных документальных подтверждений связи между нашим исследованием и исправлениями. Telegram указал не одну находку, а “all reported scenarios”, назвал конкретную исправленную версию и отдельно описал оставшийся системный proxy-вектор. Поскольку переданный отчёт включал NTLM relay, утечку памяти и несколько способов достижения уязвимого кода, такая формулировка заметно шире первоначального описания награды как относящейся только к NTLM через настройки прокси.
При этом письмо не сообщало о координации с Qt и не содержало сведений о публичной атрибуции исследователей. Поэтому наиболее сильная и проверяемая позиция звучит так: Telegram получил полный отчёт, подготовил совпадающие исправления, а затем сам подтвердил, что версия 6.9.2 закрывает переданные сценарии, — но ExPatch и имена исследователей в связанных публичных изменениях указаны не были.
Когда технический вопрос стал вопросом атрибуции
После ответа Telegram мы собирались продолжить исследование: найденный read-примитив открывал путь к поиску write-примитива и прямой RCE-цепочки, не зависящей от конфигурации корпоративного домена. Однако при повторной проверке актуального кода выяснилось, что ключевые пути уже закрыты: NTLM/Negotiate больше нельзя было навязать через прокси, вручную добавленный в Telegram, а qExtractServerTime() отклоняла некорректный timestamp и больше не включала непрочитанный хвост heap в NTLMv2-ответ.
Мы хотели, чтобы эти ошибки были устранены — именно поэтому и передали Telegram полный отчёт. Вопрос был не в том, имел ли Telegram право оперативно защитить пользователей: разумеется, имел. Вопрос заключался в другом: когда появились эти изменения, почему они настолько точно соответствовали переданным нами материалам и почему исследователей не уведомили о работе с Qt.
Независимо обнаружить общий риск автоматической NTLM-аутентификации возможно. Гораздо труднее объяснить простым совпадением одновременное совпадение конкретной функции qExtractServerTime(), управляемого поля MsvAvTimestamp.avLen, механизма утечки непрочитанного heap и способа исправления через проверку оставшегося размера буфера и обязательную длину timestamp в восемь байт.
Чтобы отделить факты от предположений, мы восстановили публичную историю патчей — по датам, функциям и отдельным строкам кода. Она не раскрывает канал, по которому информация попала в Qt, но показывает, что последовательность событий и техническая точность изменений требуют объяснения, а вопрос атрибуции нельзя списать на случайное совпадение.
Не количество совпадений, а их точность
Мы не будем приписывать себе каждое изменение NTLM-кода, появившееся в Qt в последующие недели. Наш довод не требует длинного списка задач и не строится на общем тематическом сходстве. Он основан на документированной последовательности и соответствии, прослеживаемом до конкретных строк кода.
6 июня Telegram получил наш конфиденциальный отчёт с описанием дефекта в qExtractServerTime(): управляемое сервером поле MsvAvTimestamp.avLen, увеличение QByteArray через timeArray.resize(avLen), неполное чтение входного буфера и последующая отправка непрочитанного содержимого heap внутри NTLMv2-ответа. В отчёте также было предложено проверять avLen по оставшемуся размеру буфера и принимать timestamp только ожидаемой длины — восемь байт.
8 июня история репозитория Telegram датирует появление патча, реализующего эти проверки. В нём появились условия avLen > end - p и avLen != 8, возврат только восьми байтов timestamp и ограничение последующей записи теми же восемью байтами. Одновременно патч закрывал второй описанный нами путь — запрещал NTLM/Negotiate для прокси, явно настроенных внутри Telegram Desktop.
12 июня в Qt Gerrit было создано изменение 744448 (https://codereview.qt-project.org/c/qt/qtbase/+/744448). Оно исправляло ту же функцию qExtractServerTime(), проверяло то же поле MsvAvTimestamp.avLen, требовало восьмибайтовую длину timestamp и добавляло тесты для усечённого targetInfo, некорректного avLen и выхода заявленной длины за границы входного буфера.

Публичная история фиксирует однозначную последовательность: сначала Telegram получил конфиденциальный отчёт с конкретным дефектом и способом его исправления; через два дня те же меры появились в патче Telegram; ещё через четыре дня практически идентичное изменение было оформлено в Qt Gerrit.
Ни Telegram, ни Qt публично не объяснили происхождение этого технического соответствия и не указали нас в просмотренных записях. Поэтому вопрос заключается уже не в том, существует ли совпадение, а в том, как результаты переданного нами исследования оказались отражены в upstream-исправлении без уведомления и атрибуции.
Это не спор о тысяче долларов
К этому моменту речь уже шла не о размере награды. Мы потратили более сорока дней на анализ исходного кода, фаззинг и лабораторное воспроизведение атак. Нам удалось связать поведение официального, немодифицированного Telegram Desktop с двумя независимыми последствиями: автоматическим NTLM relay, подтверждённым вплоть до RCE в доменной лаборатории, и повторяемой утечкой памяти через NTLMv2-ответы. Следующим этапом должен был стать поиск write-примитива для построения цепочки, не зависящей от корпоративной инфраструктуры.
Для небольшой исследовательской команды публичная атрибуция — не формальность и не вопрос самолюбия. Это профессиональный результат сорока дней работы, доказательство компетенции и основа доверия, на котором вообще существует coordinated disclosure. Исследователь передаёт вендору неопубликованные материалы первым, предоставляя ему время и техническое преимущество для защиты пользователей. В ответ он вправе ожидать прозрачной координации и сохранения авторства исследования.
Именно такое преимущество мы предоставили Telegram. Мы не стали сначала закреплять приоритет через публичные материалы или обращаться к разработчику библиотеки. После бессонной ночи мы направили Telegram законченный конфиденциальный отчёт, потому что считали безопасность пользователей важнее собственного признания и верили, что команда поступит с переданной информацией ответственно.
Telegram использовал эту фору для подготовки исправлений — и пользователи от этого выиграли. Но после появления соответствующих изменений в Qt нас не уведомили о координации, а ExPatch и имена исследователей не появились в просмотренных публичных записях. Компания получила все преимущества ответственного раскрытия; исследователи не получили даже базовой прозрачности, на которой эта модель держится.
Благодарим вас за время и внимание к этой работе. Исследование живёт не там, где оно опубликовано, а там, где его читают, проверяют и обсуждают. Именно так отдельная находка становится вкладом в безопасность всех пользователей.
С уважением, Александр Ростилов и Денис Ростилов ExPatch Vulnerability Research Team
Sazonov
Я практически целиком прочитал ваш опус, но, несмотря на ваши утверждения, так и не понял, это всё-таки баг в телеграмме или в QtNetwork и он теоретически воспроизводим в других приложениях?