HTTP 요청 스머글링(request smuggling)은 하나의 TCP 연결 위에서 요청의 경계를 서로 다르게 해석하게 만들어, 공격자의 요청 일부를 다음 사용자의 요청에 몰래 끼워 넣는 공격입니다. 오늘날 웹은 CDN·리버스 프록시 같은 프런트엔드가 요청을 받아 하나의 연결로 백엔드 서버에 전달하는 구조가 많은데, 두 서버가 요청이 어디서 끝나는지를 다르게 판단하면 이 틈이 열립니다.
Content-Length vs Transfer-Encoding
요청 본문의 길이를 나타내는 방법은 두 가지입니다. Content-Length는 본문의 바이트 수를 숫자로 명시하고, Transfer-Encoding: chunked는 본문을 여러 청크로 쪼개 각 청크 앞에 크기를 16진수로 붙이고 0 크기 청크로 끝을 표시합니다. 문제는 한 요청에 두 헤더가 동시에 있을 때입니다. 표준(RFC 9112)은 이 경우 Transfer-Encoding을 우선하고 Content-Length를 무시하라고 정하지만, 현실의 모든 서버가 똑같이 처리하지는 않습니다. 프런트엔드는 Content-Length를 믿고 백엔드는 Transfer-Encoding을 믿는 식으로 해석이 갈리면 요청 경계가 어긋납니다.
CL.TE / TE.CL / TE.TE
이 해석 불일치는 어느 서버가 어느 헤더를 쓰느냐에 따라 세 가지 대표 변종으로 나뉩니다.
- CL.TE: 프런트엔드는 Content-Length를, 백엔드는 Transfer-Encoding을 사용. 프런트엔드가 본문 전체를 하나로 보고 넘기지만, 백엔드는 chunked로 파싱해 일부를 '다음 요청의 시작'으로 남겨 둡니다.
- TE.CL: 반대로 프런트엔드가
Transfer-Encoding을, 백엔드가Content-Length를 사용. - TE.TE: 둘 다
Transfer-Encoding을 지원하지만, 한쪽이 헤더를 난독화(예:Transfer-Encoding : chunked, 공백/오타/중복)하면 그 서버만 이를 무시하게 만들어 불일치를 유도.
POST / HTTP/1.1
Host: victim.example
Content-Length: 13
Transfer-Encoding: chunked
0
GPOST / HTTP/1.1
Host: victim.example
...
위 CL.TE 예시에서 프런트엔드는 Content-Length: 13을 보고 0\r\n\r\nGPOST...까지 한 요청으로 백엔드에 전달합니다. 그러나 백엔드는 Transfer-Encoding: chunked를 따라 0 청크에서 요청이 끝났다고 보고, 뒤에 남은 GPOST / HTTP/1.1 ...을 다음 요청의 앞부분으로 취급합니다. 이 남겨진 조각이 바로 '스머글된(밀반입된)' 요청입니다.
프런트엔드·백엔드 desync
이렇게 남은 조각은 백엔드의 연결 버퍼에 남아, 바로 다음에 그 연결로 들어온 다른 사용자의 정상 요청 앞에 붙습니다. 그 결과 피해자의 요청이 공격자가 심어 둔 경로·헤더와 뒤섞여 처리됩니다(요청 desync). 프런트엔드와 백엔드가 요청 스트림에 대한 '합의'를 잃었기 때문에, 이 어긋남은 연결이 유지되는 동안 연쇄적으로 이어질 수 있습니다.
공격 영향
요청 스머글링의 위력은 큽니다. 공격자는 프런트엔드의 접근 제어(WAF·인증)를 우회해 백엔드의 금지된 경로에 도달하거나, 다른 사용자의 요청을 탈취해 세션·자격 증명을 훔치거나, 응답을 뒤섞어 피해자에게 엉뚱한 페이지를 보이게 하거나(응답 queue poisoning), 캐시를 오염시켜 악성 콘텐츠를 널리 퍼뜨릴 수 있습니다. 하나의 파싱 불일치가 인증·격리·캐시라는 여러 방어선을 동시에 무너뜨리는 셈입니다.
방어
- 모호한 요청 거부:
Content-Length와Transfer-Encoding이 둘 다 있거나, 청크 문법이 어긋난 요청은 그대로 처리하지 말고400으로 거부합니다. - 정규화(normalize): 프런트엔드가 요청을 백엔드로 넘기기 전에 헤더를 한 가지 형태로 재작성해, 프런트·백엔드가 반드시 같은 해석을 하도록 만듭니다. 난독화된
Transfer-Encoding변형을 잡아냅니다. - 엔드투엔드 HTTP/2: HTTP/2는 본문 길이를 프레임 구조로 명확히 규정해 이 모호성을 원천 차단합니다. 다만 프런트엔드가 이를 HTTP/1.1로 다운그레이드해 백엔드에 전달하면 취약점이 되살아나므로, 프런트-백엔드 구간까지 HTTP/2를 유지하는 것이 안전합니다.
- 연결 재사용 정책 점검: 백엔드로 가는 연결의 재사용·keep-alive 동작을 이해하고, 파서가 일치하는 구현으로 통일합니다.