JAVA安全-模板注入-FreeMarker

admin 2024年12月28日22:03:45评论15 views字数 9249阅读30分49秒阅读模式
JAVA安全-模板注入-FreeMarker
JAVA安全-模板注入-FreeMarker

点击上方蓝字·关注我们

免责声明
JAVA安全-模板注入-FreeMarker

由于传播、利用本公众号菜狗安全所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号菜狗安全及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,会立即删除并致歉。

文章目录
JAVA安全-模板注入-FreeMarker
介绍环境搭建漏洞复现深入分析危险内置类Execute    ObjectConstructor    JythonRuntime漏洞修复以及高版本特性最后
介绍

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

模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言,不是像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。

JAVA安全-模板注入-FreeMarker

这种方式通常被称为MVC (模型 视图 控制器) 模式,对于动态网页来说,是一种特别流行的模式。 它帮助从开发人员(Java 程序员)中分离出网页设计师(HTML设计师)。设计师无需面对模板中的复杂逻辑, 在没有程序员来修改或重新编译代码时,也可以修改页面的样式。

而FreeMarker最初的设计,是被用来在MVC模式的Web开发框架中生成HTML页面的,它没有被绑定到 Servlet或HTML或任意Web相关的东西上。它也可以用于非Web应用环境中。

环境搭建

创建一个javaweb项目

JAVA安全-模板注入-FreeMarker

项目加载完成后,在pom.xml文件中添加FreeMarker依赖

<dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.31</version></dependency>

然后编写一个.ftl模板文件

<html><head><metacharset="utf-8"><title>这是一个Freemarker测试页面</title></head><body><#--我只是一个注释,我不会有任何输出 -->${name} 你好</body></html>
JAVA安全-模板注入-FreeMarker

然后开始编写调用FreeMarker通过模板文件动态生成我们的页面

publicclassTestDemo {public static void main(String[] args) throws IOExceptionTemplateException {Configuration config = new Configuration(Configuration.getVersion());        //创建一个Configuration对象,并传入当前FreeMarker版本号        config.setDirectoryForTemplateLoading(new File("D:/JAVA开发/FreeMarkerDemo/src/main/webapp"));        //设置FreeMarker模板文件的加载目录        config.setDefaultEncoding("UTF-8");        //设置FreeMarker模板文件的默认编码为UTF-8        Template template = config.getTemplate("test.ftl");        //从配置中加载名为test.ftl的模板文件        HashMap Map = new HashMap();        //创建一个HashMap对象,用于存储传递给模板的数据        Map.put("name","菜狗");        //向HashMap中添加一个键值对,键为"name",值为"菜狗"        FileWriter fileWriter = new FileWriter(new File("D:/JAVA开发/FreeMarkerDemo/src/main/webapp/test.html"));        //创建一个FileWriter对象,用于将生成的HTML内容写入到对应文件中        template.process(Map,fileWriter);        //使用模板和数据生成HTML内容,并将其写入到FileWriter对象中        fileWriter.close();        //关闭FileWriter对象,确保所有内容都被正确写入文件并释放资源    }}

运行看下效果

JAVA安全-模板注入-FreeMarker

可以看到多了个html文件

漏洞复现

我们尝试在test.ftl文件中插入我们的poc,然后再次生成文件

poc:<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}
JAVA安全-模板注入-FreeMarker

可以看到计算器弹出,漏洞存在

深入分析

然后我们来分析漏洞成因,在分析前我们先了解下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>

看完后,我们再把我们的poc拿出来分析下

<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}

1、可以看到这个poc是FTL指令,用的是<#assign标签,使用该指令你可以创建一个新的变量, 或者替换一个已经存在的变量

2、value的值对应的是"freemarker.template.utility.Execute"这个类是freemarker内置的一个工具类,用于执行系统命令

3、?new() 是FreeMarker中的操作符,用于实例化一个对象

4、${value("calc.exe")} 是FreeMarker中的插值表达式,用于输出变量的值

我们看下freemarker.template.utility.Execute.class

JAVA安全-模板注入-FreeMarker

在执行处和参数下断点,调用堆栈如下,我们一步步跟下流程

exec:84, 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:347, Environment (freemarker.core)visit:353, Environment (freemarker.core)process:326, Environment (freemarker.core)process:383, Template (freemarker.template)main:28, TestDemo (org.example.freemarkerdemo)

从template.process()方法开始

JAVA安全-模板注入-FreeMarker

进入process,执行createProcessingEnvironment(),process()方法

