PoC — errore di validazione dell'origine che consente l'esfiltrazione del cookie SSO PRT di Entra ID in linux-entra-sso (GHSA-g9vc-5j77-f2cm, CVE-2026-87005, CVSS 5.3).
Stato CVE: richiesto, in attesa di assegnazione. Questa segnalazione è pubblicata come GHSA-g9vc-5j77-f2cm. All'assegnazione del CVE questo repository viene rinominato
CVE-YYYY-NNNNN-linux-entra-sso-PoCe questo banner viene sostituito con il link al CVE.
| Ricercatore | Dostxodjayev Abdullox (@squeeze440) |
| Advisory | GHSA-g9vc-5j77-f2cm |
| CVSS 3.1 | 5.3 (Medium) |
| Debolezza | CWE-346, CWE-20 |
Platform.SSO_URL in linux-entra-sso manca di un separatore di percorso finale, quindi la guardia onBeforeSendHeaders di Firefox/Thunderbird (e.url.startsWith(Platform.SSO_URL)) viene aggirata da qualsiasi dominio registrato dall'attaccante nella forma login.microsoftonline.com.<attacker-domain>, causando l'acquisizione e l'iniezione da parte dell'estensione di un cookie SSO Primary Refresh Token (PRT) di Entra ID attivo nelle richieste inviate all'host dell'attaccante stesso.
siemens/linux-entra-sso — Plugin per browser su Linux per l'SSO su Microsoft Entra ID tramite il Microsoft Identity Broker locale (Intune). La build Firefox è praticamente sfruttabile; Thunderbird condivide lo stesso identico percorso di codice vulnerabile ma al momento non è raggiungibile tramite questo vettore (vedi Dettagli); Chrome/Chromium non è interessato (vedi Dettagli).
v1.10.0 (commit 676854c, main attuale al 2026-08-03). Confermato presente nella release attuale, ovvero non corretto da GHSA-52rj-42vh-2rxc / CVE-2026-42177 (tale correzione ha toccato solo l'adattatore declarativeNetRequest di Chrome).
5.3 (Medium) — CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N
AC:H — lo sfruttamento richiede che l'estensione possieda un permesso host concesso che copra il nome host creato dall'attaccante. In pratica l'attaccante deve indurre la vittima, tramite ingegneria sociale, a cliccare il link "Background SSO (enable)" dell'estensione mentre si trova sulla pagina dell'attaccante — una condizione al di fuori del controllo diretto dell'attaccante, in linea con la motivazione di complessità usata per il GHSA-52rj-42vh-2rxc correlato.UI:R — la concessione del permesso è un clic esplicito dell'utente.C:H — un cookie SSO PRT rubato è direttamente riutilizzabile contro login.microsoftonline.com per coniare token di accesso/ID per qualsiasi app a cui la vittima ha acconsentito (dirottamento completo della sessione SSO).I:N/A:N — il bug divulga una credenziale ma non modifica di per sé i dati o la disponibilità.Causa principale — src/platform.js:9:
static SSO_URL = "https://login.microsoftonline.com";
Nessun / finale.
Controllo vulnerabile — 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 esegue un confronto grezzo per prefisso di caratteri, non un confronto per hostname/origine. Poiché Platform.SSO_URL non ha un separatore finale, qualsiasi URL il cui host sia login.microsoftonline.com.<qualsiasi-cosa> — un nome DNS sintatticamente valido che l'attaccante controlla completamente — soddisfa il controllo, ad esempio:
"https://login.microsoftonline.com.attacker.example/phish".startsWith("https://login.microsoftonline.com")
=> true
Questo è lo stesso controllo difensivo che la descrizione stessa di GHSA-52rj-42vh-2rxc definiva "il controllo difensivo canonico ... assente dall'adattatore Chrome" — qui esiste, ma è esso stesso aggirabile, quindi la correzione dell'advisory (che ha rafforzato solo la regola declarativeNetRequest di Chrome) ha lasciato aperto questo percorso. platform/thunderbird/js/platform-thunderbird.js estende PlatformFirefox senza sovrascrivere update_request_handlers() o #onBeforeSendHeaders, quindi Thunderbird eredita lo stesso difetto.
Percorso di attivazione (update_request_handlers, platform/firefox/js/platform-firefox.js:36-53): il listener viene registrato con urls: this.well_known_app_filters, che viene popolato da chrome.permissions.getAll().origins (src/platform.js:78-79, update_host_permissions()). Qualsiasi permesso host concesso dall'utente — inclusa una concessione per singolo sito tramite il link "Background SSO (enable)" del popup (popup/menu.js:171-260, usando current_filter = "https://" + tab_hostname + "/*" derivato dalla scheda attiva) — è sufficiente per registrare il listener per quel preciso host. Non è richiesto alcun permesso catch-all https://*/*, a differenza del prerequisito di sfruttamento descritto in GHSA-52rj-42vh-2rxc; qui è sufficiente una singola concessione mirata sul dominio look-alike dell'attaccante stesso, il che è una soglia inferiore rispetto al PoC dell'advisory precedente.
Sink — stesso file, righe 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 (l'URL completo dell'attaccante) viene passato direttamente come ssoUrl al broker nativo. linux-entra-sso.py:27-30 documenta che il backend del broker Microsoft non convalida ssoUrl lato server ("il valore corretto non viene controllato ... dal backend di autorizzazione"), quindi un cookie viene emesso comunque, e viene iniettato negli header sulla richiesta che va effettivamente all'host dell'attaccante stesso — un'esfiltrazione diretta, non semplicemente un header impostato su una richiesta diretta a Microsoft.
Chrome non è interessato: platform/chrome/js/platform-chrome.js:96-99 (dopo la correzione GHSA-52rj-42vh-2rxc, commit 8183759) ancora con "|" + Platform.SSO_URL + "/" (ancora di inizio stringa + slash finale) e aggiunge un'allow-list esplicita requestDomains: [URL.parse(Platform.SSO_URL).hostname] — entrambe respingono correttamente un host login.microsoftonline.com.attacker.example.
Thunderbird — stesso codice vulnerabile, al momento non raggiungibile tramite questo vettore: platform/thunderbird/js/platform-thunderbird.js estende PlatformFirefox invariato, quindi l'identico bypass startsWith esiste nel binario della build Thunderbird. Tuttavia platform/thunderbird/manifest.json dichiara solo un "host_permissions": ["https://login.microsoftonline.com/*"] fisso con nessuna chiave optional_host_permissions. WebExtensions consente a chrome.permissions.request() di concedere origini solo se pre-dichiarate in optional_permissions/optional_host_permissions; poiché Thunderbird non ne dichiara nessuna, il flusso "Background SSO (enable)" del popup (popup/menu.js:259-261) non può ottenere il permesso per un dominio scelto dall'attaccante su Thunderbird oggi. Il bug è latente lì, al momento non raggiungibile dall'attaccante, e dovrebbe comunque essere corretto per difesa in profondità / nel caso in cui optional_host_permissions venga mai aggiunto a quel manifest.
Confermato dinamicamente il predicato vulnerabile rispetto alla costante reale distribuita (importata direttamente da src/platform.js del target, non copiata a mano). Script conservato in ~/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.
Il resto della catena (il broker che emette un cookie per un ssoUrl non convalidato, e il listener webRequest di WebExtensions che scatta per un permesso concesso su un singolo host) è tracciato staticamente, non confermato dinamicamente — riprodurlo end-to-end richiede un host Linux live registrato che esegue microsoft-identity-broker contro un tenant Entra reale, il che è al di fuori di questo ambiente. I passaggi tracciati:
login.microsoftonline.com.attacker.example e serve una pagina lì.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() aggiorna well_known_app_filters → update_request_handlers() ri-registra il listener webRequest.onBeforeSendHeaders di Firefox includendo il nuovo pattern host.main_frame/sub_frame verso https://login.microsoftonline.com.attacker.example/... attiva il listener; il controllo startsWith (mostrato aggirato sopra) passa.broker.acquirePrtSsoCookie(account, e.url) restituisce un cookie PRT reale (secondo linux-entra-sso.py:27-30, ssoUrl non è convalidato dal backend del broker).Un attaccante che riesce a far concedere a un utente Firefox di linux-entra-sso il "Background SSO" per un dominio look-alike controllato dall'attaccante riceve il cookie SSO PRT di Entra ID della vittima direttamente sul proprio server. Quel cookie è riutilizzabile contro login.microsoftonline.com per ottenere token di accesso/ID per qualsiasi applicazione a cui la vittima ha acconsentito — dirottamento completo della sessione SSO. A differenza dell'advisory precedente, questo non richiede il permesso catch-all ampio https://*/*, ma solo una concessione per singolo sito sul dominio dell'attaccante stesso. Thunderbird distribuisce lo stesso controllo difettoso ma il suo manifest attualmente non ha modo di concedere a runtime un permesso per un dominio arbitrario, quindi oggi non è sfruttabile tramite questo vettore (vedi Dettagli).
Ancorare il confronto a un controllo corretto di origine/hostname invece che a un prefisso di stringa grezzo, rispecchiando la correzione già applicata all'adattatore Chrome:
if (!e.url.startsWith(Platform.SSO_URL + "/")) {
return { requestHeaders: e.requestHeaders };
}
o, in modo più robusto, confrontare new URL(e.url).origin con new URL(Platform.SSO_URL).origin (respinge anche trucchi con userinfo/porta). Applicare la correzione a livello della costante Platform.SSO_URL (aggiungendo lo / finale una volta in src/platform.js) chiuderebbe il problema sia per il controllo Firefox sia per qualsiasi altro consumatore della costante, e un test di regressione che verifichi che startsWith respinga https://login.microsoftonline.com.attacker.example/... chiuderebbe la lacuna di copertura nello stesso modo in cui il test di regressione testMatchOutcome suggerito dalla correzione di Chrome lo ha fatto per GHSA-52rj-42vh-2rxc.
Dostxodjayev Abdullox (GitHub: squeeze440)
Nel repository non è presente alcun SECURITY.md e l'API di GitHub non riporta alcuna policy di sicurezza a livello di repository, ma il Private Vulnerability Reporting di GitHub è abilitato su siemens/linux-entra-sso (verificato tramite il pulsante "Report a vulnerability" nella scheda Security del repo). Segnalare tramite quel canale: https://github.com/siemens/linux-entra-sso/security/advisories/new.