요청을 몰래 보내는게 가능해?(HTTP request smuggling)

2024. 6. 9. 17:48보안, 해킹/웹

HTTP request smuggling이란?

직역하면 HTTP 요청 밀수다. 흔히 '약'을 몰래 반입할 때 쓰는 단어지만 이것만큼 완벽한 표현이 없는 공격방식이다. HTTP request smuggling이란, 프론트엔드 서버(로드 밸런서, 리버스 프록시 등)와 백엔드 서버에서 요청 패킷이 처리되는 방식의 차이를 악용해 프론트엔드 서버의 필터링 로직을 우회하여 백엔드 서버로 악성 공격 패킷을 전송하는 공격 방식이다.

... 말이 어렵다. 차근차근 알아보자.

프론트엔드 서버와 백엔드 서버로 구성된 웹 어플리케이션이 존재한다. 사용자가 이러한 웹 어플리케이션에 요청을 보내게 될 경우 이 요청은 프론트엔드 서버로 전달되고, 프론트엔드 서버는 백엔드 서버로 요청을 전달하는 구조를 가진다.

이때 프론트엔드 서버는 단일 사용자가 아닌 여러 사용자로부터 요청을 받아 백엔드로 전달한다. 프론트엔드 서버는 이 요청들의 헤더를 참고하여 각 요청의 시작 지점과 끝 지점을 확인하고, 처리 순서대로 그대로 백엔드 서버에 전달한다. 그리고 이 요청을 받은 백엔드 서버에서도 요청의 헤더를 참고하여 요청의 시작 지점과 끝 지점을 확인하고, 이를 통해 각 요청을 구분하여 처리한다.

출처:portswigger

그런데 사용자(공격자)가 프론트엔드 서버로 요청을 보낼 때 헤더를 적당히 조작하여 헤더의 끝 부분이 어딘지 애매하게 만들 수 있다.

단순하게 예로 들면, 5/21(수) 18시까지 약속된 장소에서 만나자고 하는 거다. 사실 이 글을 작성하는 2024년 기준 5월 21일은 화요일이다. 뭔가 오타를 낸 건 알겠는데 원래 의도가 화요일에 오라는 건지 수요일에 오라는 건지 알 수가 없다.

이렇게 헤더의 시작과 끝이 어디인지 애매하게 보내면 다음 그림과 같이 프론트엔드 서버와 백엔드 서버에서 요청을 다르게 해석하게 될 수 있다.

공격자가 요청 뒷부분에 악성 패킷을 심는다. 헤더 조작을 통해 프론트와 백엔드의 불일치를 발생시켜 바로 뒷 요청의 앞부분에 공격자의 악성 패킷이 위치하게 된다.

이때 문제가 발생하는데, 프론트엔드 서버에서는 간혹 접근 제어 등의 보안 기능을 구현하는 경우가 있다. 이때 공격자가 몰래 넣은 요청 패킷이 원래대로라면 프론트엔드 서버에서 걸러졌어야 하는데 프론트엔드 서버가 이를 확인하지 못하고 백엔드 서버로 넘겨버릴 수가 있다. 백엔드 서버는 프론트엔드 서버가 악성 요청 패킷을 적당히 걸러 줄 것이라고 믿고 있기 때문에 아무렇지 않게 요청을 처리하고, 공격자가 삽입한 요청 패킷을 읽게 되어 불가능했던 요청이 가능하게 된다.

구체적인 원리

HTTP request smuggling은 HTTP/1.1 버전에서 패킷 길이를 지정할 때 두 가지 헤더를 지원하기 때문에 발생한다. HTTP 패킷을 파싱할 때 HTTP 패킷 길이를 지정하는 방법에는 Content-Length 헤더를 설정하는 방법과 Transfer-Encoding 헤더를 설정 하는 방법 두 가지가 있다.

Content-Length 헤더는 메시지 본문의 길이를 바이트 단위로 지정한다.

POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

q=smuggling

 

Transfer-Encoding 헤더는 패킷이 길어 분할 인코딩이 필요할 때 사용한다. Transfer-Encoding 헤더는 이때 패킷이 청크 단위로 분할되어 있음을 알려주는 역할을 한다. 각 청크는 청크 크기(16진수), 청크 내용으로 구성되며, 크기가 0인 청크로 종료된다.

POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

b
q=smuggling
0

 

b는 16진수로 11이기 때문에 11자가 온다. 0은 끝나는 지점이다.

 

