环境,找个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对象。
可以想象,该项技术的实现必然有大量的反射技术。下面我们来分析一下实现过程。
在这里开始,将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);
image.png
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。
现在我们知道为什么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); } }