前言
開局一張圖,姿勢全靠yy
模板引擎可以讓(網(wǎng)站)程序?qū)崿F(xiàn)界面與數(shù)據(jù)分離,業(yè)務(wù)代碼與邏輯代碼的分離,這大大提升了開發(fā)效率,良好的設(shè)計也使得代碼重用變得更加容易。與此同時,它也擴展了黑客的攻擊面。除了常規(guī)的 XSS 外,注入到模板中的代碼還有可能引發(fā) RCE(遠程代碼執(zhí)行)。通常來說,這類問題會在博客,CMS,wiki 中產(chǎn)生。雖然模板引擎會提供沙箱機制,攻擊者依然有許多手段繞過它。
和常見Web注入的成因一樣,也是服務(wù)端接收了用戶的輸入,將其作為 Web 應(yīng)用模板內(nèi)容的一部分,在進行目標編譯渲染的過程中,執(zhí)行了用戶插入的惡意內(nèi)容,因而可能導(dǎo)致了敏感信息泄露、代碼執(zhí)行、GetShell 等問題。其影響范圍主要取決于模版引擎的復(fù)雜性。
小課堂---模板渲染是what?
首先 模板渲染分解為前端渲染和后端渲染,還有瀏覽器渲染。
模板只是一種提供給程序來解析的一種語法,換句話說,模板是用于從數(shù)據(jù)(變量)到實際的視覺表現(xiàn)(HTML代碼)這項工作的一種實現(xiàn)手段,而這種手段不論在前端還是后端都有應(yīng)用。
通俗點理解:拿到數(shù)據(jù),塞到模板里,然后讓渲染引擎將賽進去的東西生成 html 的文本,返回給瀏覽器,這樣做的好處展示數(shù)據(jù)快,大大提升效率。
后端渲染
后端渲染HTML的情況下,瀏覽器會直接接收到經(jīng)過服務(wù)器計算之后的呈現(xiàn)給用戶的最終的HTML字符串,這里的計算就是服務(wù)器經(jīng)過解析存放在服務(wù)器端的模板文件來完成的,在這種情況下,瀏覽器只進行了HTML的解析,以及通過操作系統(tǒng)提供的操縱顯示器顯示內(nèi)容的系統(tǒng)調(diào)用在顯示器上把HTML所代表的圖像顯示給用戶。
實現(xiàn):后端拼字符串唄…… (理論上后端模板也是字符串)
好處:模板統(tǒng)一在后端。前端(相對)省事,不占用客戶端運算資源(解析模板),只要不大改結(jié)構(gòu),文字啥的小修改后端改了就好了。
壞處:占用(部分、少部分)服務(wù)器運算資源、,response 出的數(shù)據(jù)量會(稍)大點,模板改了前端的交互和樣式什么的一樣得跟著聯(lián)動修改。
前端渲染
前端渲染就是指瀏覽器會從后端得到一些信息,這些信息或許是適用于題主所說的angularjs的模板文件,亦或是JSON等各種數(shù)據(jù)交換格式所包裝的數(shù)據(jù),甚至是直接的合法的HTML字符串。這些形式都不重要,重要的是,將這些信息組織排列形成最終可讀的HTML字符串是由瀏覽器來完成的,在形成了HTML字符串之后,再進行顯示。
好處:不占用服務(wù)端運算資源(解析模板),模板在前端(很有可能僅部分在前端),改結(jié)構(gòu)變交互都前端自己來了,改完自己調(diào)就行。不用麻煩后端再聯(lián)調(diào)神馬的。
壞處:占用(一部分、少部分)客戶端運算資源(解析模板)。前端代碼多點,畢竟包含模板代碼了么。腳本是不是首次下就慢點了(看你在意不在意這個畢竟能304和CDN啥的)??赡茉斐汕昂髢煞菽0宓那闆r,總歸要后端吐出個首屏啥的先讓用戶看見吧。那這部分頁面模板不就是后端拼好了吐出來的么。
示例1:定義一個模板,例如
????<html>
????<div>{$what}</div>
????</html>
這只是一個模板。{$what}是數(shù)據(jù)。此時不知道數(shù)據(jù)是什么。
????如果我想html里面成為
????<div>peiqi</div>
????渲染到html代碼里
渲染完成后
????<html>
????<div>peiqi</div>
????</html>
當然這只是最簡答的例子;
一般來說,至少會提供分支,迭代。還有一些內(nèi)置函數(shù),如格式化等等
那么 {$what}這個數(shù)據(jù)如何代碼傳入。
比如我工作用的模板引擎是smarty
我定義了一個模板。
????<html>
????<div>{$what}</div>
????</html>????
js代碼就是這么寫就可以了
????var tpl= new jSmart(tplStr);//tplStr就是模板的字符串。
????var content = "Hello World";
????tpl.fetch({what:content});
這樣就可以了
其余的就交給引擎去渲染執(zhí)行了。
示例2:
模板:front.tpl
????<div>
????{%$a%}
????</div>
后端:
設(shè)置變量:$smarty->assign('a', 'give data');
展示模板:$smarty->display("front.tpl");
到前端時是渲染好的html串:
????<div>
????give data
????</div>
這種方式的特點是展示數(shù)據(jù)快,直接后端拼裝好數(shù)據(jù)與模板,展現(xiàn)到用戶面前。
為什么需要服務(wù)器模板
首先解釋一下為什么HTML代碼和應(yīng)用程序邏輯混合在一起不好,看下面的例子你就知道了。假如你使用下面的代碼為你的用戶提供服務(wù):
這不僅僅是靜態(tài)HTML代碼。用戶名是從cookie里獲取并且自動填寫的。這樣一來,只要你之前登錄過該網(wǎng)站,你就無需再次輸入。但是這有一個問題,那就是你必須通過某種形式將值插入到HTML文檔中,有兩種方法可以實現(xiàn),一種是正確的,一種是有危害的。不過我們要先問一下,為什么要這么做。
下圖展示了解決該問題的完全錯誤的方法:
這段代碼有很多問題,作者不僅僅沒有對用戶的輸入進行處理,HTML代碼和PHP代碼復(fù)雜的混合在一起,非常難以理解。部分HTML代碼分布在多個函數(shù)中,這還不算什么,當你嘗試修改HTML代碼中任何內(nèi)容時,你才發(fā)現(xiàn)有多難受,比如新增css類或修改HTML標簽的順序。
上面這個例子顯然是有意寫的這么爛的,不過可以通過格式化進行優(yōu)化。然而,在大型的代碼庫中,即使代碼格式良好,也會很快變得無法管理。這就是為什么我們需要模板。
與上面混亂的代碼相比,服務(wù)器端模板提供了一種更加簡單的方法來管理動態(tài)生成的HTML代碼。最大的優(yōu)點就是你可以在服務(wù)器端動態(tài)生成HTML頁面,看起來跟靜態(tài)HTML頁面一樣?,F(xiàn)在我們來看看,當我們使用服務(wù)器端模板時,復(fù)雜的代碼看起來如何。
這對前面的代碼做了一些優(yōu)化,它依然是靜態(tài)的。為了顯示正確的信息而不是大括號占位符,我們需要一個替換它們的模板引擎。后端代碼可能是這樣的。
這段代碼的意思非常清楚,首先加載login.tpl模板文件,然后對與模板中名稱相同的變量賦值(大括號里的變量),然后調(diào)用show()函數(shù),相應(yīng)的替換它們的內(nèi)容并輸出HTML代碼。然而,我們在模板中增加了新的功能,這將會向用戶展示模板渲染的時間。
什么是服務(wù)端模板注入
通過模板,Web應(yīng)用可以把輸入轉(zhuǎn)換成特定的HTML文件或者email格式。就拿一個銷售軟件來說,我們假設(shè)它會發(fā)送大量的郵件給客戶,并在每封郵件前SKE插入問候語,它會通過Twig(一個模板引擎)做如下處理:
$output = $twig->render( $_GET['custom_email'] , array("first_name" => $user.first_name) );
有經(jīng)驗的讀者可能迅速發(fā)現(xiàn) XSS,但是問題不止如此。這行代碼其實有更深層次的隱患,假設(shè)我們發(fā)送如下請求:
????custom_email={{7*7}} // GET 參數(shù)
????49??// $output 結(jié)果
????還有更神奇的結(jié)果:
????custom_email={{self}} // GET 參數(shù)
????Object of class
????__TwigTemplate_7ae62e582f8a35e5ea6cc639800ecf15b96c0d6f78db3538221c1145580ca4a5
? ? could not be converted to string // 錯誤
我們不難猜到服務(wù)器執(zhí)行了我們傳過去的數(shù)據(jù)。每當服務(wù)器用模板引擎解析用戶的輸入時,這類問題都有可能發(fā)生。除了常規(guī)的輸入外,攻擊者還可以通過 LFI(文件包含)觸發(fā)它。模板注入和 SQL 注入的產(chǎn)生原因有幾分相似——都是將未過濾的數(shù)據(jù)傳給引擎解析。
為什么我們在模板注入前加“服務(wù)端”呢?這是為了和 jQuery,KnockoutJS 產(chǎn)生的客戶端模板注入?yún)^(qū)別開來。通常的來講,前者甚至可以讓攻擊者執(zhí)行任意代碼,而后者只能 XSS。
注入原理
????<?php
????require_once dirname(__FILE__).'/../lib/Twig/Autoloader.php';
????Twig_Autoloader::register(true);
????$twig = new Twig_Environment(new Twig_Loader_String());
????$output = $twig->render("Hello {{name}}", array("name" => $_GET["name"]));??// 將用戶輸入作為模版變量的值
????echo $output;
?????>
使用 Twig 模版引擎渲染頁面,其中模版含有 {{name}} 變量,其模版變量值來自于 GET 請求參數(shù) $_GET["name"]。
顯然這段代碼并沒有什么問題,即使你想通過 name 參數(shù)傳遞一段 JavaScript 代碼給服務(wù)端進行渲染,也許你會認為這里可以進行 XSS,
但是由于模版引擎一般都默認對渲染的變量值進行編碼和轉(zhuǎn)義,所以并不會造成跨站腳本攻擊:
但是,如果渲染的模版內(nèi)容受到用戶的控制,情況就不一樣了。修改代碼為:
????<?php
????require_once dirname(__FILE__).'/../lib/Twig/Autoloader.php';
????Twig_Autoloader::register(true);
????$twig = new Twig_Environment(new Twig_Loader_String());
????$output = $twig->render("Hello {$_GET['name']}");??// 將用戶輸入作為模版內(nèi)容的一部分
????echo $output;????
?????>
上面這段代碼在構(gòu)建模版時,拼接了用戶輸入作為模板的內(nèi)容,現(xiàn)在如果再向服務(wù)端直接傳遞 JavaScript 代碼,用戶輸入會原樣輸出,測試結(jié)果顯而易見。
在 Twig 模板引擎里, {{var}}??除了可以輸出傳遞的變量以外,還能執(zhí)行一些基本的表達式然后將其結(jié)果作為該模板變量的值,例如這里用戶輸入 name={{2*10}} ,則在服務(wù)端拼接的模版內(nèi)容為:
這里簡單分析一下,由于 {# comment #}??作為 Twig 模板引擎的默認注釋形式,所以在前端輸出的時候并不會顯示,而 {{2*8}}??作為模板變量最終會返回 16??作為其值進行顯示,因此前端最終會返回內(nèi)容 Hello IsVuln16OK ,如下圖:
漏洞發(fā)掘
每一個(重)模板引擎都有著自己的語法(點),Payload 的構(gòu)造需要針對各類模板引擎制定其不同的掃描規(guī)則,就如同 SQL 注入中有著不同的數(shù)據(jù)庫類型一樣。
簡單來說,就是更改請求參數(shù)使之承載含有模板引擎語法的 Payload,通過頁面渲染返回的內(nèi)容檢測承載的 Payload 是否有得到編譯解析,有解析則可以判定含有 Payload 對應(yīng)模板引擎注入,否則不存在 SSTI。
模板語言的語法和 HTML 語法相差甚大,因此我們可以用其獨特的語法來探測漏洞。雖然各種模板的實現(xiàn)細節(jié)不大一樣,不過它們的基本語法大致相同,我們可以發(fā)送如下 payload:
????smarty=Hello ${7*7}
????Hello 49
????freemarker=Hello ${7*7}
????Hello 49
來確認漏洞。
在一些環(huán)境下,用戶的輸入也會被當作模板的可執(zhí)行代碼。比如說變量名:
????personal_greeting=username
????Hello user01
這種情況下,XSS 的方法就無效了。但是我們可以通過破壞 template 語句,并附加注入的HTML標簽以確認漏洞:
????personal_greeting=username<tag>
????Hello
????personal_greeting=username}}<tag>
????Hello user01 <tag>
但是,如果渲染的模版內(nèi)容受到用戶的控制,情況就不一樣了。修改代碼為:
????<?php
????require_once dirname(__FILE__).'/../lib/Twig/Autoloader.php';
????Twig_Autoloader::register(true);
????$twig = new Twig_Environment(new Twig_Loader_String());
????$output = $twig->render("Hello {$_GET['name']}");??// 將用戶輸入作為模版內(nèi)容的一部分
????echo $output;????
?????>
上面這段代碼在構(gòu)建模版時,拼接了用戶輸入作為模板的內(nèi)容,現(xiàn)在如果再向服務(wù)端直接傳遞 JavaScript 代碼,用戶輸入會原樣輸出,測試結(jié)果顯而易見。
模板引擎注入
模板引擎
模板引擎(這里特指用于Web開發(fā)的模板引擎)是為了使用戶界面與業(yè)務(wù)數(shù)據(jù)(內(nèi)容)分離而產(chǎn)生的,它可以生成特定格式的文檔,用于網(wǎng)站的模板引擎就會生成一個標準的HTML文檔。
一些模板引擎:Smarty,Mako,Jinja2,Jade,Velocity,F(xiàn)reemaker和Twig,模板注入是一種注入攻擊,可以產(chǎn)生一些特別有趣的影響。對于AngularJS的情況,這可能意味著XSS,并且在服務(wù)器端注入的情況下可能意味著遠程代碼執(zhí)行。
重點來了,不同引擎有不同的測試以及注入方式!
flask/jinja2模板注入
Flask是一個使用 Python 編寫的輕量級 Web 應(yīng)用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎則使用 Jinja2
Flask框架中提供的模版引擎可能會被一些無量開發(fā)者利用引入一個服務(wù)端模版注入漏洞,如果對此感到有些困惑可以看看James Kettle在黑帽大會中分享的議題(PDF),簡而言之這個漏洞允許將語言/語法注入到模板中。在服務(wù)器的context中執(zhí)行這個輸入重現(xiàn),根據(jù)應(yīng)用的context可能導(dǎo)致任意遠程代碼執(zhí)行(遠端控制設(shè)備)
參考文章:https://www.freebuf.com/articles/web/88768.html
參考文章:https://www.freebuf.com/articles/web/98928.html
PHP/模版引擎Twig注入
可以參考本博客文章 Flask從零到無 。
這里給出一個漏洞環(huán)境代碼,本地測試
????from flask import Flask
????from flask import render_template
????from flask import request
????from flask import render_template_string
????app = Flask(__name__)
????@app.route('/test',methods=['GET', 'POST'])
????def test():
????????template = '''
????????????<div class="center-content error">
????????????????<h1>Oops! That page doesn't exist.</h1>
????????????????<h3>%s</h3>
????????????</div>?
????????''' %(request.url)
return render_template_string(template)
????if __name__ == '__main__':
????????app.debug = True
????????app.run()
代碼簡析: 我們自己簡單寫一個string類型的 html,html返回當前url,我們放入到渲染函數(shù)render_template_string進行渲染,然后頁面會打印出當前url,如果url里含有{{}} 那么便可以進行模板注入。
測試url http://127.0.0.1:5000/test?{{config}}
測試結(jié)果如下:

