简介
环境,找个spring参数绑定的博客就行,这里给个环境例子https://github.com/wycm/SpringMVC-Demo.git。
具体参考SpringMVC参数绑定入门就这一篇 https://segmentfault.com/a/1190000022586808
该漏洞的本质类似于php的变量覆盖漏洞,exp利用的话,恰好覆盖到tomcat的配置,并修改tomcat的日志位置到根目录,修改日志的后缀为jsp。但是这里叫SpringMVC的参数绑定。如图
http://localhost:8080/web_war/ParameterBind/test2?name=aa
如果我们把name这个变量给User这个对象,User对象的代码如下
public class User {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
正常思维是从Http get参数中获取name的值 实例化对象,然后赋值,但是spring框架简化这个过程,所以就叫参数绑定。spring代码如下
@ResponseBody
@RequestMapping("/test2")
public String test2(User u){
System.out.println(u.toString());
return "test2";
}
也就是说spring从http请求中自动解析变量,并给user对象。
可以想象,该项技术的实现必然有大量的反射技术。下面我们来分析一下实现过程。
参数绑定实现过程
org.springframework.beans.AbstractPropertyAccessor#setPropertyValues(org.springframework.beans.PropertyValues, boolean, boolean)
在这里开始,将http请求中每一个kv对,设置到bean对象上,
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
throws BeansException {
List<PropertyAccessException> propertyAccessExceptions = null;
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
for (PropertyValue pv : propertyValues) {
// This method may throw any BeansException, which won't be caught
// here, if there is a critical failure such as no matching field.
// We can attempt to deal only with less serious exceptions.
setPropertyValue(pv);
org.springframework.beans.BeanWrapperImpl#setPropertyValue(org.springframework.beans.PropertyValue)
public void setPropertyValue(PropertyValue pv) throws BeansException {
PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
if (tokens == null) {
String propertyName = pv.getName();
BeanWrapperImpl nestedBw;
try {
nestedBw = getBeanWrapperForPropertyPath(propertyName);
}
catch (NotReadablePropertyException ex) {
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
"Nested property in path '" + propertyName + "' does not exist", ex);
}
tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
if (nestedBw == this) {
pv.getOriginalPropertyValue().resolvedTokens = tokens;
}
nestedBw.setPropertyValue(tokens, pv);
}
else {
setPropertyValue(tokens, pv);
}
}
在getBeanWrapperForPropertyPath中,开始解析http中的key,也就是类似于这类请求
下一个调用上一个的get + 属性名。在这里就是调用class的setModel方法,参数为aa,字符串类型。也就是设置class的Model值为aa。那么问题来了,class是谁?所以对于参数绑定来讲,就是你的那个bean对象的属性。也就是系统默认会有name和age。但是偏偏多了一个class,指向bean对象的类的引用。导致通过这个class引用,修改非bean对象的属性的值。也就造成了变量覆盖。
但是通过参数绑定去修改的对象有限,必须能通过class为起始对象,并且可以通过无参get方法获取到引用,必须有get/set方法。修改的值必须为字符串。
每个bean对象的Propery的cache,在初始化的时候由下面的方法调用生成。
org.springframework.beans.CachedIntrospectionResults#CachedIntrospectionResults
这也就是为什么很多exp在Java8不可以的原因。
当初 Spring 修复了 CVE-2010-1622,修复方式是拦截 Class.getClassLoader的访问,也就是如上图,但是Java9新增了可以通过Class.getModule方法。通过getModule的结果可以调用getClassloader的方式继续访问更多对象的属性。
https://docs.oracle.com/javase/9/docs/api/java/lang/Module.html
org.springframework.beans.BeanWrapperImpl#setPropertyValue(org.springframework.beans.BeanWrapperImpl.PropertyTokenHolder, org.springframework.beans.PropertyValue)
调用set+属性名的方法,设置bean的值
class.module.classLoader.resources.context.parent.pipeline.first.prefix我们可以发现,可以直接从class中获取到tomcat的context,在context中存储很多东西,例如修改日志路径属性等等,修改的值为字符串,完美符合本次漏洞的需求。
但是weblogic的context会不会可以通过class获取到引用很难说。所以影响的局限性不限于tomcat这一种中间件。
自查方案
目前exp具体影响不明,因为一个完美的武器级exp需要满足
-
必须要能通过class为起始对象获取到引用(深度搜索的起点为class),并且还要有无参get方法 -
必须有get/set方法,符合java bean规范。 -
set方法的值必须为字符串 -
该controller必须存在spring的参数绑定。
目前只流传tomcat的exp,不排除其他中间件的exp,不排除dos等其他漏洞的exp。
waf规则
现在我们知道为什么java9可以而java8不可以的原因,所以我们可以断定class.module
这串字符串一定出现在exp的请求中,可以重点防御这串字符串
甲方自查手册
-
确定线上业务中的controller是否使用了spring的参数绑定技术,如果使用则按照下一条继续排查
-
jdk版本是否为jdk9以上,jdk8以下天然防御
因为该漏洞的本质是变量覆盖漏洞,但是利用手法通过覆盖tomcat的配置修改tomcat的日志位置到根目录,修改日志的后缀为jsp去getshell。
-
如果防止getshell,则重点排查中间件是否为tomcat。
-
非tomcat中间件目前来说不一定会被getshell,但是存在被该漏洞影响到线上业务的风险(任意变量覆盖到中间件的其他变量配置,导致dos等其他场景),建议停机修改应用,修复方式如下
注意,目前只有通过应用下线重发布的方式打补丁,并且非spring官方推荐修复,存在一定几率的翻车风险。同时按以下两个步骤进行漏涧的临时修复:
1.在应用中全局搜索@InitBinder注解,看看方法体内是否调用dataBinder.setDisallowedFields方法,如果发现此代码片段的引入,则在原来的黑名单中,添加{“class.“,”Class. “,”. class.“, “.Class.“}。(注:如果此代码片段使用较多,需要每个地方都追加)
在应用系统的项目包下新建以下全局类,并保证这个类被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;
@ControllerAdvice
@Order(10000)
public class a{
@InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
String[] abd = new String[]{"class.*", "Class.*", "*.class.*", "*.Class.*"};
dataBinder.setDisallowedFields(abd);
}
}
原文始发于微信公众号(宽字节安全):Spring 参数绑定的分析以及甲方自查