JAVA安全-模板注入-FreeMarker

我们继续跟进

JAVA安全-模板注入-FreeMarker

前面部分是获取当前线程中的环境变量然后设置,关键点在visit方法,它是用于访问模板的根节点并进行处理的,getTemplate()获取当前的模板对象,getRootTreeNode()从模板对象中获取根节点,我们跟进

JAVA安全-模板注入-FreeMarker

element就是我们获取到的模板内容,然后通过.accept变量模板文件里面的内容,传入templateElementsToVisit

接着进入for循环,把templateElementsToVisit中的模板内容一条条取出,然后进入二次调用visit(),这里跳过前面的看下关于我们传入POC的处理流程

JAVA安全-模板注入-FreeMarker

把我们的POC赋值给栈数组instructionStack的最后一个位置

JAVA安全-模板注入-FreeMarker

接着触发accept,这里条件判断对走第三条,触发env.getCurrentNamespace()

JAVA安全-模板注入-FreeMarker

然后接着往下走,来到一个if语句,看样子是判断语句类型,接着触发valueExp.eval,env是我们的模板对象

JAVA安全-模板注入-FreeMarker

来到eval方法,这里constantValue = null,符合条件,执行_eval(env),继续跟

JAVA安全-模板注入-FreeMarker

这里会执行一条Object result = targetMethod.exec(argumentStrings);我们可以看到 targetMethod 目前就是我们在 ftl 语句当中构造的那个能够进行命令执行的类

也就是说这里的语句执行等同于Object result = freemarker.template.utility.Execute.exec(argumentStrings);

JAVA安全-模板注入-FreeMarker

接着来到exec,这里通过会newInstance() 的方式进行初始化,命令执行的参数,会被拿出来,在下一次的同样流程中作为命令被执行

JAVA安全-模板注入-FreeMarker
JAVA安全-模板注入-FreeMarker

到这里触发流程就跟完了,那么FreeMarker的注入问题的成因是ftl中存在某些具有高风险操作的elements tag,这些elements tag的解析类通过继承实现了对应的eval接口,并且在实现类中引入了高风险的操作

危险内置类

Execute

我们上面的poc中使用到的是freemarker.template.utility.Execute这个内置类的exec方法,那么还有哪些freemarker的内置类的exec方法能够造成高危行为呢?

ObjectConstructor

我们看下这个类的exec方法

JAVA安全-模板注入-FreeMarker

反射调用我们传入的对象,我们尝试构造poc

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

通过它这里的反射调用JDK自带的命令执行类ProcessBuilder,执行calc

JAVA安全-模板注入-FreeMarker

JythonRuntime

这个类我没见过,查资料后,说是可以使得 Python 脚本可以在 Java 虚拟机上运行

网上关于这个类的poc如下

<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")</@value>
JAVA安全-模板注入-FreeMarker

运行后报错,计算器未弹出,查资料发现要执行这个类环境要有jython-standalone依赖,所以不是很推荐使用,不如前两个

在pom.xml中添加对应依赖

<dependency><groupId>org.python</groupId><artifactId>jython-standalone</artifactId><version>2.7.0</version></dependency>
JAVA安全-模板注入-FreeMarker
JAVA安全-模板注入-FreeMarker

修复方式以及高版本特性

从漏洞复现和深入分析我们了解实际上照成安全问题的原因是调用了能够照成危害的freemarker内置类,那么我们只要把有危害的内置类禁用掉不就行了,官方也是这样想的

后续freemarker更新了个.setNewBuiltinClassResolver()方法来限制内建函数对类的访问

该配置有以下三种参数

  • UNRESTRICTED_RESOLVER:可以通过ClassUtil.forName(String)获得任何类
  • SAFER_RESOLVER:禁止加载ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime这三个类
  • ALLOWS_NOTHING_RESOLVER:禁止解析任何类

我们可以尝试配置下

JAVA安全-模板注入-FreeMarker

再尝试运行poc

JAVA安全-模板注入-FreeMarker

可以看到执行不了,这个是要手动设置的,非默认所以高版本系统也有可能出现安全问题

除了类禁用还有一个问题,在freemark的2.3.22版本之后api_builtin_enabled默认为false,同时FreeMarker为了防御通过其他方式调用恶意方法,在FreeMarker中内置了一份危险方法名单unsafeMethods.properties,例如:getClassLoader、newInstance等危险方法都被禁用了

最新版本2.3.33禁用的api如下