이때, 이 두 가지 헤더를 동시에 사용할 경우 충돌이 발생할 수 있다. 예를들어 다음과 같이 헤더를 설정할 수 있다.

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED

 

(아 참고로 요청의 body에서 줄바꿈은 \r\n으로, 문자 2개다. 이는 CR(Carrige Return), LF(Line Feed)라는 두 개의 개행문자로, 그냥 줄바꿈을 표현하는 하나의 방식이라고 생각하면 된다. 여기서 설명하기엔 너무 길어지니 CRLF에 대해 검색해보면 된다.)

 

이 경우, Content-Length를 기준으로 보면 본문은 SMUGGLED까지지만 Transfer-Encoding을 기준으로 보면 0에서 종료된다. 만약 프론트엔드 서버에서는 Content-Length를 기준으로, 백엔드 서버에서는 Transfer-Encoding을 기준으로 요청을 나눈다면 프론트엔드 서버는 하나의 요청이지만, 백엔드 서버에서는 하나의 요청으로 끝나지 않는다. 이때 SMUGGLED 위치에 임의의 요청 패킷을 넣으면 백엔드 서버는 두 개의 요청으로도 인식할 수 있는 것이다.

 

일반적으로 Content-Length 헤더와 Transfer-Encoding 헤더가 둘 다 존재하는 경우 Content-Length 헤더를 무시하도록 설정해서 이러한 문제가 발생하지 않도록 하지만 두 개 이상의 서버가 연결되어 있는 경우 다음과 같은 이유로 HTTP request smuggling 취약점이 발생할 수 있다.

  • 일부 서버가 Transfer-Encoding 헤더를 지원하지 않는 경우
  • Transfer-Encoding 헤더가 난독화*되어 일부 서버에서 처리되지 않는 경우
난독화라고 하면 보통 분석하기 어렵게 하기 위한 목적으로 코드를 읽기 어렵게 처리하는 것을 말하지만, 여기서 말하는 난독화는 일부 서버에서 인식하지 못하도록 약간 변형하는 것을 말한다.

단순한 예시로 Transfer-Encoding: chunked라는 헤더를 Transfer-Encoding : chunked라고 쓸 수 있다. (콜론(:) 앞에 공백이 추가되었다.)
이게 대체 무슨 차이가 있냐고 생각할 수 있는데, 실제로 타 서비스에서 이 공백 하나 때문에 요청 밀수 취약점이 발생하여 불특정 다수의 계정을 탈취할 수가 있었다.

참고링크: https://hackerone.com/reports/737140

 

HTTP request smuggling 공격 종류

일반적인 HTTP request smuggling은 HTTP/1.1 요청에 Content-Length 헤더와 Transfer-Encoding 헤더를 이중으로 설정하고, 프론트엔드 서버와 백엔드 서버 간에 패킷을 다르게 처리하도록 하는 것이다. 두 서버가 어떻게 동작하느냐에 따라 다음과 같은 종류의 공격방식을 선택할 수 있다.

  • CL.TE(Content-Length -> Transfer-Encoding)
  • TE.CL(Transfer-Encoding -> Content-Length)
  • TE.TE(Transfer-Encoding -> Transfer-Encoding) 
여기서 소개하는 방식은 전부 프론트엔드 서버와 백엔드 서버가 HTTP/1.1 버전일 때 유효한 공격이다. HTTP/2 버전에서는 다른 방법을 찾아야 한다.

1. CL.TE

프론트엔드 서버에서는 Content-Length, 백엔드 서버에서는 Transfer-Encoding을 사용하는 경우이다. 아래와 같은 형태로 공격이 가능하다.

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED

 

프론트엔드 서버에서는 Content-Length 헤더를 통해 패킷을 파싱한다. 따라서 위 요청을 하나의 요청으로 처리하여 백엔드 서버로 전달한다.

반면에 백엔드 서버에서는 Transfer-Encoding 헤더를 통해 패킷을 파싱한다. 그렇기 때문에 0에서 크기가 0인 청크로 인식하게 되고, 요청의 끝으로 판단한다. 요청을 처리한 후 백엔드 서버에는 SMUGGLED라는 요청이 남게 되는데, 이후 공격자나 다른 클라이언트가 프론트엔드 서버로 다른 요청을 보내면 프론트엔드 서버는 처리한 요청을 백엔드 서버로 전달한다. 이때 백엔드 서버는 프론트엔드 서버로부터 이후에 보낸 요청을 받지만, 서버에 남아있는 SMUGGLED라는 요청을 이후에 받은 요청의 시작점으로 인식하고 SMUGGLED 요청을 처리하게 된다.

