| 항목 | 내용 |
|---|---|
| CVE ID | CVE-2005-2088 |
| 영향 제품/버전 | Apache HTTP Server 1.3.33 이하, 2.0.54 이하의 HTTP proxy 구성 |
| 취약 지점 | mod_proxy_http가 요청 본문을 backend로 전달할 때 Transfer-Encoding: chunked와 Content-Length의 충돌을 단일 framing으로 정규화하지 않음 |
| CWE | CWE-444 (Inconsistent Interpretation of HTTP Requests) |
| CVSS | CVSS v2 4.3 Medium — AV:N/AC:M/Au:N/C:N/I:P/A:N |
| 공개일 | 2005-06-29 |
| 수정 버전 | Apache HTTP Server 1.3.34, 2.0.55 |
| 주요 수정 | Apache 2.0.55에서 request header 정규화와 mod_proxy_http request body forwarding 재작성 |
Apache HTTP Server가 forward proxy 또는 reverse proxy로 동작할 때, 취약 버전의 mod_proxy_http는 클라이언트 요청 헤더를 backend 요청으로 복사하고 요청 본문 바이트를 그대로 스트리밍했다. 이때 클라이언트가 Transfer-Encoding: chunked와 Content-Length를 함께 보내면 Apache와 backend가 서로 다른 본문 경계를 선택할 수 있었다.
HTTP/1.1 규칙상 두 헤더가 동시에 있으면 Transfer-Encoding이 우선이고 Content-Length는 무시해야 한다. 하지만 취약한 proxy 전달 경로는 이 충돌을 backend로 넘기면서, 한쪽은 chunked body의 0 청크까지를 현재 요청으로 보고 다른 쪽은 Content-Length만큼만 현재 요청으로 보는 상태를 만들 수 있었다. 남은 바이트가 같은 persistent backend connection의 다음 요청처럼 해석되면 요청 밀수, cache poisoning, 접근 제어 우회 같은 영향으로 이어진다.
Apache 2.0.55는 core 요청 파싱 뒤 TE와 CL이 함께 있으면 CL을 제거하고, proxy 경로에서도 두 헤더를 따로 보류한 뒤 정확히 하나의 body framing만 backend에 보낸다. 작은 본문은 실제로 읽은 바이트 수로 CL을 다시 계산하고, 큰 chunked 본문은 chunked로 재인코딩하거나 필요 시 spool 후 CL을 계산한다.
HTTP proxy는 클라이언트와 backend 사이에서 요청을 새로 만든다. 클라이언트가 보낸 바이트를 단순히 터널링하는 것이 아니라, 앞쪽 연결에서 HTTP 요청을 읽고 뒤쪽 연결에 또 다른 HTTP 요청을 쓴다.
클라이언트
│ HTTP 요청 A
▼
Apache mod_proxy_http
│ backend용 HTTP 요청 A'
▼
backend 서버
이 구조에서 안전하려면 proxy와 backend가 "현재 요청 본문이 어디서 끝나는가"를 같은 방식으로 이해해야 한다. 둘의 본문 길이 해석이 어긋나면 backend connection에 남은 바이트가 다음 요청의 시작으로 보일 수 있다.
Content-Length는 본문이 정확히 몇 바이트인지 숫자로 말한다. Transfer-Encoding: chunked는 본문을 여러 청크로 나눠 보내고, 길이 0인 청크가 나오면 본문이 끝났다고 말한다. 둘은 모두 본문 경계를 정하는 헤더다.
Content-Length 방식
headers
blank line
[지정한 바이트 수만큼 body]
chunked 방식
headers
blank line
<chunk-size>\r\n
[chunk bytes]\r\n
0\r\n
\r\n
둘이 함께 있으면 RFC 2616의 framing 규칙은 Content-Length를 무시하라고 정했다. 이유는 단순하다. 두 값이 다르면 한 파서는 앞쪽 경계를, 다른 파서는 뒤쪽 경계를 선택할 수 있기 때문이다.
같은 바이트 스트림
├─ proxy: chunked 종료 지점까지 현재 요청
└─ backend: Content-Length 바이트까지만 현재 요청
│
▼
남은 바이트가 backend의 다음 요청으로 재해석될 수 있음
Apache 2.0.54의 ap_proxy_http_request()는 요청 헤더를 순회하면서 hop-by-hop 헤더를 제거했다. 이 목록에는 Transfer-Encoding이 들어 있었지만 Content-Length는 일반적으로 그대로 전달됐다. 그런 다음 헤더를 backend connection으로 먼저 쓰고, 클라이언트 input filter에서 읽은 본문 brigade를 반복해서 그대로 backend output filter에 보냈다.
/* Apache 2.0.54 mod_proxy_http 흐름 요약 */
for (headers_in) {
if (key is "Transfer-Encoding" or other hop-by-hop header)
continue;
write_header_to_backend(key, value);
}
write_blank_line_and_flush();
do {
ap_get_brigade(r->input_filters, bb, ...);
remove_EOS_bucket_if_needed(bb);
ap_pass_brigade(origin->output_filters, bb);
} while (!seen_eos);문제는 앞쪽 connection의 input filter가 Transfer-Encoding: chunked를 기준으로 본문을 읽을 수 있는데, backend로 보내는 헤더에는 클라이언트의 Content-Length가 남을 수 있다는 점이다. backend는 proxy가 보낸 요청 헤더에서 CL을 보고 다른 길이를 선택할 수 있다.
[패치 전]
클라이언트 요청
Transfer-Encoding: chunked
Content-Length: N
│
▼
Apache proxy
- TE는 hop-by-hop으로 제거
- CL은 backend 요청 헤더로 전달
- body는 input filter가 읽은 바이트를 계속 스트리밍
│
▼
backend 요청
Content-Length: N
body bytes...
│
├─ backend가 N바이트 뒤를 요청 끝으로 판단
▼
남은 바이트가 같은 backend keep-alive 연결의 다음 요청 후보가 됨
공식 CVE 설명의 "request body was sent with the request, making the backend vulnerable"라는 표현은 이 경계를 가리킨다. 공격자는 proxy와 backend의 framing 선택 차이를 이용해 backend가 보게 될 숨은 요청을 앞 요청의 본문 안에 배치할 수 있다.
이 취약점은 Apache 자체가 정적 파일을 직접 제공하는 경로보다, Apache가 HTTP proxy로 backend와 persistent connection을 유지하는 구성에서 의미가 있다. 실제 영향은 backend가 CL을 어떻게 처리하는지, connection reuse가 가능한지, 앞단 cache나 WAF가 어느 요청을 검사하는지에 따라 달라진다.
가능한 결과는 다음과 같다.
- backend가 proxy가 검사하지 않은 숨은 요청을 처리한다.
- cache가 공격자가 의도한 응답을 다른 URL의 응답처럼 저장한다.
- 앞단 접근 제어는 첫 요청만 보고 허용했지만 backend는 뒤따르는 요청을 별도 요청으로 처리한다.
이 보고서는 공개 exploit 코드를 실행하거나 재현하지 않고, 공개 diff와 권고가 보여주는 정적 코드 흐름만 분석했다.
Apache 2.0.55는 두 계층에서 같은 원칙을 적용했다. 먼저 core 요청 파싱 후 Transfer-Encoding과 Content-Length가 모두 있으면 Content-Length를 제거한다.
+if (apr_table_get(r->headers_in, "Transfer-Encoding")
+ && apr_table_get(r->headers_in, "Content-Length")) {
+ apr_table_unset(r->headers_in, "Content-Length");
+}둘째, mod_proxy_http는 TE와 CL을 바로 backend로 복사하지 않고 old_te_val, old_cl_val로 보류한다. 두 값이 함께 있으면 CL을 지우고 backend keep-alive를 닫도록 표시한다. 이후 본문을 실제로 읽어 작은 본문은 계산한 CL로 보내고, chunked로 보내야 하는 경우는 Transfer-Encoding: chunked를 새로 붙여 chunk framing을 생성한다. 즉 backend에는 CL 또는 TE 중 하나만 도달한다.
[패치 후]
headers_in 순회
│
├─ Transfer-Encoding → old_te_val에 보류, 즉시 전달하지 않음
├─ Content-Length → old_cl_val에 보류, 즉시 전달하지 않음
└─ 그 외 안전한 헤더만 header_brigade에 추가
old_te_val && old_cl_val
│
├─ Content-Length 제거
├─ old_cl_val = NULL
└─ backend keep-alive 종료 표시
본문 처리 선택
│
├─ 실제 본문 전체가 메모리에 들어옴 → 읽은 바이트 수로 CL 재계산
├─ chunked 유지 필요 → chunked body를 새로 작성
└─ CL 필요하지만 전체 길이 계산 필요 → spool 후 CL 계산
backend가 받는 요청
└─ 정확히 하나의 framing만 포함
stream_reqbody_cl()도 CL보다 더 많은 바이트가 흘러나가지 않도록 검사한다. 주석은 CL을 넘는 바이트를 backend로 보내면 "다른 요청"으로 해석될 수 있다고 명시한다. 이것이 CVE의 핵심 위험을 제거하는 방어선이다.
운영자는 Apache HTTP Server를 1.3.34 또는 2.0.55 이상으로 올려야 한다. 임시 완화가 필요하면 Apache의 HTTP proxy 기능, 특히 신뢰되지 않은 클라이언트가 접근 가능한 reverse proxy/forward proxy 경로에서 request body가 있는 persistent backend 연결을 제한해야 한다. 다만 공식 해결책은 업그레이드다.
- 공개 Apache 2.0.55 diff는 core header 정규화와
mod_proxy_httpbody forwarding 재작성을 함께 포함한다. 이 보고서는 두 변경이 함께 TE/CL 불일치 request smuggling을 제거한다고 분석하지만, 1.3.x branch의 정확한 backport diff는 별도로 깊게 다루지 않았다. - 공개 권고는 cache poisoning, WAF bypass, XSS 가능성을 영향으로 언급하지만, 구체적 공격 성공 여부는 backend 서버의 HTTP parser와 proxy/cache 구성에 따라 달라진다.
- CVE-2005-2088 — NVD — 영향 버전, CVSS, CWE, request smuggling 영향 설명
- Apache HTTP Server 2.0 vulnerabilities — 2.0.55에서 수정된 공식 Apache 보안 목록
- Apache HTTP Server 2.0.55 release announcement — TE/CL 충돌 처리와 proxy body 전달 수정 요약
- Apache httpd 2.0.54...2.0.55 compare —
server/protocol.c,modules/proxy/proxy_http.c패치 원문 - Ubuntu CVE-2005-2088 — 배포판 영향 추적