哇!被反應前面每篇文長都太長了,寫點輕鬆小品吧 xD!
今天文章會帶大家認識 Template Injection (伺服器和客戶端)這個 Bug Type 以及一個 Payload 的構造過程、WAF 規則以及一些常見的自動化手段。
現行的網站框架常常會有所謂的模板結構,例如最簡單以 Python Flask 而言:
@app.route('/')
def index():
return render_template('index.html', user=request.args.get('name', 'Guest'))
其中 index.html 內容可能是
<h1>
Hello {{name}}
</h1>
就這樣簡單的結構即可完成一個給定固定模板,根據使用者提供的參數進行內容渲染之功能。
然而,假設今天無條件信任使用者傳入的模板內容並進行渲染呢?
相信各位朋友如果有在 CTF 或任何地方接觸過 SSTI,一定都看過的例子是
{{7*7}}
疑?顯示出了 49,沒錯!這代表用戶傳入的文字是被轉成模板語言/放進模板語法的,這樣的攻擊就叫做模板注入(Server Side Template Injection)。
當然,由於模板代表的是傳入一個變數/量值,所以在部分程式語言中 SSTI 都不是像 {{os.system('whoami')}} 那樣可以直接完成攻擊的。
像是對於 Python Flask,一個標準的 RCE Payload 大概長這個樣子:
{{''.__class__.mro()[1].__subclasses__()[396]('cat flag.txt',shell=True,stdout=-1).communicate()[0].strip()}}
代表的是:
從 '' 中取到 __class__ string 後,透過 mro() (Method Resolution Order) 去取到 <object> 這個大類,才能再枚舉裡面細項的 __subclasses__ 還原所有可使用的 class,找到可以執行系統命令的 class 來調用執行命令函數(像是一個常用的就是 <class 'subprocess.Popen'>),直接 call 我們的 Popen 函數來執行任意命令。
像這樣的一個攻擊類別,是由 Port Swigger 資深研究員 James Kettle 率先在 2015 年於 Black Hat USA 以一篇 Server-Side Template Injection: RCE for the modern webapp[1] 做出發表的。
它甚至針對不同的模板引擎做了一張判斷圖表,原理當然就是基於每種模板引擎識別的模板標籤語法之不同

不過隨著時代演進,許多方便的工具當然會應運而生,像是筆者非常喜歡的 Tinja:
https://github.com/Hackmanit/TInjA
就內建了 44 種模板語法供識別,是打滲透測試的好幫手!
像是 Server Side 的請求偽造 SSRF (Server Side Request Forgery) 有在 Client Side 與之對應的 CSRF (Cliet Side ...) 一樣,模板注入這樣的攻擊也會發生在 Client (Browser) Side (達成效果通常等價大家熟悉的 XSS)
如果熟悉前端開發的朋友,一定多少聽過 AngularJS 這樣的前端模組,通常有雙向資料繫結、模組化等優點。
而在 AngularJS 中就有所謂的模板語法 像這樣:
<!DOCTYPE html>
<html ng-app="a">
<body ng-controller="c">
<input ng-model="name">
<h1>Hello, {{n}}!</h1>
<script src="https://googleapis.com"></script>
<script>
angular.module('a', []).controller('c', function($scope) {
$scope.name = 'World';
});
</script>
</body>
</html>
AngularJS 中最常見的 Payload 通常長像:
{{$on.constructor('alert(1)')()}}
{{constructor.constructor('alert(1)')()}}
這些是透過 JS 中的 constructor [2] 可以用來建構一個會執行 alert(1) 的 Function Object,最後透過小括號觸發執行。
然而,有些時候如果伺服器端有開啟所謂的 CSP (Content Security Policy) [3] 保護,並且當中沒有所謂 unsafe-eval 的選項開啟,就會阻止這樣的 inline 函數構造後被執行。
這時候如果還想達成 CSTI Attack,常見做法就是要坐地取材想辦法拿到 window 等 JS global object 來直接執行想執行的 JS 函數。
可以參考 Huli 大大寫的:https://blog.huli.tw/2022/09/01/angularjs-csp-bypass-cdnjs/ [3]
這類攻擊帶來的危害除了伺服器端的直接渲染,更多時候也是在進行 XSS 攻擊時為了繞過上面提過的 CSP 保護做的受執行 JS 引入來源限制。
當它允許一些 cdn 通過時,如果攻擊者能在 cdn side 找到 AngularJS 可引入(例如 https://cdnjs.cloudflare.com 上面有 https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js ),那基本上就可以搭配 CSTI 攻擊達成組合技完成 XSS。
由於 CSTI 其實不是本次主題只是源自筆者寫到這邊的碎碎念,更多技巧就一樣可以去參考 Huli 大大的 Beyond XSS 一文吧 owob
https://aszx87410.github.io/beyond-xss/ch3/csti/#angularjs-and-csp-bypass [4]
對於 SSTI 攻擊的防守,除了不要寫出像是 render_template_string(user.input) 這樣的程式碼,常常 WAF (網頁防火牆)也是受 Real World 實戰以及 CTF 賽場熱議的主題。
道高一尺,魔高一丈!
一樣以 Python Flask 為例,但同樣的 mindset 常可以擴展去別的語種
如果 WAF 只記得過濾了 {{...}},因為模板多變的寫法可以改寫用 {%...%} 方法做表達。
字串組合也是一招,像是如果過濾了 class 字串,可傳入的 Payload 在遇到 class/subclass 時候會改寫成 "__subcla"+"ss__"
其他還有很多像是:
當句點 . 被過濾,我們有 obj['attr'] 等價於 obj.attr
當中括號 [] 被過濾,有 arr.__getitem__(0) arr.pop(0) 都等價於 arr[0] ...
還有太多了,不過這邊一樣推薦一個 Flask SSTI 很好用的 WAF Bypass 工具:
https://github.com/Marven11/Fenjing
最後埋個伏筆,對抗 SSTI 攻擊其實大家還很常討論的是...(待續)