댓글 (0)
댓글은 익명으로 작성되며, 삭제 비밀번호를 설정하면 본인만 삭제할 수 있습니다. 비밀번호를 설정하지 않은 댓글은 누구나 삭제할 수 있습니다.
HTTP 리퀘스트 스머글링의 근본 원인을 포스텔의 법칙(견고성 원칙)에서 짚고, Content-Length와 Transfer-Encoding 불일치가 만드는 CL.TE·TE.CL·TE.TE 세 변종과 RFC 9112 기반 방어를 분석한다.
댓글은 익명으로 작성되며, 삭제 비밀번호를 설정하면 본인만 삭제할 수 있습니다. 비밀번호를 설정하지 않은 댓글은 누구나 삭제할 수 있습니다.
2019년, 한 보안 연구원이 보낸 변형된 HTTP 요청 하나가 여러 대형 웹사이트에서 다음 접속자의 요청을 가로챘다. 악성코드도, 탈취한 비밀번호도 없었다. 그가 악용한 것은 40여 년 전 인터넷을 키운 한 줄의 설계 철학이었다. "보내는 쪽은 엄격하게, 받는 쪽은 관대하게."
이 관대함이 바로 리퀘스트 스머글링(HTTP Request Smuggling)의 근본 원인이다. 특정 제품의 버그가 아니라 프로토콜을 다루는 태도에서 비롯되기 때문에, 한 번 잊혔다가 2019년 다시 부활하는 식으로, 같은 공격이 형태만 바꿔 반복되고 있다.
리퀘스트 스머글링은 보낸 요청이 형식적으로 완전히 유효한 HTTP라는 점에서 위험하다. 방화벽도, 침입 탐지 시스템도 이를 정상 트래픽으로 판단한다. 공격 한 번으로 다음 사용자의 세션 쿠키를 탈취하거나, 응답을 통째로 바꿔치기하거나, WAF 검사를 통과시킬 수 있다.
더 까다로운 점은 탐지 지점이 분산된다는 것이다. 공격은 프론트엔드(CDN·로드밸런서)와 백엔드 서버 사이의 틈에서 발생한다. 어느 한 쪽만 들여다봐서는 보이지 않는다.
그리고 무엇보다, 이 공격의 뿌리가 특정 코드 결함이 아니라 프로토콜을 다루는 태도에 있다는 점이 핵심이다. 그래서 한 제품을 고쳐도 다른 조합에서 같은 문제가 되살아난다.
1980년 인터넷 초기 표준을 설계한 존 포스텔(Jon Postel)은 RFC 761에 한 문장을 남겼다. "구현은 보수적으로 보내고, 관대하게 받아들여야 한다." 이른바 포스텔의 법칙, 또는 견고성 원칙(Robustness Principle)이다.
이 관대함은 초기 인터넷의 상호운용성을 키운 일등 공신이었다. 구현이 조금씩 달라도 서로 너그럽게 받아주니 네트워크가 빠르게 확장됐다.
문제는 보안이었다. 받는 쪽이 모호한 입력을 알아서 "좋게 해석"하면, 그 해석은 구현마다 달라진다. 2023년 IETF는 RFC 9413에서 이 관용성이 장기적으로 프로토콜을 취약하고 경직되게 만든다고 공식적으로 지적했다. 관대하게 받은 입력의 해석이 갈리는 순간, 그 틈이 공격 표면이 된다.
리퀘스트 스머글링이 노리는 모호함은 구체적이다. HTTP/1.1에서 요청 본문이 어디서 끝나는지를 알려주는 헤더가 두 개 있다.
| 헤더 | 본문 끝을 정하는 방식 |
|---|---|
| Content-Length | 본문의 바이트 수를 숫자로 명시 |
| Transfer-Encoding: chunked | 본문을 조각으로 나눠 보내고, 크기 0인 조각으로 종료 |
두 헤더가 한 요청에 동시에 들어오면 어떻게 될까. 표준은 Transfer-Encoding을 우선하라고 했지만, 오랫동안 그 강제력이 "SHOULD"(권장) 수준이었다. 모든 서버가 똑같이 처리하지 않았다는 뜻이다.
바로 이 지점에서, 앞단 서버와 뒷단 서버가 같은 요청을 보고 서로 다른 곳에서 요청이 끝났다고 판단하는 일이 벌어진다.
앞단(프록시)은 Content-Length를, 뒷단(백엔드)은 Transfer-Encoding을 따르는 경우를 보자. 공격자는 두 헤더를 모두 담은 요청을 보낸다.
프록시는 Content-Length가 가리키는 길이만큼을 "하나의 요청"으로 보고 전부 검사한 뒤 통과시킨다. 그런데 백엔드는 Transfer-Encoding의 종료 신호(크기 0 조각)에서 요청이 일찍 끝났다고 본다. 같은 바이트 묶음인데 끝나는 지점이 둘로 갈린 것이다. 백엔드가 보기에, 프록시가 한 요청이라며 넘긴 데이터에는 요청 하나와 정체불명의 나머지 바이트가 함께 들어 있다.
여기서 핵심은 프록시와 백엔드가 효율을 위해 하나의 연결을 여러 사용자가 돌려 쓴다는 점이다(HTTP keep-alive). 백엔드는 다 처리하지 못한 나머지 바이트를 버리지 않고, 같은 연결로 들어올 다음 요청의 앞부분으로 간주해 버퍼에 남겨 둔다. 그리고 잠시 뒤 진짜 다음 사용자의 요청이 같은 연결로 도착하면, 공격자가 남긴 그 바이트 뒤에 피해자의 요청이 통째로 이어 붙는다.
그 결과 피해자가 보낸 요청은 더 이상 피해자가 의도한 그대로가 아니다. 공격자가 심어둔 경로로 빨려 들어가거나, 피해자의 인증 쿠키가 공격자가 미리 심어 둔 요청에 실려 되돌아온다. 사용자가 아무 잘못도 하지 않았는데 자기 세션을 도둑맞는 셈이다. 이것을 자격 증명 탈취형 스머글링이라 부른다.
방향을 바꾸거나 난독화를 더하면 변종이 된다.
TE.TE가 특히 흥미롭다. 양쪽 서버가 모두 Transfer-Encoding을 이해하는데도, 헤더 이름에 미묘한 변형(여분의 공백, 대소문자 섞기 등)을 주면 한쪽 서버만 그것을 유효한 Transfer-Encoding으로 인식한다. 다시 한번, "관대하게 받아들이는" 쪽이 함정에 빠지는 구조다.
바이너리로 메시지 경계를 다루는 HTTP/2는 이 모호함을 구조적으로 없앤다. 하지만 현실의 많은 프록시는 클라이언트와는 HTTP/2로, 백엔드와는 HTTP/1.1로 대화한다. 이 변환 과정에서 새로운 불일치가 생긴다.
PortSwigger의 보안 연구원 제임스 케틀(James Kettle)은 2019년 "HTTP Desync Attacks"로 이 공격을 부활시켰고, 2021년 "HTTP/2: The Sequel is Always Worse"에서 HTTP/2를 HTTP/1.1로 강등하는 과정의 desync(H2.CL·H2.TE)를 시연했다. 프로토콜을 새로 만들어도, 옛 프로토콜과 섞이는 경계에서 같은 문제가 되살아난 것이다.
리퀘스트 스머글링은 은밀하지만 완전히 투명하지는 않다. 공격자가 백엔드의 요청 큐를 오염시키는 순간, 다음 사용자의 응답에 엉뚱한 내용이 섞여 나온다. 정상적으로는 있을 수 없는 응답-요청의 불일치가, 충분히 관측하면 드러나는 신호다.
탐지와 방어의 핵심은 다음과 같다.
지시형 처방보다 중요한 사실은, 이 모든 방어가 결국 '관대하게 받지 말고, 모호하면 거부하라'는 한 원칙으로 수렴한다는 점이다. 포스텔의 법칙이 연 문을, 포스텔의 법칙을 뒤집어 닫는 셈이다.
AI 활용 안내 이 글은 AI(Claude)의 도움을 받아 작성되었습니다. 인용된 통계와 사례는 참고 자료에 명시된 출처에 근거하며, 설명을 위한 일부 표현은 각색되었습니다.
면책 조항 본 글은 보안 인식 제고를 위한 교육 목적으로 작성되었습니다. 언급된 공격 기법을 실제로 시도하는 행위는 「정보통신망법」, 「형법」 등에 따라 처벌받을 수 있으며, 본 블로그는 이에 대한 법적 책임을 지지 않습니다.