CVE-2018-1273:Spring Data Commons 解析器选择不当导致的RCE
调试环境搭建
下载官方示例代码:
1 git clone https://github.com/spring-projects/spring-data-examples
使用IDEA导入maven项目,将Projection
中pom.xml
的spring-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