개요
톰캣에 존재했던 HTTP Request Smuggling 취약점(CVE-2022-42252
)을 분석한다. 취약했던 시점의 소스 코드를 분석하고 그 소스 코드가 어떻게 수정되엇는지를 확인한다.
취약점 개요
- HTTP Request Smuggling중에서 CL.0 패턴이다. 전단의 서버는 CL(Content-Length) 헤더를 보지만, 뒷단의 서버(톰캣)는 CL헤더를 보지 않기 때문에(CL헤더의 값이 0 바이트인 것처럼 행동) 양자간에 어디부터 어디까지가 HTTP요청의 시작과 끝인지 해석에 차이가 발생한다.
취약점 발생 조건
- 톰캣앞에 다른 서버 (nginx나 Apache와 같은 웹서버 혹은 WAF나 로드밸런서 등)가 있어야 한다. 이 서버는 CL헤더를 인식해야 한다.
- 톰캣에서
rejectIllegalHeader
설정이false
로 되어 있어야 한다. 이는 이상한 헤더가 있어도 거절하지 않고 처리한다는 의미다. - 그 결과 톰캣이 이상한 CL헤더를 만나면 이 헤더자체를 지운다. (CL헤더가 없는 것처럼 혹은 CL헤더 값이 0인 것처럼 동작한다.)
- 톰캣 8.5.x 에서는 이 값이 기본으로
false
이다. 따라서 기본적으로 취약하다.
취약한 소스 코드 준비하기
STEP 1. 취약점을 수정한 커밋 ID를 알아내기
먼저 취약점이 수정된 시점의 커밋 ID를 알아내야 한다. 이를 추적하는 방법을 정리해둔다.
방법 1. 구글 검색이나 CVE 관련 링크에서 찾기
구글에서 검색해보면 Snyk에서 제공하는 취약점 상세 페이지가 결과에 표시된다. 여기에 들어가면 Commit링크를 볼 수 있다.
이 Commit링크는 Tomcat 버전 11 수정용 커밋링크였다.
Tomcat 버전 11 수정용 커밋링크: https://github.com/apache/tomcat/commit/c9fe754e5d17e262dfbd3eab2a03ca96ff372dc3
방법 2. git 커밋 메세지에서 찾기
커밋메시지로 찾는 방법이다. 개발자가 친절하다면 커밋 메세지에 CVE번호를 적었을 수도 있다. CVE번호가 아니라면 특정 키워드로 찾는다.
커밋메세지에서 찾는 커맨드는 다음과 같다. 대소문자를 구별하므로 적절히 변경하가면서 찾는다.
git log --grep=키워드
git log --grep=CVE-2022-42252
git log --grep=Content-Length
git log --grep=content-length
키워드가 일치하는 것이 있으면 결과에 표시된다. 시간상으로 새로운 것이 위에 표시된다. CVE DB를 보면 취약점이 발표된 날을 볼 수 있다. 대략 이 날짜와 일치하는 부분의 코드를 찾는다. 예를들어 이번 분석 타겟인 CVE-2022-42252
는 2022년 11월 1일에 NVD에 등록된 것으로 나온다.
content-length
키워드로 검색하면 2022년 11월에 가장 가까운 commit은 a1c07906d8dcaf7957e5cc97f5cdbac7d18a205a
다. 커밋메세지도 Requests with invalid content-length should always be rejected
인 것을 보면 내용도 맞는 것 같다.
git log --grep=content-length
commit 5dbfefa88293078d578c72e3ff17c4da5389110e
Author: remm <remm@apache.org>
Date: Wed Oct 11 09:41:14 2023 +0200
Revert compression refactoring
BZ 67670
Add test case to verify content-length is not present when using the
connector compression (also that checks DefaultServlet is working with
it).
commit a1c07906d8dcaf7957e5cc97f5cdbac7d18a205a
Author: Mark Thomas <markt@apache.org>
Date: Mon Oct 3 11:59:01 2022 +0100
Requests with invalid content-length should always be rejected
commit 4d08ac6b5f490223adc4ea37930116852cc9fadd
Author: Mark Thomas <markt@apache.org>
Date: Wed Jun 16 11:45:42 2021 +0100
Fix test broken by response buffer fixes
The 8192 byte response body fills the response buffer but does not (now)
trigger a commit so Tomcat is able to set the content-length header in
post-processing.
...
https://github.com/apache/tomcat/commit/a1c07906d8dcaf7957e5cc97f5cdbac7d18a205a
에 접근하면 소스코드를 볼 수 있다!
STEP 2. 수정직전의 소스코드 커밋 ID 확인
git log 커밋ID
커맨드를 사용하면 이 커밋직전의 커밋들을 볼 수 있다.
$git log a1c07906d8dcaf7957e5cc97f5cdbac7d18a205a
commit a1c07906d8dcaf7957e5cc97f5cdbac7d18a205a
Author: Mark Thomas <markt@apache.org>
Date: Mon Oct 3 11:59:01 2022 +0100
Requests with invalid content-length should always be rejected
commit 75d11526eff3e590490288b89d17fe1903e7da9a
Author: Mark Thomas <markt@apache.org>
Date: Fri Sep 30 20:19:42 2022 +0100
Update RewriteValve to use dotall mode
commit b01c241e56cec904d3cd9fa00c9a2bc0b6831bbc
Author: Mark Thomas <markt@apache.org>
Date: Fri Sep 30 18:17:14 2022 +0100
BZ 66281 - unexpected timeouts when using HTTP/2 and NIO2
Timeouts may appear as client disconnections
https://bz.apache.org/bugzilla/show_bug.cgi?id=66281
커밋ID a1c07906d8dcaf7957e5cc97f5cdbac7d18a205a
의 직전 커밋은 75d11526eff3e590490288b89d17fe1903e7da9a
인 것을 알았다.
STEP 3. 톰캣의 소스코드를 취약점 수정 직전의 소스코드로 돌리기
특정 commit 시점으로 소스코드를 되돌리는 방법은 여기에서 확인할 수 있다.
git reset --hard <old-commit-id>
git push -f <remote-name> <branch-name>
다음 커맨드를 사용했다. 취약한 시점으로 돌려야 하므로 수정한 커밋 ID a1c07906d8dcaf7957e5cc97f5cdbac7d18a205a
의 직전의 커밋ID인 75d11526eff3e590490288b89d17fe1903e7da9a
지정한다.
git reset --hard 75d11526eff3e590490288b89d17fe1903e7da9a
참고. 현재 커밋 위치를 확인하는 방법
다음 커맨드를 사용한다.
> git rev-parse HEAD
75d11526eff3e590490288b89d17fe1903e7da9a
소스코드 분석
TestHttp11InputBuffer 클래스안에 있는 parseHeader
함수(852라인~1043라인)가 메인이다. 이 함수에는 HTTP요청헤더를 어떻게 처리할 것인지에 대한 코드가 적혀있다.
HTTP요청 처리는 Http11Processor
클래스의 service 함수에서 처리된다. 이 함수에서 Http11InputBuffer
클래스의 parseHeaders함수를 호출한다. 이 함수는 모든 헤더의 파싱이 성공하면 true를, 실패하면 false를 리턴한다. parseHeaders함수 내에서 헤더를 순차적으로 읽는다. 실제 헤더를 읽고 처리가능한지는 parseHeader함수에서 담당한다.
참고
- 취약점 정보: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-42252
- 취약점 POC: https://insbug.medium.com/apache-tomcat-request-smuggling-vulnerability-cve-2022-42252-836cb4bcb3d
- 톰캣 설정: https://tomcat.apache.org/tomcat-8.5-doc/config/http.html
- 취약점 수정 코드: https://github.com/apache/tomcat/commit/a1c07906d8dcaf7957e5cc97f5cdbac7d18a205a