개요

톰캣에 존재했던 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링크를 볼 수 있다.

Snyk 상세 페이지

이 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