2. TE.CL

프론트엔드 서버에서는 Transfer-Encoding, 백엔드 서버에서는 Content-Length를 사용하는 경우이다. 아래와 같은 형태로 공격이 가능하다.

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 3
Transfer-Encoding: chunked

8
SMUGGLED
0

 

프론트엔드 서버에서는 Transfer-Encoding 헤더를 통해 패킷을 파싱한다. 따라서 위 요청을 두 개의 청크로 이루어진 하나의 요청으로 처리하여 백엔드 서버로 전달한다.

반면에 백엔드 서버에서는 Content-Length 헤더를 통해 패킷을 파싱한다. 그렇기 때문에 길이가 3인 8\r\n 위치까지를 요청의 끝으로 인식한다. 요청을 처리한 후 백엔드 서버에는 SMUGGLED라는 요청이 남게 되고, 이후의 과정은 CL.TE에서와 동일하다.

3. TE.TE

두 서버에서 모두 Transfer-Encoding 헤더를 지원한다. 그래서 위에서 잠깐 언급한 난독화 기법을 사용할 것이다.

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-length: 4
Transfer-Encoding: chunked
Transfer-encoding: cow

5b
POST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15

x=1
0

 

프론트엔드 서버에서는 정상적인 Transfer-Encoding 헤더를 인식하여 요청을 파싱한다. (참고로, Transfer-Encoding 헤더와 Content-Length 헤더가 둘 다 되어 있으면 일반적으로는 Transfer-Encoding 헤더를 우선적으로 처리한다.) 따라서 위 요청을 하나의 요청으로 처리한다.

반면에 백엔드 서버에서는 프론트엔드 서버와 다른 처리방식 때문에 Transfer-Encoding 헤더 처리 과정에서 오류가 발생한다. (이 경우, 뒤의 Transfer-Encoding 헤더가 2개 이상일 경우 가장 마지막 헤더만 인식한다던지 여러 이유가 있을 것이다.) 그래서 Content-Length 헤더를 통해 파싱을 진행하게 되고, 공격자가 중간에 삽입한 POST 요청이 실행되게 된다.

HTTP request smuggling의 악용

그래서 이 취약점이 얼마나 큰 문제를 발생시키는지 알아보자.

1. 프론트엔드 보안 제어 우회

일부 어플리케이션에서는 프론트엔드 서버에서 일부 보안 기능을 구현하고, 받은 요청을 검토하여 처리 허용 여부를 결정하기도 한다. 이때 접근 제어를 우회하여 일반적으로는 불가능한 요청을 백엔드 서버에 직접 요청할 수 있다.

  • 예시

사용자가 /home에 접근할 수 있지만 /admin에는 접근할 수 없고, 이러한 접근 제어를 프론트엔드 서버에서 수행한다고 가정한다. 이때 아래와 같은 방법으로 이러한 접근 제한을 우회할 수 있다.

POST /home HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 62
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: vulnerable-website.com
Foo: xGET /home HTTP/1.1
Host: vulnerable-website.com

 

이 경우 Content-Length 값은 Foo: x까지를 하나의 요청으로 인식하도록 설정되어 있기 때문에 프론트엔드 서버는 두 개의 /home 요청을 받은 것으로 간주하고 이를 백엔드 서버로 전달한다. 하지만 백엔드 서버에서는 /home에 대한 요청과 /admin에 대한 요청으로 인식하기 때문에 프론트엔드의 보안 설정을 우회하여 /admin에 대한 요청을 할 수 있다. 백엔드 서버는 해당 요청이 프론트엔드의 보안 검사를 통과하였다고 믿기 때문이다.

2. 프론트엔드에서 재작성된 요청 노출

몇몇 서비스에서 프론트엔드 서버는 요청이 백엔드 서버로 전달되기 전에 몇 가지 추가 요청 헤더를 붙여 요청을 재작성하기도 한다. 일반적으로 다음과 같은 헤더가 추가될 수 있다.

  • TLS 연결 종료 및 사용된 프로토콜, 암호 관련 헤더
  • 사용자 IP가 포함된 X-Forwarded-For 헤더
  • 세션 토큰을 기반으로 사용자 ID 결정, 사용자 식별 헤더
  • 기타 중요 정보

