首页 > 其他分享 >Thymeleaf SSTI模板注入分析

Thymeleaf SSTI模板注入分析

时间:2024-04-12 18:56:06浏览次数:29  
标签:ni return Thymeleaf SSTI 模板 && import 表达式

环境搭建

先搭建一个SpringMVC项目,参考这篇文章,或者参考我以前的spring内存马分析那篇文章
https://blog.csdn.net/weixin_65287123/article/details/136648903

SpringMVC路由

简单写个servlet

package com.example.controller;

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.ResponseBody;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.UnsupportedEncodingException;
import java.util.Base64;

@Controller
public class TestController {
    @GetMapping("/")
    public String Welcome(String type) throws UnsupportedEncodingException {
        System.out.println(type);
        if(!type.equals("")) {
            return "hello";
        }
        return "index";
    }
    @ResponseBody
    @RequestMapping("/readobject")
    public String frontdoor(String payload) throws IOException, ClassNotFoundException {
        byte[] base64decodedBytes = Base64.getDecoder().decode(payload);
        ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();
        return "right";
    }
}

这样就是访问到index.jsp

路由解析流程主要就是ModelView以及最后Render。return处打个断点,看怎么处理的
先进入invokeAndHandle,调用invokeForRequest方法,这个操作会获取到我们传进去的视图名称

往下走,这个mavContainer就是一个ModelAndViewContainer容器对象

进入handleReturnValue方法

跟进另一个handleReturnValue
略过dispatch的调试环节,直接定位到render处

往下走进入ThymeleafView的render方法,然后走到这

这个方法是重点,后面会说到,退出这个方法后,流程就结束了

Thymeleaf模板注入成因

其实就是上面的renderFragment函数。这里直接讲3.0.12版本后的方法,因为3.0.12后加了一层check,需要绕过,之前版本的就是直接SPEL表达式就可以RCE,__${T%20(java.lang.Runtime).getRuntime().exec(%22calc%22)}__::.x
poc如上,接下来我们将一步步解释为什么poc是上述形式,先改一下controller

 @GetMapping("/")
    public String Welcome(String type) throws UnsupportedEncodingException {
        System.out.println(type);
        if(!type.equals("")) {
            return "hello/"+type+"/challenge";
        }
        return "index";
    }

type传入我们的payload,renderFragment方法里获取我们的payload

往下走,这里会判断viewTemplateName是否包含::
这里需要介绍一个东西

