CVE-2026-48710: Starlette Host 헤더 검증 누락이 request.url.path를 어떻게 오염시키나

CVE-2026-48710은 Host 헤더에 RFC 9112 §3.2 문법 밖 문자(예: /, ?, #)를 주입하면 Starlette이 재구성한 request.url.path가 실제 라우팅 경로와 달라져 경로 기반 인증·라우팅 제한을 우회할 수 있다는 원리다. X41 D-Sec가 확인한(confirmed) 영향 범위는 starlette >= 0.8.3, < 1.0.1이며, GitHub 어드바이저리는 <= 1.0.0 전체를 영향 버전으로 표기한다. 두 출처의 패치 경계는 1.0.1로 일치한다.

CVE-2026-48710 개요: CISA KEV 등록 배경과 BadHost라는 이름

CVE-2026-48710은 Starlette의 Host 헤더 검증 누락으로 발생하는 HTTP 요청/응답 스머글링 취약점이다. CISA는 2026년 9월 2일 이 CVE를 KEV(Known Exploited Vulnerabilities) 카탈로그에 추가했다. 동시 공지에서 총 7개 KEV 항목이 등록되었다. CISA KEV 카탈로그에서는 악용 방식을 “inject paths into the host part”로 기술하고 있다.

발견과 공개 절차는 X41 D-Sec GmbH가 주도했다. X41 D-Sec는 이 취약점을 X41-2026-002로 분류해 공개했으며, GitHub Security Advisory ID는 GHSA-86qp-5c8j-p5mr이다. GitHub 어드바이저리의 공식 제목은 “Missing Host header validation poisons request.url.path, bypassing path-based security checks”이다. OSTIF.org 공동 발표에서는 이 취약점을 ‘BADHOST’로 명명하고, Starlette 기반 미들웨어(FastAPI 미들웨어 포함)가 request.url 또는 request.url.path를 사용해 인증이나 라우팅 제한을 강제하는 경우 해당 제한이 인증되지 않은 원격 공격자에게 우회될 수 있다고 경고한다.

CISA KEV에서 ‘HTTP Request/Response Smuggling’이라는 명칭을 사용하지만, 이는 CISA 카탈로그의 분류 관행에 따른 것이다. GitHub 어드바이저리와 X41 원문에서는 클래식 요청/응답 스머글링이 아니라 Host 헤더 검증 누락에 따른 request.url.path 오염과 경로 기반 인증 우회로 기술하고 있다.

request.url.path 오염의 기술 원리: Host 헤더 재구성과 RFC 9112 문법 위반

Starlette의 라우팅 알고리즘은 원본 HTTP 패스(raw path)를 기반으로 요청을 분배한다. 그러나 request.url은 Host 헤더에서 재구성된다. 구체적으로 Starlette은 f"{scheme}://{host_header}{path}" 형식으로 URL 문자열을 결합한 뒤 다시 파싱한다.

RFC 9112 §3.2에 따라 Host 헤더 값의 유효 범위는 uri-host [ ":" port ]이며, uri-host는 RFC 3986 §3.2.2의 제한된 host 문법을 따른다. 이 문법 밖의 문자, 특히 /, ?, #가 포함되면 재파싱 과정에서 path/query/fragment 경계가 이동한다. 그 결과 파싱된 request.url.path는 서버가 실제로 수신한 경로와 일치하지 않게 된다.

예를 들어 클라이언트가 GET /foo를 보내면서 Host: example.com/abc?bar=를 첨부하면, Starlette은 http://example.com/abc?bar=/foo로 재구성하고 이를 파싱해 path가 /abc가 된다. 실제 라우팅 경로는 /foo이지만 request.url.path/abc를 반환한다. 이 불일치가 경로 기반 보안 검증을 우회하는 핵심 메커니즘이다.

영향을 받는 환경은 request.url 또는 request.url.path로 인증·라우팅 제한을 적용하는 Starlette 기반 미들웨어(FastAPI 미들웨어 포함)이며, 컨테이너, virtualenv, 벤더링된 아티팩트에 Starlette 버전이 고정된 설치(LLM 툴링에 흔함)도 포함된다.

PoC 검증: curl 한 글자 주입으로 403 Forbidden을 200 OK로 바꾸는 과정

X41 D-Sec가 공개한 PoC는 단일 문자 ?만 Host 헤더에 주입한다:

  1. 정상 요청: curl -i -H 'Host: foo' localhost:8000/admin → 응답 403 Forbidden
  2. 악용 요청: curl -i -H 'Host: foo?' localhost:8000/admin → 응답 200 OK

두 요청의 실제 HTTP path는 모두 /admin이다. 라우팅 엔진은 raw path /admin으로 분배하지만, Host: foo?로 재구성된 URL(http://foo?/admin)을 파싱하면 ?가 쿼리 경계로 취급되어 path가 빈 문자열이 되고 /admin이 query로 이동한다. 이 때문에 request.url.path 기반 인증 미들웨어는 /admin에 대한 거부 규칙을 매칭하지 못한다. Starlette의 라우팅 알고리즘은 HTTP path에 의존하지만, 미들웨어와 엔드포인트가 접근하는 request.url.path 속성은 재구성된 URL에 기반하기 때문이다.

X41 D-Sec GmbH와 연구진(JJ)은 2026-02-04에 이 PoC를 제작하고 벤더에 통보했다.

영향 버전과 패치: starlette 0.8.3~1.0.0 영향과 1.0.1 패치 내용

GitHub 어드바이저리는 영향 버전을 starlette <= 1.0.0(pip 패키지)로 명시하고, 패치 버전은 1.0.1으로 확정했다. X41 D-Sec는 ‘확인된(confirmed)’ 영향 범위를 starlette >= 0.8.3, < 1.0.1로 한정하고 있다. 두 출처 모두 패치 경계(1.0.1)에서 일치한다.

패치된 버전 1.0.1은 request.url을 구성할 때 Host 헤더를 RFC 9112 §3.2 / RFC 3986 §3.2.2 문법으로 검증하고, 잘못된 값에는 scope["server"]로 폴백한다. 이 폴백 동작으로 Malformed Host 헤더가 request.url.path를 오염시킬 경로가 차단된다.

공개 타임라인은 다음과 같다:

  1. 2026-01-27 – 무관한 소스 코드 감사 과정에서 취약점 확인
  2. 2026-02-04 – PoC 제작 및 벤더 접촉
  3. 2026-03-01 – 벤더가 패치 제안
  4. 2026-05-21 – 벤더가 패치 공개
  5. 2026-05-22 – X41 D-Sec 어드바이저리(X41-2026-002) 공개

완화 전략: request.scope[‘path’] 사용, 리버스 프록시, RFC 준수 ASGI 서버

패치 적용이 즉시 어려운 환경에서 X41 D-Sec는 세 가지 워크어라운드를 권고한다:

  1. request.url.path 대신 request.scope["path"]를 사용해 인증·라우팅 판정을 수행한다. scope["path"]는 ASGI 서버가 전달한 원본 raw path이므로 Host 헤더 재구성 영향을 받지 않는다.
  2. nginx 또는 Apache HTTP Server 등 잘못된 Host 헤더를 거부하는 리버스 프록시를 Python 애플리케이션 앞에 배치한다.
  3. Host 헤더를 RFC 사양에 따라 검증하는 ASGI 서버를 사용한다.

이 세 가지는 공식 패치(1.0.1 업그레이드)가 우선이라는 전제 아래, 패치 전 임시 조치로 적용하는 방법이다.

FastAPI·vLLM·LiteLLM으로 확산되는 생태계 파급 효과

이 취약점은 Starlette 단일 패키지로 끝나지 않는다. 발견 당시 vLLM에서 확인되었으며, FastAPI를 경유해 LiteLLM, Text Generation Inference, OpenAI-shim 프록시 대다수, MCP 서버, 에이전트 하네스, eval 대시보드, 모델 관리 UI에까지 영향이 미친다. 발견자들은 이를 ‘BadHost’로 명명했다.

Ars Technica는 X41 D-Sec가 이 취약점을 ‘critical severity’로 묘사했다고 보도했고, Starlette 개발사 기준 주간 다운로드량이 3억 2,500만 회에 달한다고 전했다. X41 D-Sec는 보안 업체 Nemesis와 협력해 온라인 스캐너(mcp-scan.nemesis.services)를 구축했다.

CVSS 점수 논쟁: 6.5(Moderate) vs 7(High), 평가 체계별 차이 읽기

같은 CVE-2026-48710에 대해 출처별 심각도 점수가 다르다.

출처 점수 체계 점수 심각도 분류 비고
GitHub GHSA-86qp-5c8j-p5mr CVSS 3.1 6.5 Moderate AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
X41 D-Sec (X41-2026-002) CVSS 4.0 7 High AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N
Ars Technica 보도 7/10(X41 CVSS 4.0 점수 인용) “critical severity”(기사 헤드라인 표현) X41 D-Sec CVSS 4.0 점수 7을 인용

CVSS 3.1과 CVSS 4.0은 메트릭 구조 자체가 다르다. 3.1에서 Scope(S)가 Unchanged인 반면 4.0은 Confidentiality/Integrity/Availability를 원본 시스템과 부수 시스템으로 분리한다. X41의 벡터에서 SC:H, SI:H는 Starlette이 포함되는 상위 애플리케이션(FastAPI, vLLM 등)의 무결성·보안영향까지 반영한 결과로, 단순 Starlette 패키지 내부 영향만 본 GitHub 3.1 점수와 차이를 보인다. 실무에서는 두 점수 체계를 혼동하지 않고 조직의 리스크 평가 프레임워크에 해당하는 체계를 참조한다.

참고: X41 공식 어드바이저리(X41-2026-002)의 Severity Rating은 ‘High'(CVSS 4.0 점수 7)이며, ‘critical’이라는 등급 표기는 X41 원문에 존재하지 않는다. ‘critical severity’ 표현은 Ars Technica 기사 헤드라인 및 본문 서술에 해당한다.

시급성과 대응 체크리스트

CISA KEV 등재(2026-09-02)는 악용 증거가 확인되었음을 의미하며, “향후 발생 가능성”이 아닌 “이미 발생 중” 상태를 전제로 대응해야 한다. 아래 순서로 점검을 권고한다.

  1. pip show starlette 또는 pip freeze | grep starlette로 현재 배포 환경의 Starlette 버전을 확인한다. 컨테이너 이미지, virtualenv, 벤더링 아티팩트(LLM 툴링 환경)까지 포함한다.
  2. 버전이 >= 0.8.3, < 1.0.1이면 즉시 1.0.1로 업그레이드한다.
  3. 업그레이드 전까지 request.scope["path"] 기반 인증 판정 전환, 리버스 프록시 Host 헤더 검증, RFC 준수 ASGI 서버 중 적용 가능한 조치부터 적용한다.
  4. FastAPI, vLLM, LiteLLM 등 Starlette 상위 의존성이 있는 서비스의 버전 릴리스 노트를 점검한다.
  5. 외부에서 들어오는 Host 헤더가 request.url 기반 로직에 도달하는지 코드 감사로 확인한다.

패치 공개(2026-05-21) 이후 KEV 등재(2026-09-02)까지 약 3.5개월의 간격이 있었다. 이 기간 동안 패치된 1.0.1이 이미 유통되고 있었기 때문에, 미적용 환경은 KEV 등재 시점에 “알고도 패치하지 않은” 상태로 분류될 수 있다.

댓글 남기기