HTTP 요청 스머글링

Content-Length와 Transfer-Encoding의 모호성, CL.TE/TE.CL/TE.TE 변종, 프런트엔드·백엔드 desync, 공격 영향, 그리고 정규화·엔드투엔드 HTTP/2·모호한 요청 거부 같은 방어법을 설명합니다.

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

이 해석 불일치는 어느 서버가 어느 헤더를 쓰느냐에 따라 세 가지 대표 변종으로 나뉩니다.

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), 캐시를 오염시켜 악성 콘텐츠를 널리 퍼뜨릴 수 있습니다. 하나의 파싱 불일치가 인증·격리·캐시라는 여러 방어선을 동시에 무너뜨리는 셈입니다.

방어