
JupyterHub XSRF bypass를 통한 교차 출처 폼 POST (Sec-Fetch-Mode: no-cors) — CWE-352
Sec-Fetch-Mode: no-cors)심각도: 중간
CWE: CWE-352 — 사이트 간 요청 위조 (XSRF)
영향 받는 버전: jupyterhub 4.1.0 ≤ 버전 < 5.4.5 (패치 버전 5.4.5)
권고: GHSA-m68r-v472-jgq9
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-40864
발견자: Romain Deperne
JupyterHub의 XSRF 보호 기능(4.1.0에서 재작성됨)은 Sec-Fetch-Mode 요청 헤더를 사용하여 요청이 동일 출처인지 판단했습니다. Sec-Fetch-Mode: no-cors를 동일 출처로 간주했지만, no-cors는 정확히 브라우저가 교차 출처 "단순" 양식 제출을 위해 보내는 모드입니다. 결과적으로 Hub의 양식 엔드포인트(/hub/spawn, /hub/accept-share)로의 교차 출처 HTML form POST가 XSRF 검사를 완전히 우회했습니다.
JSON API는 영향을 받지 않습니다(단순하지 않은 콘텐츠 유형이 필요하며, 이는 CORS 사전 요청을 강제합니다). 오직 HTML 양식 엔드포인트만 이러한 방식으로 접근 가능합니다.
XSRF 로직은 동일 출처 탐색을 포착하기 위한 일련의 Sec-Fetch-* 상태에 대해 "신뢰"로 단락(short-circuit)됩니다. Sec-Fetch-Mode: no-cors가 해당 신뢰 집합에 포함되었습니다. 그러나 no-cors는 브라우저가 다른 출처로 전송하는 일반 <form method=POST>에 할당하는 모드입니다 — 정확히 토큰이 막아야 하는 고전적인 CSRF 벡터입니다. 따라서 단순한 form body를 수락하고 이 XSRF 게이트에만 의존하는 모든 상태 변경 엔드포인트는 교차 출처에서 위조 가능합니다.
실제 엔드포인트에 매핑하면: /hub/spawn(피해자의 서버 시작)과 /hub/accept-share(피해자가 공격자의 서버 공유를 수락하도록 함)는 모두 이 게이트 뒤의 form POST입니다.
/hub/spawn — 공격자 페이지가 동의 없이 피해자의 단일 사용자 서버를 스폰할 수 있습니다(리소스 소비 / 예기치 않은 상태; 공격자는 해당 서버에 접근 권한을 얻지 않습니다)./hub/accept-share — 공격자가 자신의 서버를 공유할 수 있는 JupyterHub 사용자인 경우, 피해자에게 공유 수락을 강제하여 피해자가 공격자의 서버에 접근하도록 할 수 있습니다(추가 사회 공학/데이터 드롭 시나리오를 위한 설정 단계).Sec-Fetch-Mode를 출처 오라클로 사용하는 것은 타당하지 않습니다: no-cors는 동일 출처를 의미하지 않습니다. 5.4.5에서의 수정은 no-cors를 동일 출처로 신뢰하지 않도록 합니다. 즉시 업그레이드할 수 없는 운영자는 리버스 프록시에서 Sec-Fetch-Mode: no-cors를 포함한 요청을 차단할 수 있습니다.
poc/csrf_spawn.html — 공격자 출처에 호스팅하고 로그인한 JupyterHub 사용자가 열도록 합니다. 자동 제출 양식은 /hub/spawn으로 교차 출처 POST를 발행합니다(브라우저가 Sec-Fetch-Mode: no-cors를 보냄); 취약한 빌드는 유효한 _xsrf 토큰 없이도 이를 수락하고 피해자의 서버를 스폰합니다. 공유 수락 변형의 경우 action을 /hub/accept-share로 지정합니다.
1. Edit TARGET-JUPYTERHUB in poc/csrf_spawn.html
2. Serve the file from an attacker origin (any static host)
3. Open it in a browser already authenticated to the target Hub
4. Observe the victim's server spawn with no XSRF token supplied
책임 있게 공개됨. 수정 사항 출시 후 PoC 게시됨.