
PoC — linux-entra-sso에서 Entra ID PRT SSO 쿠키 유출을 가능하게 하는 origin 검증 오류 (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 (Medium) |
| 취약점 | CWE-346, CWE-20 |
linux-entra-sso의 Platform.SSO_URL에 후행 경로 구분자가 없어서, Firefox/Thunderbird의 onBeforeSendHeaders 가드(e.url.startsWith(Platform.SSO_URL))가 login.microsoftonline.com.<attacker-domain> 형태로 공격자가 등록한 모든 도메인에 의해 우회되며, 이로 인해 확장 프로그램이 활성 Entra ID PRT(Primary Refresh Token) SSO 쿠키를 획득하여 공격자 자신의 호스트로 전송되는 요청에 주입하게 됩니다.
siemens/linux-entra-sso — 로컬 Microsoft Identity Broker(Intune)를 통해 Microsoft Entra ID에 SSO하기 위한 Linux용 브라우저 플러그인. Firefox 빌드는 실질적으로 악용 가능하며, Thunderbird는 동일한 취약한 코드 경로를 공유하지만 현재 이 벡터로는 도달할 수 없습니다(세부 사항 참조). Chrome/Chromium은 영향을 받지 않습니다(세부 사항 참조).
v1.10.0 (커밋 676854c, 2026-08-03 기준 현재 main). 현재 릴리스에 존재함을 확인했습니다. 즉, GHSA-52rj-42vh-2rxc / CVE-2026-42177로 수정되지 않았습니다 (해당 수정은 Chrome declarativeNetRequest 어댑터만 건드렸습니다).
5.3 (Medium) — 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 — 탈취된 PRT SSO 쿠키는 login.microsoftonline.com에 대해 직접 재사용하여 피해자가 동의한 모든 앱에 대한 액세스/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에 후행 구분자가 없기 때문에, 호스트가 login.microsoftonline.com.<anything>인 모든 URL — 공격자가 완전히 통제하는 구문상 유효한 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는 update_request_handlers()나 #onBeforeSendHeaders를 재정의하지 않고 PlatformFirefox를 확장하므로, 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 + "/*" 사용) 포함 — 은 해당 정확한 호스트에 대해 리스너를 등록하기에 충분합니다. GHSA-52rj-42vh-2rxc에 설명된 악용 전제 조건과 달리 포괄적인 https://*/* 권한은 필요하지 않습니다. 여기서는 공격자 자신의 유사 도메인에 대한 단일 표적 부여만으로 충분하며, 이는 이전 권고의 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을 서버 측에서 검증하지 않는다고 문서화하고 있으므로("올바른 값은 ... 권한 부여 백엔드에 의해 검사되지 않음"), 쿠키는 관계없이 발급되며, 실제로 공격자 자신의 호스트로 가는 요청의 헤더에 주입됩니다 — 이는 단순히 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를 확장하므로, Thunderbird 빌드의 바이너리에도 동일한 startsWith 우회가 존재합니다. 그러나 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.
체인의 나머지 부분(브로커가 검증되지 않은 ssoUrl에 대해 쿠키를 발급하고, WebExtensions webRequest 리스너가 부여된 단일 호스트 권한에 대해 실행되는 것)은 정적으로 추적되었으며 동적으로 확인되지는 않았습니다 — 이를 종단 간 재현하려면 실제 Entra 테넌트에 대해 microsoft-identity-broker를 실행하는 활성 등록된 Linux 호스트가 필요하며, 이는 이 환경의 범위를 벗어납니다. 추적된 단계:
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 리스너를 재등록합니다.https://login.microsoftonline.com.attacker.example/...에 대한 main_frame/sub_frame 요청이 리스너를 발생시킵니다. startsWith 검사(위에서 우회가 표시됨)가 통과합니다.broker.acquirePrtSsoCookie(account, e.url)이 실제 PRT 쿠키를 반환합니다(linux-entra-sso.py:27-30에 따르면 ssoUrl은 브로커 백엔드에 의해 검증되지 않음).linux-entra-sso Firefox 사용자가 공격자가 통제하는 유사 도메인 하나에 대해 "Background SSO"를 부여하도록 유도한 공격자는 피해자의 Entra ID PRT SSO 쿠키를 자신의 서버에서 직접 받습니다. 이 쿠키는 login.microsoftonline.com에 대해 재사용하여 피해자가 동의한 모든 애플리케이션에 대한 액세스/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/...를 거부함을 검증하는 회귀 테스트는 Chrome 수정의 제안된 testMatchOutcome 회귀 테스트가 GHSA-52rj-42vh-2rxc에 대해 그랬던 것과 같은 방식으로 커버리지 격차를 해소할 것입니다.
Dostxodjayev Abdullox (GitHub: squeeze440)
저장소에 SECURITY.md가 없고 GitHub API는 저장소 수준 보안 정책을 보고하지 않지만, siemens/linux-entra-sso에는 GitHub 비공개 취약점 신고가 활성화되어 있습니다(저장소의 Security 탭 "Report a vulnerability" 버튼으로 확인). 해당 채널을 통해 신고하십시오: https://github.com/siemens/linux-entra-sso/security/advisories/new.