而如果我們使用render_template函數(shù),
????@app.route('/',methods=['GET', 'POST'])
????@app.route('/index',methods=['GET', 'POST'])#我們訪問/或者/index都會跳轉(zhuǎn)
????def index():
???????return render_template("index.html",title='Home',user=request.args.get("key"))
index.html
????<html>
??????<head>
????????<title>{{title}} - 小豬佩奇</title>
??????</head>
??????<body>
??????????<h1>Hello, {{user}}!</h1>
??????</body>
????</html>
那么將不會有模板注入,因為render_template已經(jīng)傳入一個固定好了的模板,沒法再去修改,在渲染之后傳入數(shù)據(jù),只有當?shù)谝环N代碼,我們模板可控的時候,先傳入后渲染,這樣才會導(dǎo)致ssti模板注入。
參考:https://www.freebuf.com/vuls/83999.html
CTF---模板注入
tornado的一道 ssti
https://blog.csdn.net/cccccfive/article/details/83145669
防御
為了防止此類漏洞,你應(yīng)該像使用eval()函數(shù)一樣處理字符串加載功能。盡可能加載靜態(tài)模板文件。
注意:我們已經(jīng)確定此功能類似于require()函數(shù)調(diào)用。因此,你也應(yīng)該防止本地文件包含(LFI)漏洞。不要允許用戶控制此類文件或其內(nèi)容的路徑。
另外,無論在何時,如果需要將動態(tài)數(shù)據(jù)傳遞給模板,不要直接在模板文件中執(zhí)行,你可以使用模板引擎的內(nèi)置功能來擴展表達式,實現(xiàn)同樣的效果。
附表
---
參考:https://www.anquanke.com/post/id/82856
參考:https://zhuanlan.zhihu.com/p/28823933
參考:https://www.cnblogs.com/tyomcat/p/5440488.html