그리고 이러한 헤더가 누락된 요청은 백엔드 서버에서 정상적으로 처리되지 않을 가능성이 높다. 프론트엔드 서버를 통과하지 않은 비정상적인 요청이라는 뜻이니 거르는 것일 수도 있지만, 애초에 헤더가 추가된다는 것을 전제로 기능 구현을 해두었을 것이기 때문에 어찌보면 당연하다.

문제는 공격자가 프론트엔드 서버에서 추가되는 헤더를 알 방법이 일반적으로는 없다는 것이다. 서버 내에서 자체적으로 돌아가는 기능이기 때문에 프론트엔드 서버를 까보거나 백엔드 서버에서 받은 요청을 확인해봐야 할 것이다. (근데 애초에 이렇게 서버에 접근하는 게 가능했으면 이런 공격을 할 필요도 없겠지)

이때 다음과 같은 과정을 거쳐 필요한 헤더를 확인할 수 있다.

  1. 요청 매개변수의 값이 어플리케이션의 응답 Body에 포함되는 POST 요청을 찾는다.
  2. 해당 매개변수를 요청 Body의 마지막에 위치 시킨다.
  3. 해당 요청을 이용해 HTTP request smuggling을 진행하면 Front-end 서버에서 추가되는 헤더의 내용이 매개변수 값으로 입력되어 페이지에 출력된다.

...라는게 portswigger의 설명이다. 예시를 보면서 하나하나 알아보자.

  • 예시

몇몇 로그인 페이지는 잘못된 회원정보를 입력했을 경우 아이디는 입력된 상태로 로그인 창을 띄워주는 경우가 있다. 아이디는 웬만하면 잘못 입력한 것을 사전에 확인할 수 있으니 맞다고 가정하고, 비밀번호만 재입력을 받도록 하는 목적일 것이다. 일종의 편의성 기능이다.

대표적인 예시 네이버. 로그인에 실패하더라도 아이디는 입력된 상태로 비밀번호만 재입력을 요구한다.

 

아래는 네이버 로그인 요청은 아니지만, 비슷한 구조의 예시 요청이다. 로그인에 이메일이 사용되고, 요청에 email이라는 파라미터가 포함된다.

POST /login HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 28

email=wiener@normal-user.net

 

로그인에 실패하면 아이디(또는 이메일)는 입력된 상태의 로그인 페이지를 응답하는데, 이때 로그인에 입력했던 email 파라미터의 값을 그대로 가져와 사용한다. 그렇게 되면 응답에 아래와 같이 email 파라미터의 값이 포함되어 반환된다.

<input id="email" value="wiener@normal-user.net" type="text">

 

이때 아래와 같은 요청을 통해 프론트엔드 서버에서 붙는 추가 요청 헤더에 관한 정보를 알아낼 수 있다.

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 130
Transfer-Encoding: chunked

0

POST /login HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 100

email=POST /login HTTP/1.1
Host: vulnerable-website.com
...

 

CL.TE 취약점이라 가정한다. 이때 첫 요청의 Content-Length 헤더는 email= 부분을 요청의 끝으로 지정하고 있다. 그리고 email= 뒤에는 다음으로 도착한 두 번째 요청이 붙게 된다. 가장 맨 앞의 요청을 1-1번, 공격자가 중간에 삽입한 /login 요청을 1-2번, email= 뒤에 붙은 /login 요청을 2번 요청이라고 하자.

프론트엔드 서버에서 1-1번 요청과 2번 요청에 추가 헤더를 붙여 백엔드 서버로 전달하면, 백엔드 서버는 1-2번 요청을 두 번째 요청으로 인식하고, 2번 요청을 email 파라미터의 값으로 간주한다.

그 결과로 서버는 email이 입력된 상태의 로그인 페이지를 응답하기 위해 email 파라미터 값을 가져오고, 2번 요청 내용이 포함된 다음과 같은 HTML 코드를 받을 수 있다.

<input id="email" value="POST /login HTTP/1.1
Host: vulnerable-website.com
X-Forwarded-For: 1.3.3.7
X-Forwarded-Proto: https
X-TLS-Bits: 128
X-TLS-Cipher: ECDHE-RSA-AES128-GCM-SHA256
X-TLS-Version: TLSv1.2
x-nr-external-service: external
...

 

