调试环境搭建

下载官方示例代码:

1
git clone https://github.com/spring-projects/spring-data-examples

使用IDEA导入maven项目,将Projectionpom.xmlspring-data-commons版本修改为1.13 ~ 1.13.10 或 2.0 ~ 2.0.5

运行Application.main(),部署完成

分析

在Spring中,MVC的处理流程可分为以下几步:

1
2
3
4
5
6
7
1. 用户发起HTTP请求,到达DispatcherServlet
2. DispatcherServlet通过HandlerMapping寻找处理器,HandlerMapping会返回一个包含了处理器Handler以及多个HandlerInterceptor拦截器的对象
3. DispatcherServlet通过HandlerAdapter将处理器Handler包装为适配器从而支持多种类型的处理器
4. HandlerAdapter根据适配的结果调用真正的处理器,完成功能处理,并返回一个ModelAndView对象
5. 返回的ModelAndView包含逻辑视图名,ViewResolver根据逻辑视图名解析具体的View
6. View根据传入的Model完成渲染
7. 控制权交还至DispatcherServlet,由DispatcherServlet返回给用户

看一看整体的调用栈,会发现流程和上面写出的处理流程是十分吻合的:

即:

doService->doDispatch->handle->handleInternal->invokeHandlerMethod->invokeAndHandle->invokeForRequest->getMethodArgumentValues->resolveArgument->createAttribute->bind->doBind->applyPropertyValues->setPropertyValue

首先是用户请求到达DispatchServlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
try {
ModelAndView mv = null;
Object dispatchException = null;

try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}

HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
......

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
......

}
}

在doDispatch方法中,

1
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

返回了一个HandlerAdapter,即RequestMappingHandlerAdapter,接下来便需要通过HandlerAdapter调用一个真实的处理器,并返回一个ModelAndView实例,在DispatchServlet的471行附近进行了调用:

1
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

为了更深入的理解handle的处理逻辑,直接跟进handle,结果是:

1
2
3
4
5
6
7
8
public interface HandlerAdapter {
boolean supports(Object var1);

@Nullable
ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

long getLastModified(HttpServletRequest var1, Object var2);
}

真实调用的handle则是:

1
2
3
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return this.handleInternal(request, response, (HandlerMethod)handler);
}

跟入handleInternal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
this.checkRequest(request);
ModelAndView mav;
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized(mutex) {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}

if (!response.containsHeader("Cache-Control")) {
if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
this.prepareResponse(response);
}
}

return mav;
}

handleInternal中,返回了一个ModelAndView的实例mav,而mav则是由this.invokeHandlerMethod赋值。

跟入invokeHandlerMethod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);

ModelAndView var15;
try {
WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}

if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}

......
Object result;
if (asyncManager.hasConcurrentResult()) {
result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Found concurrent result value [" + result + "]");
}

invocableMethod = invocableMethod.wrapConcurrentResult(result);
}

invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
if (asyncManager.isConcurrentHandlingStarted()) {
result = null;
return (ModelAndView)result;
}

var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
} finally {
webRequest.requestCompleted();
}

return var15;
}

考虑到版面原因,将部分无需关注的代码部分做了省略处理。在invokeHandlerMethod中经过一系列的Controller处理,返回一个ModelAndView实例。

跟入invocableMethod.invokeAndHandle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
this.setResponseStatus(webRequest);
if (returnValue == null) {
if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
} else if (StringUtils.hasText(this.getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}

mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");

try {
this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
} catch (Exception var6) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(this.getReturnValueHandlingErrorMessage("Error handling return value", returnValue), var6);
}

throw var6;
}
}

跟入invokeForRequest:

1
2
3
4
5
6
7
8
9
10
11
12
13
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(this.getMethod(), this.getBeanType()) + "' with arguments " + Arrays.toString(args));
}

Object returnValue = this.doInvoke(args);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Method [" + ClassUtils.getQualifiedMethodName(this.getMethod(), this.getBeanType()) + "] returned [" + returnValue + "]");
}

return returnValue;
}

Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);开始获取参数值,跟入getMethodArgumentValues:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
MethodParameter[] parameters = this.getMethodParameters();
Object[] args = new Object[parameters.length];

for(int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = this.resolveProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
} catch (Exception var9) {
if (this.logger.isDebugEnabled()) {
this.logger.debug(this.getArgumentResolutionErrorMessage("Failed to resolve", i), var9);
}

throw var9;
}
} else if (args[i] == null) {
throw new IllegalStateException("Could not resolve method parameter at index " + parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() + ": " + this.getArgumentResolutionErrorMessage("No suitable resolver for", i));
}
}
}

return args;
}

getMethodArgumentValues中递归处理方法参数,进入解析参数的步骤中继续分析,跟入this.argumentResolvers.resolveArgument:

1
2
3
4
5
6
7
8
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
} else {
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
}

在这一段的HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);中,resolver得到的值是ProxyingHandlerMethodArgumentResolver。该resolver是由Spring-Data-Commons提供的Resolver,也是这次RCE产生原因。

