- JDK9或以上版本系列
- Spring框架或衍生的SpringBoot等框架,版本小于v5.3.18或v5.2.20
- Spring JavaBean表单参数绑定需要满足一定条件
- 部署在Tomcat容器中,且日志记录功能开启(默认状态)
新建SpringBoot工程:
添加实体类`User`:
添加`LoginController`:
生成war包并部署到Tomcat容器中启动。
JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中。比如在`User`类中再添加一个新类`Test`:
构建如上所示的研究环境,当我们发送如下请求时:
Spring会将参数用`.`进行分割,前面的参数会自动调用`get***`,最后一个参数会自动调用`set***`,依次执行为:
...
User->getTest
Test->setT
...
通过上面这种链式的参数解析规则,我们可以`set***`实现修改Spring框架中某些类的属性。通过深入分析,发现当在`Controller`的参数前加上注解之类,比如`@RequestBody`,将不会进行链式解析,这也是限制适用范围的其中一个关键因素。
Spring历史上曾经爆出一个漏洞CVE-2010-1622,原理就是基于上面的JavaBean赋值规则。可以通过提交`class.classLoader`参数,最终执行`getClassLoader`函数获取`ClassLoader`对象,从而可以导致RCE。CVE-2010-1622漏洞补丁位于`CachedIntrospectionResults`:
CVE-2010-1622补丁通过将`classLoader`加入黑名单,导致无法加入解析属性列表。
在JDK9及以上版本的JDK中,`java.lang.Class`类中新增了一个私有变量`module`以及函数`getModule`:
进入`Module`类,发现存在`loader`变量和函数`getClassLoader`:
也就是说,通过`Module`可以获取Web Context上下文环境的`ClassLoader`对象。
通过前面的分析,我们在Tomcat+Spring+JDK11环境中构建研究环境,提交`class.module.classLoader`参数同样可以获取`ClassLoader`对象,调试发现在对BeanInfo赋值过程中,`CachedIntrospectionResults`中会加载`class org.apache.catalina.loader.ParallelWebappClassLoader`对象,可以绕过黑名单检查:
`ParallelWebappClassLoader`位于Tomcat环境中,获取`ParallelWebappClassLoader`对象后,就可以在此基础上继续寻找可利用链。
可以尝试采用递归方式搜索所有满足`class.module.classLoader`条件的链式调用的属性,加入一个`help.jsp`文件(脚本参考只考虑int、string与boolean这些基本属性)。访问后,可以获取所有潜在可操控的属性,结果如下:
一共找到300个符合条件的属性可以操控(实际应该更多)。其中的`class.module.classLoader.resources.context.parent.pipeline.first.*`对应于`AccessLogValve`类,主要存放Tomcat日志操作的相关属性:
...
class.module.classLoader.resources.context.parent.pipeline.first.directory //日志保存目录默认为logs
class.module.classLoader.resources.context.parent.pipeline.first.prefix //日志文件名前缀默认为localhost_access_log
class.module.classLoader.resources.context.parent.pipeline.first.suffix //日志文件名后缀默认为.txt
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat //日志文件名日期格式默认为.yyyy-mm-dd
...
链式调用过程如下:
LoginController
Module
ParallelWebappClassLoader
StandardRoot
StandardContext
StandardEngine
Pipeline
AccessLogValve
...
默认情况下日志目录为`logs`,文件名称为`localhost_access_log.yyy-mm-dd.txt`,可以通过构造请求修改日志存储路径、日志文件名称、后缀名属性值。
通过修改日志后缀名、文件名称、存放位置等属性,可以写入jsp的webshell。需要注意的是,由于Tomcat稍微新一点的版本出于安全性考虑,无法直接在URL中携带`<`、`{`等特殊字符(否则返回400),我们可以通过`class.module.classLoader.resources.context.parent.pipeline.first.pattern`属性来修改日志记录的格式,从而变向写入webshell。
参考`AbstractAccessLogValve`类使用说明:
比如,可以将敏感字符放到HTTP请求头中(格式:`%{xxx}i`)。最终实现在`webapps`目录下写入webshell,名称为`localhost_access_log123.jsp`:
在官方没有发布新版本前,可以通过在WAF中加入对`Class.*`等恶意字符串的过滤,或者在Spring应用程序中新建一个全局类实现对恶意字符串的过滤,同时保证这个类被Spring加载到(推荐在Controller所在的包中添加)。完成类添加后,需对项目进行重新编译打包,并重新发布项目:
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
public class a{
public void setAllowedFields(WebDataBinder dataBinder) {
String[] abd = new String[]{"class.*", "Class.*", "*.class.*", "*.Class.*"};
dataBinder.setDisallowedFields(abd);
}
}
3月31日,官方在`v5.3.18`和`v5.2.20`版本中修复了漏洞,我们看下关键补丁:
相比CVE-2010-1622漏洞补丁,新补丁对类型限制更为严格,只允许所有类中的`name`属性合法通过。
漏洞通过`class.module.classLoader`在Web Context上下文环境中找到合适的类属性进行控制从而实现Getshell,理论上漏洞利用链不局限于Tomcat,类似Weblogic、Jetty等其他的Java中间件是否可以利用不好下结论,可以尝试利用`help.jsp`来寻找潜在的利用点,这方面感兴趣的小伙伴可以关注公众号后与本人深入交流。
由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,且听安全团队及文章作者不为此承担任何责任。
点关注,不迷路!
原文始发于微信公众号(且听安全):【最新漏洞预警】CVE-2022-22965 Spring核心框架Spring4Shell远程命令执行漏洞原理与修复方式分析