여기서 못 보던 X-Forwarded-For, X-TLS-Bits 등의 헤더는 프론트엔드 서버에서 추가된 헤더다. 하지만 백엔드 서버에서는 이 2번 요청과 헤더를 1-2번 요청의 파라미터 값으로 인식하기 때문에 위와 같이 프론트엔드 서버에서 추가된 헤더가 노출되는 것이다.

원리만 보면 Reflected XSS와 유사하다고 볼 수도 있다. Reflected XSS는 요청의 내용 중 일부가 응답에 포함되는 점을 이용해 스크립트가 삽입된 요청을 피해자가 서버로 보내도록 한다. 클라이언트가 스크립트가 포함된 응답을 받으면 브라우저에서 스크립트가 실행된다. 이 공격방식은 스크립트 대신 요청+추가헤더를 넣어 추가헤더 정보를 브라우저에서 보기 위한 공격이라고 보면 된다.

 

3. 클라이언트 인증 우회

TLS handshake의 과정에서 서버는 인증서를 제공하여 클라이언트에 자신을 인증한다. 이때 인증서에는 등록된 호스트 이름과 일치해야 하는 Common name(CN)이라는 값이 포함된다. 클라이언트는 이를 통해 합법적인 서버와 통신하고 있는지 확인할 수 있다.

일부 사이트는 더 나아가 클라이언트도 서버에 인증서를 제시해야 하는 상호 TLS 인증 방식을 사용하기도 한다. 이 경우 클라이언트의 CN은 백엔드 서버의 접근 제어 매커니즘에서 사용될 수 있으며, 보통 사용자 이름 또는 이와 유사한 경우가 많다. 이러한 CN을 smuggling을 통해 백엔드 서버로 전달하여 사용자 인증을 우회하는 행위가 가능하다.

  • 예시

프론트엔드 서버는 받은 요청에 아래와 같이 클라이언트의 CN이 포함된 헤더를 추가하는 경우가 있다.

GET /admin HTTP/1.1
Host: normal-website.com
X-SSL-CLIENT-CN: carlos

 

X-SSL-CLIENT-CN과 같은 헤더는 사용자가 알 수 없기 때문에 벡엔드 서버는 이를 암시적으로 신뢰하는 경우가 많다. 만약 이러한 헤더 값에 들어갈 값을 알아내 정확히 보낼 수 있다면 클라이언트 접근 제어를 우회할 수 있다.

일반적으로 클라이언트에서 이러한 헤더를 보낸다면 프론트엔드 서버는 이를 덮어쓰기 때문에 백엔드 서버로 보내지더라도 문제가 발생하지 않는다. 하지만 HTTP request smuggling을 활용한다면 프론트엔드 서버로부터 요청을 숨길 수 있기 때문에 X-SSL-CLIENT-CN 헤더값을 조작하여 백엔드 서버로 보낼 수 있다.

POST /example HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 64
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
X-SSL-CLIENT-CN: administrator
Foo: x

4. Reflected XSS와의 연계

만약 어플리케이션이 HTTP request smuggling에 취약하고, reflected XSS에도 취약하다면 이 둘을 연계하여 어플리케이션의 다른 사용자를 공격할 수 있다.

HTTP request smuggling을 활용하여 reflected XSS를 진행한다면 사회공학기법 등을 통해 피해자가 특정 URL로 접속하도록 유도할 필요가 없다. XSS payload가 포함된 요청을 smuggling을 통해 백엔드 서버로 보내면, 다음에 서버로 요청을 보내는 사용자에게 XSS가 실행된다. 즉, 능동적인 XSS 공격이 가능하다는 의미이다.

  • 예시

어플리케이션의 User-Agent 헤더에 reflected XSS 취약점이 있다고 가정한다. 다음과 같은 요청을 보내 XSS 공격을 수행할 수 있다.

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 63
Transfer-Encoding: chunked

0

GET / HTTP/1.1
User-Agent: <script>alert(1)</script>
Foo: X

 

이때 XSS payload가 포함된 요청은 서버에 남아 다른 사용자가 서버에 요청을 보낼 경우 해당 사용자는 응답으로 XSS payload를 받게 된다.

5. On-site redirection을 open redirection으로 전환

많은 어플리케이션에서 on-site redirection 수행하고, 이때 요청의 Host 헤더의 이름을 redirection URL에 배치한다. 아래는 그 예시다.

GET /home HTTP/1.1
Host: normal-website.com