继续进入resolver.resolveArgument:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}

Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
} else {
try {
attribute = this.createAttribute(name, parameter, binderFactory, webRequest);
} catch (BindException var10) {
if (this.isBindExceptionRequired(parameter)) {
throw var10;
}

if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}

bindingResult = var10.getBindingResult();
}
}

if (bindingResult == null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
this.bindRequestParameters(binder, webRequest);
}

this.validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}

if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}

bindingResult = binder.getBindingResult();
}

Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}

attribute = this.createAttribute(name, parameter, binderFactory, webRequest);调用了createAttribute,继续跟入createAttribute:

1
2
3
4
5
protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
MapDataBinder binder = new MapDataBinder(parameter.getParameterType(), this.conversionService);
binder.bind(new MutablePropertyValues(request.getParameterMap()));
return this.proxyFactory.createProjection(parameter.getParameterType(), binder.getTarget());
}

binder.bind(new MutablePropertyValues(request.getParameterMap()));完成了bind。跟入bind:

1
2
3
4
public void bind(PropertyValues pvs) {
MutablePropertyValues mpvs = pvs instanceof MutablePropertyValues ? (MutablePropertyValues)pvs : new MutablePropertyValues(pvs);
this.doBind(mpvs);
}

其中,mpvs的组成为:

跟入doBind:

1
2
3
4
5
protected void doBind(MutablePropertyValues mpvs) {
this.checkFieldDefaults(mpvs);
this.checkFieldMarkers(mpvs);
super.doBind(mpvs);
}

再跟入super.doBind():

1
2
3
4
5
protected void doBind(MutablePropertyValues mpvs) {
this.checkAllowedFields(mpvs);
this.checkRequiredFields(mpvs);
this.applyPropertyValues(mpvs);
}

跟入applyPropertyValues:

1
2
3
4
5
6
7
8
9
10
11
12
13
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
this.getPropertyAccessor().setPropertyValues(mpvs, this.isIgnoreUnknownFields(), this.isIgnoreInvalidFields());
} catch (PropertyBatchUpdateException var7) {
PropertyAccessException[] var3 = var7.getPropertyAccessExceptions();
int var4 = var3.length;

for(int var5 = 0; var5 < var4; ++var5) {
PropertyAccessException pae = var3[var5];
this.getBindingErrorProcessor().processPropertyAccessException(pae, this.getInternalBindingResult());
}
}
}

接下来会进行三次函数名相同的调用,为防误解,截图看下调用栈:

跟入第一个setPropertyValues:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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());
Iterator var6 = propertyValues.iterator();

while(var6.hasNext()) {
PropertyValue pv = (PropertyValue)var6.next();

try {
this.setPropertyValue(pv);
} catch (NotWritablePropertyException var9) {
if (!ignoreUnknown) {
throw var9;
}
} catch (NullValueInNestedPathException var10) {
if (!ignoreInvalid) {
throw var10;
}
} catch (PropertyAccessException var11) {
if (propertyAccessExceptions == null) {
propertyAccessExceptions = new LinkedList();
}

propertyAccessExceptions.add(var11);
}
}

if (propertyAccessExceptions != null) {
PropertyAccessException[] paeArray = (PropertyAccessException[])propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]);
throw new PropertyBatchUpdateException(paeArray);
}
}

继续跟入下一个:

1
2
3
public void setPropertyValue(PropertyValue pv) throws BeansException {
this.setPropertyValue(pv.getName(), pv.getValue());
}

跟入最后一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException {
if (!this.isWritableProperty(propertyName)) {
throw new NotWritablePropertyException(this.type, propertyName);
} else {
StandardEvaluationContext context = new StandardEvaluationContext();
context.addPropertyAccessor(new MapDataBinder.MapPropertyAccessor.PropertyTraversingMapAccessor(this.type, this.conversionService));
context.setTypeConverter(new StandardTypeConverter(this.conversionService));
context.setRootObject(this.map);
Expression expression = PARSER.parseExpression(propertyName);
PropertyPath leafProperty = this.getPropertyPath(propertyName).getLeafProperty();
TypeInformation<?> owningType = leafProperty.getOwningType();
TypeInformation<?> propertyType = leafProperty.getTypeInformation();
propertyType = propertyName.endsWith("]") ? propertyType.getActualType() : propertyType;
if (propertyType != null && this.conversionRequired(value, propertyType.getType())) {
PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(owningType.getType(), leafProperty.getSegment());
if (descriptor == null) {
throw new IllegalStateException(String.format("Couldn't find PropertyDescriptor for %s on %s!", leafProperty.getSegment(), owningType.getType()));
}

MethodParameter methodParameter = new MethodParameter(descriptor.getReadMethod(), -1);
TypeDescriptor typeDescriptor = TypeDescriptor.nested(methodParameter, 0);
if (typeDescriptor == null) {
throw new IllegalStateException(String.format("Couldn't obtain type descriptor for method parameter %s!", methodParameter));
}

value = this.conversionService.convert(value, TypeDescriptor.forObject(value), typeDescriptor);
}

expression.setValue(context, value);
}
}

最终在expression.setValue(context, value);这一步完成代码执行。

Reference