COMMENTS (0)
댓글은 익명으로 작성되며, 삭제 비밀번호를 설정하면 본인만 삭제할 수 있습니다. 비밀번호를 설정하지 않은 댓글은 누구나 삭제할 수 있습니다.
HTTP 파싱 불일치를 악용하면 보안 장비를 우회할 수 있다. Request Smuggling의 CL.TE, TE.CL, TE.TE 3가지 변종과 탐지법.
댓글은 익명으로 작성되며, 삭제 비밀번호를 설정하면 본인만 삭제할 수 있습니다. 비밀번호를 설정하지 않은 댓글은 누구나 삭제할 수 있습니다.
GET / HTTP/1.1 — 웹 브라우저가 서버에 보내는 첫 줄이다. 단순해 보인다.
그런데 실제 프로덕션 환경에서 이 요청이 거치는 경로는 훨씬 복잡하다. CDN, 로드밸런서, 리버스 프록시, WAF, 캐시 서버를 지나서야 비로소 오리진 서버에 도달한다. 각 계층은 HTTP 메시지를 독자적으로 파싱한다. 그리고 파싱 방식이 다르면, 공격이 시작된다.
이 글에서는 HTTP 프로토콜 자체의 설계적 특성과 구현 간 불일치가 만드는 공격 표면을 분석한다. HTTP 통신의 구조와 한계에서 다룬 기본 구조를 이미 알고 있다면, 이제 공격자가 그 구조의 어디를 노리는지 살펴볼 차례다.
대부분의 기업 네트워크에서 TCP 80(HTTP)과 443(HTTPS) 포트는 아웃바운드로 열려 있다. 웹 접속을 차단하면 업무가 불가능하기 때문이다.
공격자에게 이것은 항상 열려 있는 문을 의미한다. C2(Command & Control) 통신을 HTTP/HTTPS로 위장하면 방화벽은 이를 정상 웹 트래픽으로 판단한다. MITRE ATT&CK에서 T1071.001(Web Protocols)로 분류하는 이 기법을 다수의 APT 그룹이 사용한다.
HTTP/1.1은 텍스트 기반 프로토콜이다. 사람이 읽을 수 있는 형식으로 설계되었다는 것은, 해석의 모호함이 존재한다는 뜻이기도 하다.
An implementation that receives an incomplete or invalid Content-Length value SHOULD close the connection. — RFC 7230, Section 3.3.3
RFC는 "SHOULD"라고 했다. "MUST"가 아니다. 이 한 단어의 차이가 Request Smuggling이라는 치명적 취약점을 만들었다.
CDN → 프록시 → WAF → 서버로 이어지는 체인에서, 각 계층이 같은 HTTP 메시지를 다르게 해석하면 공격자는 그 틈에 악성 요청을 삽입할 수 있다.
이 글에서 다루는 공격은 모두 HTTP 프로토콜의 명세 모호함 또는 구현 간 불일치에서 비롯된다.
| 개념 | 설명 |
|---|---|
| Desync(비동기화) | 프론트엔드와 백엔드가 요청 경계를 다르게 해석하는 상태 |
| Cache Key | 캐시가 응답을 저장/검색할 때 사용하는 식별자 (보통 URL + Host) |
| Unkeyed Input | 캐시 키에 포함되지 않지만 응답 내용에 영향을 주는 입력 |
| Origin Reflection | 서버가 요청의 Origin 헤더를 검증 없이 응답에 반영하는 동작 |
| CRLF | Carriage Return + Line Feed. HTTP 헤더의 줄 구분자 |
다음 섹션에서 각 공격이 어떤 불일치를 악용하는지 구체적으로 살펴본다.
Request Smuggling은 HTTP 공격 표면에서 가장 위험한 기법이다. 프론트엔드(CDN, 로드밸런서)와 백엔드 서버가 요청의 끝을 다르게 판단할 때 발생한다.
HTTP/1.1에서 요청 본문의 길이를 결정하는 헤더는 두 가지다.
| 헤더 | 동작 |
|---|---|
| Content-Length | 본문의 바이트 수를 명시 |
| Transfer-Encoding: chunked | 본문을 청크 단위로 전송, 0 크기 청크로 종료 |
RFC 7230은 두 헤더가 동시에 존재하면 Transfer-Encoding을 우선하라고 명시한다. 하지만 모든 서버가 이를 따르지는 않는다.
CL.TE 공격 예시 — 프론트엔드는 Content-Length, 백엔드는 Transfer-Encoding을 우선하는 경우:
POST / HTTP/1.1
Host: target.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
프론트엔드는 Content-Length: 13을 보고 0\r\n\r\nSMUGGLED까지를 하나의 요청으로 판단한다. 하지만 백엔드는 Transfer-Encoding: chunked를 따라 0\r\n\r\n에서 요청이 끝났다고 본다. 남은 SMUGGLED는 다음 요청의 시작으로 해석된다.
이 불일치를 이용하면 WAF를 우회할 수 있다. 프론트엔드가 검사한 요청과 백엔드가 처리하는 요청이 다르기 때문이다.
실제 연구 사례: 보안 연구원 James Kettle은 2019년 DEF CON에서 이 기법을 대중화했고, 2021년 Black Hat USA에서 HTTP/2 환경으로 확장한 연구를 발표했다. Amazon ALB 취약점 악용과 Bitbucket 전체 페이지 오염에 성공한 사례가 포함된다.
많은 웹 애플리케이션이 HTTP Host 헤더를 신뢰할 수 있는 값으로 가정한다. 비밀번호 재설정 링크를 생성할 때 Host 헤더에서 도메인을 가져오거나, 리다이렉트 URL을 구성할 때 Host 값을 그대로 사용하는 식이다.
문제는 Host 헤더가 클라이언트가 제어할 수 있는 입력이라는 점이다.
비밀번호 재설정 공격 시나리오:
POST /password-reset HTTP/1.1
Host: evil-attacker.com
Content-Type: application/x-www-form-urlencoded
email=victim@example.com
서버가 Host 헤더에서 도메인을 추출해 재설정 링크를 생성하면, 피해자는 다음과 같은 이메일을 받는다:
비밀번호 재설정 링크:
https://evil-attacker.com/reset?token=abc123def456
피해자가 이 링크를 클릭하면 토큰이 공격자 서버로 전송된다. OWASP Web Security Testing Guide는 이를 Host Header Injection 항목에서 다룬다.
Host 헤더 외에도 X-Forwarded-Host, X-Host, X-Forwarded-Server 같은 대체 헤더가 같은 문제를 일으킨다. 프록시 환경에서 원래 호스트를 전달하기 위해 만들어진 이 헤더들이 공격 벡터가 된다.
웹 캐시는 성능을 위해 존재한다. 같은 URL에 대한 응답을 저장했다가 다른 사용자에게 재사용한다. 그런데 캐시가 저장 기준으로 삼는 Cache Key에 포함되지 않는 입력이 응답 내용에 영향을 주면 어떻게 될까?
공격자가 Unkeyed Input에 악성 페이로드를 삽입하고, 이 응답이 캐시에 저장되면 같은 URL을 요청하는 모든 사용자가 오염된 응답을 받게 된다.
공격 흐름:
| 단계 | 동작 |
|---|---|
| 1. 정찰 | 캐시 키에 포함되지 않지만 응답에 반영되는 헤더를 탐색 |
| 2. 주입 | X-Forwarded-Host 등 Unkeyed 헤더에 악성 도메인 삽입 |
| 3. 캐시 저장 | 오염된 응답이 캐시에 저장됨 |
| 4. 확산 | 같은 URL을 요청하는 모든 사용자가 악성 응답 수신 |
PortSwigger의 James Kettle은 2018년 "Practical Web Cache Poisoning" 연구에서 이 기법을 체계화했다. 온라인 신문 전체 페이지를 지속적으로 오염시키는 데 성공했으며, Firefox 업데이트를 전역적으로 비활성화하는 PoC도 발표했다.
Cache Deception과의 차이: Cache Poisoning은 공격자가 캐시에 악성 콘텐츠를 주입하는 것이고, Cache Deception은 피해자의 민감 정보를 캐시에 저장시키는 것이다. 공격 방향이 정반대다.
| 구분 | Cache Poisoning | Cache Deception |
|---|---|---|
| 목적 | 악성 콘텐츠를 다른 사용자에게 서빙 | 피해자의 민감 정보 탈취 |
| 공격 대상 | 캐시 키 설계 결함 | 캐시-오리진 간 경로 해석 불일치 |
HTTP 헤더는 \r\n(CRLF)으로 구분된다. 헤더와 본문 사이에는 빈 줄이 있다. 사용자 입력이 HTTP 응답 헤더에 반영될 때, CRLF 문자를 주입할 수 있으면 응답 구조 자체를 조작할 수 있다.
HTTP/1.1 302 Found
Location: /redirect?url=https://example.com%0d%0aSet-Cookie:%20session=attacker_value
%0d%0a는 URL 인코딩된 \r\n이다. 서버가 이를 디코딩한 후 응답에 반영하면:
HTTP/1.1 302 Found
Location: /redirect?url=https://example.com
Set-Cookie: session=attacker_value
공격자는 응답에 임의의 헤더를 추가했다. Set-Cookie로 세션을 고정하거나, 두 번째 빈 줄을 주입해 응답 본문 자체를 조작할 수도 있다.
MITRE CWE는 이를 CWE-113: HTTP Response Splitting으로 분류한다. 2024년에도 GFI KerioControl 방화벽에서 CRLF 주입이 RCE로 확대 가능한 취약점()이 발견되었다.
브라우저의 Same-Origin Policy는 다른 출처의 리소스에 대한 접근을 차단한다. CORS는 이 제약을 선택적으로 완화하기 위한 메커니즘이다.
문제는 CORS 설정 오류가 매우 흔하다는 것이다.
가장 위험한 패턴 — Origin Reflection:
GET /api/user-data HTTP/1.1
Host: api.example.com
Origin: https://evil.com
Cookie: session=victim_session_id
서버가 Origin 헤더를 검증 없이 반영하면:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
{"name": "피해자", "email": "victim@example.com"}
evil.com에서 실행되는 JavaScript가 피해자의 인증 정보를 포함한 응답을 읽을 수 있게 된다. Same-Origin Policy가 사실상 무력화되는 것이다.
OWASP는 이를 Security Misconfiguration 범주에서 다룬다.
| 설정 오류 | 위험도 |
|---|---|
| Origin Reflection (모든 Origin 허용) | 높음 — 모든 사이트에서 민감 데이터 접근 가능 |
| null Origin 신뢰 | 높음 — sandboxed iframe에서 악용 가능 |
| 서브도메인 정규식 오류 | 중간 — 유사 도메인 허용 가능 |
2023년 10월, Google, Cloudflare, Amazon이 동시에 경고를 발표했다. HTTP/2의 스트림 멀티플렉싱 기능을 악용한 DDoS 공격이 초당 3.98억 요청이라는 전례 없는 규모에 도달했다.
This novel attack works by making and rapidly canceling a large volume of HTTP/2 requests. — CISA Alert, CVE-2023-44487
HTTP/2는 하나의 TCP 연결에서 여러 스트림을 동시에 처리한다. 공격자는 요청을 보낸 직후 RST_STREAM 프레임으로 취소하고, 비워진 슬롯에 즉시 새 요청을 보내는 과정을 반복한다. 서버는 요청 처리를 시작하지만 클라이언트에게 응답을 보낼 필요는 없으므로, 비대칭적 리소스 소비가 발생한다.
| 조직 | 완화한 최대 트래픽 |
|---|---|
| Amazon | 초당 1.55억 요청 |
| Cloudflare | 초당 2.01억 요청 |
| 초당 3.98억 요청 |
각 수치는 Cloudflare 기술 분석과 Google Cloud 보안 블로그에서 발표한 것이다.
이 공격은 HTTP/2 명세 자체의 특성을 악용한다. 구현 버그가 아닌 프로토콜 설계의 허점이다. 모든 HTTP/2 구현체가 영향을 받았으며, CISA는 즉시 패치를 권고했다.
지금까지 살펴본 공격들의 공통점은 세 가지다.
첫째, 정상 트래픽으로 위장된다. Request Smuggling 요청은 형식적으로 유효한 HTTP다. CORS 악용은 브라우저의 정상 동작이다. 방화벽과 IDS가 이를 악성으로 판단하기 어렵다.
둘째, 증폭 효과가 크다. Cache Poisoning은 단일 요청으로 TTL 기간 동안 모든 사용자에게 영향을 준다. Rapid Reset은 소수의 클라이언트로 수억 건의 요청을 생성한다.
셋째, 탐지 지점이 분산된다. Request Smuggling은 프론트엔드와 백엔드 사이에서 발생하므로 어느 한 지점에서의 모니터링으로는 탐지가 불가능하다.
| 공격 | 핵심 방어 |
|---|---|
| Request Smuggling | 프록시와 백엔드의 HTTP 파서 통일, HTTP/2 end-to-end 사용 |
| Host Header Poisoning | Host 헤더 화이트리스트 검증, 설정에서 도메인 하드코딩 |
| Cache Poisoning | Unkeyed Input 최소화, Vary 헤더 적절히 설정 |
| CRLF Injection | 응답 헤더에 반영되는 모든 입력에서 CRLF 문자 제거 |
| CORS Misconfiguration | Origin 화이트리스트 명시, 와일드카드 + Credentials 조합 금지 |
| HTTP/2 Rapid Reset | RST_STREAM Rate Limiting, 동시 스트림 수 제한, 패치 적용 |
HTTP 프로토콜의 공격 표면은 명세의 모호함, 구현 간 불일치, 구성 오류 세 가지에서 비롯된다.
Request Smuggling은 파싱 불일치를, Cache Poisoning은 캐시 키 설계를, CORS는 접근 정책 구성을 악용한다. 공통점은 모두 정상 HTTP 트래픽 안에서 동작한다는 것이다.
HTTP 통신의 구조와 한계에서 프로토콜의 기본 구조를, API 보안의 현실에서 애플리케이션 수준의 위협을 다뤘다면, 이 글은 그 사이 — 프로토콜 수준의 공격 표면을 다룬 것이다.
다음에 알아볼 주제:
CL.TE만 Request Smuggling의 유일한 형태는 아니다. TE.CL 변종에서는 프론트엔드가 Transfer-Encoding을, 백엔드가 Content-Length를 사용한다. 공격자는 chunked 인코딩 내부에 숨긴 두 번째 요청이 백엔드에서 Content-Length 기준으로 파싱되면서 분리되는 점을 악용한다.
TE.TE 변종은 두 시스템 모두 Transfer-Encoding을 지원하지만, 헤더 난독화에 대한 처리가 다른 경우 발생한다. Transfer-Encoding: xchunked, Transfer-Encoding: chunked에 추가 공백, 또는 대소문자 변형 같은 기법으로 한쪽 시스템만 Transfer-Encoding을 인식하게 만든다.
HTTP/2 프론트엔드가 HTTP/1.1 백엔드로 프록시하는 환경에서 H2.CL 공격이 가능하다. HTTP/2는 자체적인 프레임 길이로 메시지 경계를 관리하지만, HTTP/1.1로 변환될 때 Content-Length 헤더가 추가된다. 공격자가 HTTP/2 요청에 Content-Length 헤더를 직접 포함하면, 프론트엔드는 프레임 길이를 사용하고 백엔드는 Content-Length를 사용하여 불일치가 발생한다.
2023년 Netflix 기술 블로그에 따르면 H2.CL 취약점은 주요 CDN 제공업체 3곳에서 발견됐으며, 패치 전까지 수백만 사용자에게 영향을 미칠 수 있었다.
Request Smuggling 방어의 핵심은 프론트엔드와 백엔드의 HTTP 파싱 동작을 일치시키는 것이다. 모호한 요청(CL과 TE가 동시에 존재하는 경우)은 즉시 거부해야 한다. HTTP/2를 end-to-end로 사용하면 프로토콜 다운그레이드 자체를 제거할 수 있다.
웹 캐시 포이즈닝 방어는 캐시 키에 모든 관련 헤더를 포함하거나, Vary 헤더를 적극 활용하는 것이 핵심이다. Cloudflare는 2024년부터 알려진 공격 패턴의 unkeyed 헤더를 자동 정규화하는 기능을 도입했다.
2019년 PortSwigger의 James Kettle은 Request Smuggling을 사용하여 여러 대형 웹사이트에서 취약점을 발견했다. 그 중 하나는 미국 국방부 웹사이트였다. 프론트엔드 서버(CDN)는 Transfer-Encoding 헤더를 우선 처리하고, 백엔드 서버는 Content-Length를 우선 처리하여 요청 경계가 어긋났다.
이 불일치를 이용하면 공격자의 HTTP 요청 일부가 다음 사용자의 요청 앞에 붙는다. 예를 들어 공격자가 조작된 요청을 보내면, 다음 순서의 정상 사용자 요청이 공격자가 제어하는 URL로 리다이렉트되거나, 사용자의 쿠키가 공격자에게 전달될 수 있다. 이를 Request Smuggling to Credential Stealing이라 한다.
HTTP 헤더는 CRLF( )로 구분된다. 사용자 입력이 HTTP 응답 헤더에 반영될 때 CRLF 문자를 주입하면 임의의 헤더를 추가하거나 응답 본문을 조작할 수 있다.
예를 들어 리다이렉트 URL에 %0d%0aSet-Cookie:%20admin=true를 삽입하면 응답에 악성 쿠키가 추가된다. 더 심각한 경우 %0d%0a%0d%0a(빈 줄)를 삽입하면 헤더 영역이 끝나고 본문이 시작되어, 공격자가 응답 본문 자체를 제어할 수 있다. 이를 HTTP Response Splitting이라 한다.
대부분의 웹 서버는 Host 헤더로 요청을 라우팅한다. 하나의 IP에 여러 도메인이 호스팅되는 가상 호스트 환경에서 Host 헤더는 필수다. 하지만 이 헤더를 신뢰하면 보안 문제가 발생한다.
비밀번호 재설정 이메일에서 Host 헤더의 값을 링크 도메인으로 사용하는 애플리케이션이 있다. 공격자가 Host 헤더를 자신의 도메인으로 변경하면, 피해자에게 발송되는 재설정 링크가 공격자의 서버를 가리킨다. 피해자가 링크를 클릭하면 재설정 토큰이 공격자에게 전달된다.
X-Forwarded-Host, X-Host, X-Forwarded-Server 같은 대체 헤더도 같은 위험을 가진다. Django의 ALLOWED_HOSTS 설정, 또는 화이트리스트 기반 Host 헤더 검증이 필수적인 이유다.
HTTP 파싱 불일치 공격에 대한 가장 근본적인 방어는 프록시 체인의 단순화다. 프론트엔드 서버와 백엔드 서버 사이의 계층이 많을수록 파싱 불일치가 발생할 확률이 높아진다. CDN → 로드밸런서 → WAF → 리버스 프록시 → 애플리케이션 서버로 이어지는 5단계 체인에서 각 구성 요소의 HTTP 파서가 미묘하게 다르게 동작할 수 있다.
HTTP/2와 HTTP/3의 바이너리 프레이밍은 텍스트 기반 HTTP/1.1의 파싱 모호성을 구조적으로 제거한다. 하지만 많은 리버스 프록시가 클라이언트와는 HTTP/2로 통신하면서 백엔드와는 HTTP/1.1로 통신하는 다운그레이드 구성을 사용한다. 이 변환 과정에서 새로운 파싱 불일치가 발생할 수 있다.
ModSecurity, AWS WAF 등의 웹 애플리케이션 방화벽은 Request Smuggling 탐지 규칙을 제공한다. Content-Length와 Transfer-Encoding이 동시에 존재하는 요청, 비표준 Transfer-Encoding 값, 잘못된 청크 인코딩을 탐지하여 차단한다.
end-to-end HTTP/2 통신을 사용하면 텍스트 기반 파싱 모호성을 근본적으로 제거할 수 있다. 하지만 레거시 백엔드와의 호환성 때문에 완전한 전환에는 시간이 걸린다. 그 전까지는 WAF 규칙 강화, 프록시 설정의 정규화(Normalization), 그리고 정기적인 Request Smuggling 테스트가 최선의 방어 전략이다. Burp Suite의 HTTP Request Smuggler 확장과 같은 도구로 주기적으로 자체 환경을 점검하는 것이 권장된다.
AI 활용 안내 이 글은 AI(Claude)의 도움을 받아 작성되었습니다. 인용된 통계와 사례는 참고 자료에 명시된 출처에 근거하며, 설명을 위한 일부 표현은 각색되었습니다.
면책 조항 본 글은 보안 인식 제고를 위한 교육 목적으로 작성되었습니다. 언급된 공격 기법을 실제로 시도하는 행위는 「정보통신망법」, 「형법」 등에 따라 처벌받을 수 있으며, 본 블로그는 이에 대한 법적 책임을 지지 않습니다.