HTTP/1.1 301 Moved Permanently
Location: <https://normal-website.com/home/>

 

이를 악용하면 HTTP request smuggling을 이용하여 다른 사용자를 외부 도메인으로 redirection할 수 있다.

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 54
Transfer-Encoding: chunked

0

GET /home HTTP/1.1
Host: attacker-website.com
Foo: X

 

이러한 요청은 공격자의 웹사이트로의 redirection을 유발하며, 백엔드 서버로 요청을 보내는 다음 사용자에게 영향을 줄 수 있다.

GET /home HTTP/1.1
Host: attacker-website.com
Foo: XGET /scripts/include.js HTTP/1.1
Host: vulnerable-website.com

HTTP/1.1 301 Moved Permanently
Location: <https://attacker-website.com/home/>

 

이를 통해 공격자는 피해자를 자신의 웹사이트로 유도할 수 있다.

6. Web cache poisoning

이전 공격의 변형으로 Web cache poisoning을 수행하는 것이 가능하다. 프론트엔드 서버에서 콘텐츠를 캐싱하는 경우, off-site redirection 응답이 캐싱되어 이후에 특정 URL을 요청하는 모든 사용자에게 이전의 공격을 수행할 수 있다.

7. Web cache deception

Web cache poisoning이 공격자가 악성 콘텐츠를 캐시에 저장하도록 하여 다른 사용자에게 제공하는 방법이라면, Web cache deception은 다른 사용자의 중요 정보를 캐시에 저장하도록 하여 공격자가 캐시를 통해 중요 정보를 습득하는 방법이다.

  • 예시

아래의 요청은 사용자별 중요 정보를 가져오는 요청을 숨겨 전달한다.

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 43
Transfer-Encoding: chunked

0

GET /private/messages HTTP/1.1
Foo: X

 

/private/message로 보내는 요청은 서버에 남아 다른 사용자가 백엔드 서버로 요청을 보낼 때 아래와 같이 결합된다.

GET /private/messages HTTP/1.1
Foo: XGET /static/some-image.png HTTP/1.1
Host: vulnerable-website.com
Cookie: sessionId=q1jn30m6mqa7nbwsa0bhmbr7ln2vmh7z
...

 

백엔드 서버는 일반적인 방식으로 이 요청에 응답한다. 요청을 보낸 사용자의 비공개 메시지를 응답하고, 프론트엔드 서버에서는 사용자가 보낸 요청의 URL인 /static/some-image.png의 캐시에 아래의 응답을 저장한다. 여기에는 /private/message의 내용이 담겨 있다.

GET /static/some-image.png HTTP/1.1
Host: vulnerable-website.com

HTTP/1.1 200 Ok
...
<h1>Your private messages</h1>
...

 

공격자는 사용자가 요청한 URL에 접속하여 캐시를 통해 중요 데이터를 받아올 수 있다.

이때 공격자는 타겟 콘텐츠가 어떤 URL에 캐시되는지 알 수 없다. 피해자가 어떤 URL을 요청했는지에 따라 캐시되는 위치가 다르기 때문이다. 공격자는 이 콘텐츠를 찾기 위해 많은 시간을 투자해야 할 수도 있다.

 

HTTP request smuggling의 대응 방안

HTTP request smuggling이 발생하는 이유는 프론트엔드 서버와 백엔드 서버에서 요청이 끝나는 위치를 결정하는 과정에서 두 서버가 동일하지 않은 방식을 사용하기 때문에 발생한다. 따라서 이를 막는 절차는 패킷 길이를 지정하는 방식의 불일치를 없애는 형태로 이루어진다.

  • 가능하면 HTTP/2 end-to-end로 사용하고, HTTP downgrade*를 비활성화한다. HTTP/2는 별도의 길이 정의 방식이 존재하기 때문에 기존의 CL, TE와 같은 헤더를 사용하지 않는다. 그렇기 때문에 HTTP/2는 기본적으로 HTTP request smuggling에 면역이다.
  • 프론트엔드 서버에서 모호한 요청을 정규화하고, 백엔드 서버에서 모호한 요청을 받을 경우 요청을 거부하고 TCP 연결을 종료하도록 설정한다.
  • 서버에서 exception이 발생하면 연결을 삭제한다.

(HTTP downgrade: HTTP/2 버전을 사용하는 프론트엔드 서버에서 HTTP/1.1 버전을 사용하는 백엔드 서버로 요청을 전달할 때 버전을 낮추는 과정)