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 的修改内容一直到查找到漏洞点,及利用的整个过程。学习了很多。