java.lang.Object.wait()java.lang.Object.wait(long)java.lang.Object.wait(long,int)java.lang.Object.notify()java.lang.Object.notifyAll()java.lang.Class.getClassLoader()java.lang.Class.newInstance()java.lang.Class.forName(java.lang.String)java.lang.Class.forName(java.lang.String,boolean,java.lang.ClassLoader)java.lang.reflect.Constructor.newInstance([Ljava.lang.Object;)java.lang.reflect.Method.invoke(java.lang.Object,[Ljava.lang.Object;)java.lang.reflect.Field.set(java.lang.Object,java.lang.Object)java.lang.reflect.Field.setBoolean(java.lang.Object,boolean)java.lang.reflect.Field.setByte(java.lang.Object,byte)java.lang.reflect.Field.setChar(java.lang.Object,char)java.lang.reflect.Field.setDouble(java.lang.Object,double)java.lang.reflect.Field.setFloat(java.lang.Object,float)java.lang.reflect.Field.setInt(java.lang.Object,int)java.lang.reflect.Field.setLong(java.lang.Object,long)java.lang.reflect.Field.setShort(java.lang.Object,short)java.lang.reflect.AccessibleObject.setAccessible([Ljava.lang.reflect.AccessibleObject;,boolean)java.lang.reflect.AccessibleObject.setAccessible(boolean)java.lang.Thread.destroy()java.lang.Thread.getContextClassLoader()java.lang.Thread.interrupt()java.lang.Thread.join()java.lang.Thread.join(long)java.lang.Thread.join(long,int)java.lang.Thread.resume()java.lang.Thread.run()java.lang.Thread.setContextClassLoader(java.lang.ClassLoader)java.lang.Thread.setDaemon(boolean)java.lang.Thread.setName(java.lang.String)java.lang.Thread.setPriority(int)java.lang.Thread.sleep(long)java.lang.Thread.sleep(long,int)java.lang.Thread.start()java.lang.Thread.stop()java.lang.Thread.stop(java.lang.Throwable)java.lang.Thread.suspend()java.lang.ThreadGroup.allowThreadSuspension(boolean)java.lang.ThreadGroup.destroy()java.lang.ThreadGroup.interrupt()java.lang.ThreadGroup.resume()java.lang.ThreadGroup.setDaemon(boolean)java.lang.ThreadGroup.setMaxPriority(int)java.lang.ThreadGroup.stop()java.lang.ThreadGroup.suspend()java.lang.Runtime.addShutdownHook(java.lang.Thread)java.lang.Runtime.exec(java.lang.String)java.lang.Runtime.exec([Ljava.lang.String;)java.lang.Runtime.exec([Ljava.lang.String;,[Ljava.lang.String;)java.lang.Runtime.exec([Ljava.lang.String;,[Ljava.lang.String;,java.io.File)java.lang.Runtime.exec(java.lang.String,[Ljava.lang.String;)java.lang.Runtime.exec(java.lang.String,[Ljava.lang.String;,java.io.File)java.lang.Runtime.exit(int)java.lang.Runtime.halt(int)java.lang.Runtime.load(java.lang.String)java.lang.Runtime.loadLibrary(java.lang.String)java.lang.Runtime.removeShutdownHook(java.lang.Thread)java.lang.Runtime.traceInstructions(boolean)java.lang.Runtime.traceMethodCalls(boolean)java.lang.System.exit(int)java.lang.System.load(java.lang.String)java.lang.System.loadLibrary(java.lang.String)java.lang.System.runFinalizersOnExit(boolean)java.lang.System.setErr(java.io.PrintStream)java.lang.System.setIn(java.io.InputStream)java.lang.System.setOut(java.io.PrintStream)java.lang.System.setProperties(java.util.Properties)java.lang.System.setProperty(java.lang.String,java.lang.String)java.lang.System.setSecurityManager(java.lang.SecurityManager)java.security.ProtectionDomain.getClassLoader()

可以看到非常多,要想调用api函数则必须将该值设置为true

使用.setSetting("api_builtin_enabled", "true");可以设置,如果是spring boot的话可以在application.properties文件中启用配置

spring.freemarker.settings.api_builtin_enabled=true

像网上公开的部分setNewBuiltinClassResolver()绕过和文件读取poc有版本限制也是这个原因

最后

目前交流群人数超了只能邀请,需要进群交流的加作者微信备注交流群

性感群主不定期水群在线解答问题!

JAVA安全-模板注入-FreeMarker

原文始发于微信公众号(菜狗安全):JAVA安全-模板注入-FreeMarker

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

发表评论

匿名网友 填写信息