0x00 漏洞介绍

在Spring Web Flow中,一定条件下,可以造成RCE。

0x01 漏洞分析

5月31日,pivotal.io 爆出 Spring Web Flow 的 RCE 漏洞。从Spring官网中看出来,这个 RCE 评级较低,为 Minor。

我们先从 github 的 commit 来看

Spring 官方的修复,修改了执行表达式的函数。

### 调用关系

我们知道,漏洞点为 addEmptyValueMapping 函数中的 Expression target = expressionParser.parseExpression(field, parserContext);所以我们看看是谁调用了 addEmptyValueMapping 。

从上图可以看出,分别是两个函数调用了 addEmptyValueMapping 。

  • addDefaultMappings 是从请求中遍历值,请求中的值可以被攻击者控制。
  • addModelBindings 是从 binding 中遍历值传入 addEmptyValueMapping ,经过分析,攻击者不可控。

addDefaultMappings

protected void addDefaultMappings(DefaultMapper mapper, Set<String> parameterNames, Object model) {
    for (String parameterName : parameterNames) {
        if (fieldMarkerPrefix != null && parameterName.startsWith(fieldMarkerPrefix)) {
            String field = parameterName.substring(fieldMarkerPrefix.length());
            if (!parameterNames.contains(field)) {
                addEmptyValueMapping(mapper, field, model);
            }
        } else {
            addDefaultMapping(mapper, parameterName, model);
        }
    }
}

addModelBindings

protected void addModelBindings(DefaultMapper mapper, Set<String> parameterNames, Object model) {
    for (Binding binding : binderConfiguration.getBindings()) {
        String parameterName = binding.getProperty();
        if (parameterNames.contains(parameterName)) {
            addMapping(mapper, binding, model);
        } else {
            if (fieldMarkerPrefix != null && parameterNames.contains(fieldMarkerPrefix + parameterName)) {
                addEmptyValueMapping(mapper, parameterName, model);
            }
        }
    }
}

接下来,我们看下 addDefaultMappings 的调用关系,addDefaultMappings 被 bind 调用,并且在 binderConfiguration 为空时,才能进入漏洞点。 binderConfiguration 这个值是由配置文件中是否有 binder 节点来控制的(这里需要注意的是程序执行到 bind 方法的前置条件是 view-state 节点中是否配置了 model 属性,即绑定的 javabean 对象是什么)。

protected MappingResults bind(Object model) {
    if (logger.isDebugEnabled()) {
        logger.debug("Binding to model");
    }
    DefaultMapper mapper = new DefaultMapper();
    ParameterMap requestParameters = requestContext.getRequestParameters();
    if (binderConfiguration != null) {
        addModelBindings(mapper, requestParameters.asMap().keySet(), model);
    } else {
        addDefaultMappings(mapper, requestParameters.asMap().keySet(), model);
    }
    return mapper.map(requestParameters, model);
}

调用 bind 的函数是

public void processUserEvent() {
    String eventId = getEventId();
    if (eventId == null) {
        return;
    }
    if (logger.isDebugEnabled()) {
        logger.debug("Processing user event '" + eventId + "'");
    }
    Object model = getModelObject();
    if (model != null) {
        if (logger.isDebugEnabled()) {
            logger.debug("Resolved model " + model);
        }
        TransitionDefinition transition = requestContext.getMatchingTransition(eventId);
        if (shouldBind(model, transition)) {
            mappingResults = bind(model);
            if (hasErrors(mappingResults)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Model binding resulted in errors; adding error messages to context");
                }
                addErrorMessages(mappingResults);
            }
            if (shouldValidate(model, transition)) {
                validate(model, transition);
            }
        }
    } else {
        if (logger.isDebugEnabled()) {
            logger.debug("No model to bind to; done processing user event");
        }
    }
    userEventProcessed = true;
}

整个流程如随风师傅所说,Spring Web Flow 的执行顺序和流程,由 flowcontroller 决定将请求交给那个 handler 去执行具体的流程,这里我们需要知道当用户请求有视图状态处理时,会决定当前事件下一个执行的流程,同时对于配置文件中我们配置的 view-state 元素,如果我们指定了数据的 model ,那么它会自动进行数据绑定。

接下来,我也在 Spring Work Flow 的样例中尝试能否rce。结果是不能,这下就很意外了,发现了一个很有意思的地方。

addEmptyValueMapping 中,表达式执行的地方,是个接口,存在三个定义。

分别是

  • org.springframework.binding.expression.spel.SpringELExpressionParser.parseExpression(String expression, ParserContext context) throws ParserException
  • org.springframework.binding.expression.support.AbstractExpressionParser.parseExpression(String expressionString, ParserContext context) throws ParserException
  • org.springframework.binding.expression.el.ELExpressionParser.parseExpression(String expressionString, ParserContext context) throws ParserException

在默认流程中,遍历流入了下图的函数。

此函数中,数据流入了 doParseExpression(expressionString, context); 如下图。此函数不能动态解析我们的命令。

根据随风师傅的提示,修改 factoryCreator.setUseSpringBeanBinding(false) 。

威胁参数流入了如下函数。

成功代码执行。

0x02 调试总结

本次调试,调试过程主要是对 Spring 框架的上手的练习。从 git 的修改内容一直到查找到漏洞点,及利用的整个过程。学习了很多。

0x03 参考资料

Spring WebFlow 远程代码执行漏洞分析(CVE-2017-4971)