Thymeleaf 是与 java 配合使用的一款服务端模板引擎,也是 Spring 官方支持的一款服务端模板引擎。而 SSTI 最初是由 [James Kettle](https://portswigger.net/research/server-side-template-injection) 提出研究,[Emilio Pinna](https://github.com/epinna/tplmap) 对他的研究进行了补充,不过这些作者都没有对 Thymeleaf 进行 SSTI 相关的漏洞研究工作,后来 Aleksei Tiurin 在 ACUNETIX 的官方博客上发表了关于 Thymeleaf SSTI 的[文章](https://www.acunetix.com/blog/web-security-zone/exploiting-ssti-in-thymeleaf/),因此 Thymeleaf SSTI 逐渐被安全研究者关注。
为了更方便读者理解这个 Bypass,因此在这里简单说一遍一些基础性的内容,如果了解的,可以直接跳到 0x03 的内容。
Thymeleaf 表达式可以有以下类型:

- ${...}:变量表达式 —— 通常在实际应用,一般是OGNL表达式或者是 Spring EL,如果集成了Spring的话,可以在上下文变量(context variables )中执行
- *{...}: 选择表达式 —— 类似于变量表达式,区别在于选择表达式是在当前选择的对象而不是整个上下文变量映射上执行。
- #{...}: Message (i18n) 表达式 —— 允许从外部源(比如.properties文件)检索特定于语言环境的消息
- @{...}: 链接 (URL) 表达式 —— 一般用在应用程序中设置正确的 URL/路径(URL重写)。
- ~{...}:片段表达式 —— Thymeleaf 3.x 版本新增的内容,分段段表达式是一种表示标记片段并将其移动到模板周围的简单方法。 正是由于这些表达式,片段可以被复制,或者作为参数传递给其他模板等等

实际上,Thymeleaf 出现 SSTI 问题的主要原因也正是因为这个片段表达式,我们知道片段表达式语法如下:

1. ~{templatename::selector},会在/WEB-INF/templates/目录下寻找名为templatename的模版中定义的fragment

重点是片段表达式。假如有一个html代码

<!DOCTYPE html> 
<html xmlns:th="http://www.thymeleaf.org"> 
<body> <div th:fragment="banquan"> &copy; 2021 ThreeDream yyds</div> 
</body> 
</html>

我们需要在另一个template模板文件引用上述的fragment

<div th:insert="~{footer :: banquan}"></div>

这就是片段表达式,片段表达式后面必须要有一个名字,这也对应payload中的.x,这个.x就是名称,那个.也可以去掉改为任意的字符串.
继续往下走,fragmentExpression处进行了一个拼接,刚好是片段表达式形式的拼接

跟进parser.parseExpression这个方法
继续跟进,这里进入preprocess函数
注意上方的Pattern,Pattern.compile("\\_\\_(.*?)\\_\\_", 32);
这个刚好就能识别payload的形式,然后由于是片段表达式,所以有最后的.x

往下走进入execute方法解析匹配到的payload,解析过程就不说了,就是正常的SPEL表达式解析,说一下3.12版本后的一个checker

  public static boolean containsSpELInstantiationOrStatic(final String expression) {

        /*
         * Checks whether the expression contains instantiation of objects ("new SomeClass") or makes use of
         * static methods ("T(SomeClass)") as both are forbidden in certain contexts in restricted mode.
         */

        final int explen = expression.length();
        int n = explen;
        int ni = 0; // index for computing position in the NEW_ARRAY
        int si = -1;
        char c;
        while (n-- != 0) {

            c = expression.charAt(n);

            // When checking for the "new" keyword, we need to identify that it is not a part of a larger
            // identifier, i.e. there is whitespace after it and no character that might be a part of an
            // identifier before it.
            if (ni < NEW_LEN
                    && c == NEW_ARRAY[ni]
                    && (ni > 0 || ((n + 1 < explen) && Character.isWhitespace(expression.charAt(n + 1))))) {
                ni++;
                if (ni == NEW_LEN && (n == 0 || !Character.isJavaIdentifierPart(expression.charAt(n - 1)))) {
                    return true; // we found an object instantiation
                }
                continue;
            }

            if (ni > 0) {
                // We 'restart' the matching counter just in case we had a partial match
                n += ni;
                ni = 0;
                if (si < n) {
                    // This has to be restarted too
                    si = -1;
                }
                continue;
            }

            ni = 0;

            if (c == ')') {
                si = n;
            } else if (si > n && c == '('
                        && ((n - 1 >= 0) && (expression.charAt(n - 1) == 'T'))
                        && ((n - 1 == 0) || !Character.isJavaIdentifierPart(expression.charAt(n - 2)))) {
                return true;
            } else if (si > n && !(Character.isJavaIdentifierPart(c) || c == '.')) {
                si = -1;
            }

        }

        return false;

    }

进入这个方法,他会识别new关键字,不允许存在new关键字,并且不允许存在T(.*)这种形式的字符串,因此就得bypass了,而方法也很简单,fuzz一下就知道是T ()加一个空格就行了。后续的一系列利用都是针对sepl表达式的研究了

标签:ni,return,Thymeleaf,SSTI,模板,&&,import,表达式
From: https://www.cnblogs.com/F12-blog/p/18131128

相关文章

  • 模板函数使用类型推导时的bug
    templatestaticboolparse_a_value(T&val,Json::Valuejson_val){if(json_val.isNull())returnfalse;if(typeid(val)==typeid(int)||typeid(val)==typeid(int16_t)||typeid(val)==typeid(int8_t)||typeid(val)==typeid(int32_t)){......
  • SOLIDWORKS模板批量修改工具 慧德敏学
    SOLIDWORKS批量修改模板插件-SolidKits.BOMs工具可实现工程图模板的批量替换,单位系统的批量修改,批量定义模型材质等功能。操作简单快捷,只需要提前打开SOLIDWORKS软件,执行后程序会自动完成所有替换操作。使用SOLIDWORKS绘制工程图之前,必须要选择工程图模板,模板中我们会定义好图幅......
  • 软件开发文档模板全套合集(开发+实施+运维+安全+交付)
    前言:在软件项目管理中,每个阶段都有其特定的目标和活动,确保项目的顺利进行和最终的成功交付。以下是软件项目管理各个阶段的详细资料:软件项目全套文档资料下载:点我获取1.需求阶段目标:收集、分析和定义用户需求和业务目标。主要活动:需求调研:与用户沟通,了解他们的需求和期......
  • 实用算法模板——滑动窗口
    为了更好的说明这个问题,我们借用acWing上的一道题目模拟样例:解法一:使用stl中的双端队列求解解法二:使用数组模拟队列,运行速度更快如果还有疑问,可参考:C15【模板】单调队列滑动窗口最值_哔哩哔哩_bilibili希望对你有所帮助,感谢查看!......
  • 算法模板 v1.12.2.20240411
    算法模板v1.1.1.20240115:之前历史版本已不可寻,创建第一份算法模板。v1.2.1.20240116:删除“编译”-“手动开栈”;删除“编译”-“手动开O优化”;修改“编译”-“CF模板”;删除“读写”;删除“图论”-“欧拉图”-“混合图”;删除“图论”-“可达性统计”;删除“数据类型”-“高精类”。......
  • C++——模板初阶
    目录0.前言1.泛型编程2.函数模板2.1函数模板概念2.1函数模板格式2.3函数模板的原理2.4函数模板的实例化2.5模板参数的匹配原则3.类模板3.1类模板的定义格式3.2类模板的实例化0.前言C++模板是一种泛型编程的工具,允许开发者定义对多种数据类型都适用的代......
  • 【模板】任意模数多项式乘法:三模 NTT
    前置知识https://www.cnblogs.com/caijianhong/p/template-crt.htmlhttps://www.cnblogs.com/caijianhong/p/template-fft.html题目描述任意模数多项式乘法solution首先我们打开https://blog.miskcoo.com/2014/07/fft-prime-table这篇文章找到\(998244353\)附近的几个质......
  • 蓝桥杯单片机基于西风模板超声波底层
    超声波是外设需要重新自己编写c文件和h文件在c文件中需要编写两个函数一个是波的初始化一个是方波的读取voidWave_Init(){unsignedchari;for(i=0;i<8;i++){TX=1;发送信号Delay(12)us哦Tx=0在延时12us}这样波的初始化就好了}unsignedcharWave_Read(){unsig......
  • template—模板初阶(C++)
        本篇将会对Cpp中的模板进行一个简单的介绍(后序还关系模板进阶,对模板的内容进行更深入的讲解),其中包括模板的使用:函数模板、类模板,以及对于泛型编程的理解。其中的重点为函数模板,介绍了函数模板的原理、隐式实例化和显示实例化、还有模板参数的匹配规则。目录如下......
  • Dotnet8.0常用工程模板
     --dotnetnew--installMicrosoft.Azure.WebJobs.ProjectTemplates安装最新版本dotnetnewinstallMicrosoft.DotNet.Web.Spa.ProjectTemplates安装指定版本dotnetnewinstallMicrosoft.DotNet.Web.Spa.ProjectTemplates::2.0.0安装制定版本且制定数据源dotnetnew......