Spring 参数绑定的分析以及甲方自查

2022-04-07 6,222

简介

环境,找个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);



image.png

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需要满足

  1. 必须要能通过class为起始对象获取到引用(深度搜索的起点为class),并且还要有无参get方法
  2. 必须有get/set方法,符合java bean规范。
  3. set方法的值必须为字符串
  4. 该controller必须存在spring的参数绑定。

目前只流传tomcat的exp,不排除其他中间件的exp,不排除dos等其他漏洞的exp。

waf规则

现在我们知道为什么java9可以而java8不可以的原因,所以我们可以断定class.module这串字符串一定出现在exp的请求中,可以重点防御这串字符串

甲方自查手册

  1. 确定线上业务中的controller是否使用了spring的参数绑定技术,如果使用则按照下一条继续排查

  2. jdk版本是否为jdk9以上,jdk8以下天然防御

因为该漏洞的本质是变量覆盖漏洞,但是利用手法通过覆盖tomcat的配置修改tomcat的日志位置到根目录,修改日志的后缀为jsp去getshell。

  1. 如果防止getshell,则重点排查中间件是否为tomcat。

  2. 非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);
}
}


本文作者:宽字节安全

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/176519.html

Tags:
评论  (0)
快来写下你的想法吧!

宽字节安全

文章数:26 积分: 75

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号