怎么分析Nodejs中模板引擎渲染原理與潛在隱患探討,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。
鄲城ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場(chǎng)景,ssl證書(shū)未來(lái)市場(chǎng)廣闊!成為創(chuàng)新互聯(lián)的ssl證書(shū)銷(xiāo)售渠道,可以享受市場(chǎng)價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話(huà)聯(lián)系或者加微信:028-86922220(備注:SSL證書(shū)合作)期待與您的合作!
此前,實(shí)驗(yàn)室成員在對(duì)nodejs原型鏈污染漏洞進(jìn)行梳理時(shí),發(fā)現(xiàn)原型鏈污染漏洞可結(jié)合模板引擎的渲染達(dá)到遠(yuǎn)程命令執(zhí)行的效果。為什么原型鏈污染能結(jié)合模板引擎能達(dá)到這樣的效果?模板引擎究竟是如何工作的?除了原型鏈污染,還有其他方式也能達(dá)到同樣的效果嗎?帶著這樣的疑問(wèn),實(shí)驗(yàn)室成員決定對(duì)nodejs模板引擎的內(nèi)在機(jī)制進(jìn)行一些探索。
目前JavaScript生態(tài)圈里面的模板引擎非常之多,各引擎的實(shí)現(xiàn)原理及特性都不盡相同,很難找到一款功能豐富、書(shū)寫(xiě)簡(jiǎn)單、前后端共用的模板引擎,因此有開(kāi)發(fā)者設(shè)立了一個(gè)根據(jù)不同需求挑選不同引擎的網(wǎng)站garann.github.io/template-chooser。

根據(jù)npm的官方數(shù)據(jù),目前較受歡迎的模板引擎下載量分別如圖2所示。無(wú)恒實(shí)驗(yàn)室根據(jù)已有的服務(wù)端引擎框架,挑選了目前下載量較大,開(kāi)發(fā)者常用的幾種模板引擎進(jìn)行分析,發(fā)現(xiàn)模板引擎的渲染原理基本上都差不多,只是在一些細(xì)節(jié)的處理方式不太一致。

大多數(shù)模板渲染引擎在工作時(shí)基本都對(duì)外提供了一個(gè)render函數(shù),這個(gè)函數(shù)一般至少需要兩個(gè)參數(shù),一個(gè)是渲染的模板template,一般都為字符串,另一個(gè)是要被渲染進(jìn)模板的數(shù)據(jù)data,以object(即鍵值對(duì))的形式傳入。render函數(shù)一般都具有以下的形式:

以下面的ejs渲染代碼為例,“./views/index.ejs”為模板文件的位置,{message: 'test'}為要渲染進(jìn)模板中的數(shù)據(jù):

其中,“./views/test.ejs”文件內(nèi)容如下:

渲染結(jié)果如下,將message對(duì)應(yīng)的值test渲染進(jìn)模板中。

在render函數(shù)中,除了這兩個(gè)參數(shù)之外,多數(shù)模板引擎還提供了可控制渲染特性的參數(shù)options,用來(lái)對(duì)模板對(duì)渲染特性進(jìn)行控制,比如是否開(kāi)啟調(diào)試功能,是否開(kāi)啟緩存機(jī)制,是否打印報(bào)錯(cuò)信息,以ejs為例, 可通過(guò)設(shè)置compileDebug來(lái)開(kāi)啟調(diào)試語(yǔ)句。

基本上所有模板引擎的渲染機(jī)制都包含兩個(gè)步驟:
步驟一:根據(jù)模板數(shù)據(jù)進(jìn)行定位與分割,根據(jù)各模板定義的特殊符號(hào)找到要被替換的數(shù)據(jù)。
步驟二:根據(jù)提供的鍵值對(duì)和定位的結(jié)果進(jìn)行值的替換、拼接,最終得到渲染的結(jié)果。

在本次的分析中,無(wú)恒實(shí)驗(yàn)室主要對(duì)Mustache和ejs兩種引擎的渲染機(jī)制進(jìn)行闡述,其他引擎的渲染過(guò)程大致相同。
4.1 定位
大多數(shù)渲染引擎都規(guī)定了要被替換的數(shù)據(jù)必須被某些符號(hào)所包裹,比如Mustache默認(rèn)的符號(hào)是 {{ }} ,而ejs默認(rèn)的符號(hào)是<%= %>,因此引擎在工作時(shí),首先要在傳入的模板字符串中尋找到這些特殊符號(hào)。這一步不同引擎實(shí)現(xiàn)方式不同,如Mustache,Nunjucks是通過(guò)詞法解析也就是采用字符掃描的方式對(duì)模板字符串進(jìn)行掃描,從而定位特殊符號(hào)的位置,以如下Mustache的渲染代碼為例:

Mustache相應(yīng)的定位代碼如下,其中scanner負(fù)責(zé)對(duì)模板字符串進(jìn)行掃描,掃描的結(jié)果為會(huì)存入多個(gè)token對(duì)象中。

最終會(huì)對(duì)每一個(gè)掃描到的token進(jìn)行分類(lèi)和相應(yīng)值、位置的存儲(chǔ),并放入到tokens中供后續(xù)的使用。

示例模板字符串“whoareyou {{title}}”的掃描結(jié)果如下所示,“whoareyou ”會(huì)被標(biāo)記為text類(lèi)型,也就是純文本,而"titile"會(huì)被標(biāo)記為name類(lèi)型,是要被替換的數(shù)據(jù)。

而ejs則是直接通過(guò)正則匹配的方式,循環(huán)定位特殊符號(hào)的位置。最終模板引擎可根據(jù)特殊符號(hào)的位置找到要被替換的原始數(shù)據(jù),以對(duì)如下的模板字符串進(jìn)行定位為例:

ejs會(huì)調(diào)用parsteTemplateText函數(shù)進(jìn)行定位,其中this.templateText為原始的模板字符串,this.regex為指定的特殊符號(hào)(也就是ejs指定的'<%'、'%>'等),接下來(lái)則會(huì)通過(guò)while循環(huán)不斷匹配將原始模板字符串分割。

最終分割示例模板字符串的結(jié)果如下所示,可以看到要被替換的數(shù)據(jù)message已經(jīng)被定位出來(lái)了。

4.2 替換
在定位到要被替換的原始數(shù)據(jù)后,模板引擎則開(kāi)始根據(jù)輸入對(duì)原始數(shù)據(jù)進(jìn)行替換操作。這一步不同引擎的替換方式也不一樣。Mustache(包括Handlebars)的替換方式非常簡(jiǎn)單粗暴,就是直接替換,代碼如下所示:

如上所示,如果檢測(cè)到token為name類(lèi)型,也就是要被替換的類(lèi)型,則調(diào)用escapedValue函數(shù),該函數(shù)中會(huì)調(diào)用lookup,根據(jù)token找到對(duì)應(yīng)的值。

context.lookup最終會(huì)尋找到對(duì)應(yīng)的title的值,也就是joe。

Pug, Nunjucks, ejs等引擎的替換過(guò)程就略顯麻煩,ejs的render函數(shù)中,會(huì)先調(diào)用handleCache函數(shù)。

把如下的代碼拎出來(lái),可以看到handleCache(opts, template)的結(jié)果是一個(gè)匿名函數(shù)

而result就是最終的渲染結(jié)果,這也就意味著,ejs的具體渲染過(guò)程是由函數(shù)進(jìn)行控制的,最終會(huì)等價(jià)于:

而在handleCache函數(shù)中,這個(gè)匿名函數(shù)是經(jīng)過(guò)compile編譯而來(lái)的。

跟進(jìn)compile函數(shù)中,可以看到最終調(diào)用了Function函數(shù)構(gòu)造器動(dòng)態(tài)構(gòu)造了一個(gè)匿名函數(shù)。

以上面的ejs示例代碼為例,渲染引擎最終構(gòu)造的匿名函數(shù)如下所示,通過(guò)__append得到最終要渲染的模板數(shù)據(jù),這里同樣會(huì)采用escapeFn防止xss,可以看到ejs的處理過(guò)程,實(shí)際上也是根據(jù)定位分割的結(jié)果進(jìn)行處理拼接的過(guò)程。

從上面的分析可以看到,ejs的渲染,是通過(guò) **“動(dòng)態(tài)生成函數(shù)代碼 -> 生成匿名函數(shù) -> 調(diào)用匿名函數(shù)”**的方式去實(shí)現(xiàn)的(實(shí)際上Nunjucks, pug等許多引擎都是這么實(shí)現(xiàn)的),而這種方式是相當(dāng)危險(xiǎn)的,一旦動(dòng)態(tài)生成的函數(shù)代碼中存在用戶(hù)可控的部分,惡意用戶(hù)就可以在匿名函數(shù)中插入惡意代碼并且會(huì)被渲染引擎所執(zhí)行,最終導(dǎo)致遠(yuǎn)程代碼執(zhí)行漏洞。之前GYCTF2020 Ez_Express的題就是利用了ejs的這種特性。
其利用原理如下,ejs在渲染時(shí)會(huì)獲取渲染選項(xiàng)的某些值拼接進(jìn)函數(shù)代碼中,其中就包括動(dòng)態(tài)拼接outputFunctionName這個(gè)選項(xiàng)的值進(jìn)函數(shù)代碼中,但是ejs的作者從一開(kāi)始就禁止了用戶(hù)對(duì)渲染選項(xiàng)中outputFunctionName進(jìn)行操控,同樣也包括其他的選項(xiàng)。既然無(wú)法從正面進(jìn)行操控,可以利用javascript的原型鏈污染,往Object中注入outputFunctionName屬性進(jìn)而操控。

5.1 渲染選項(xiàng)的污染
能不能不通過(guò)原型鏈污染的方式注入惡意代碼?
來(lái)看看ejs渲染的入口函數(shù)render,其接受三個(gè)參數(shù),第一個(gè)是template,為模板字符串,第二個(gè)為data,一般會(huì)是用戶(hù)可控的數(shù)據(jù),第三個(gè)為渲染選項(xiàng)opts,這個(gè)渲染選項(xiàng)可控制匿名函數(shù)代碼的生成。

ejs為了保持可用性,允許第二個(gè)參數(shù)(一般是用戶(hù)可控參數(shù))使用一些渲染選項(xiàng),但是這些選項(xiàng)是有限定的,在代碼中被utils.shallowCopyFromList(opts, data, _OPTS_PASSABLE_WITH_DATA)函數(shù)所限定,“_OPTS_PASSABLE_WITH_DATA”指定了可控制的范圍,該范圍如下:

最終這些渲染選項(xiàng)會(huì)進(jìn)入到compile函數(shù)也就是匿名函數(shù)代碼生成的過(guò)程中,也就是說(shuō),若用戶(hù)數(shù)據(jù)能夠控制傳入的data選項(xiàng),那么惡意用戶(hù)就能污染opts中的“delimiter”, “scope”, “context”,“debug”,“compileDebug”, “client”,“_with”,“rmWhitespace”,“strict”,“filename”, “async”。

5.2 匿名函數(shù)代碼的生成
那么這些可污染選項(xiàng),能對(duì)匿名函數(shù)代碼的生成造成什么影響呢?繼續(xù)跟進(jìn)渲染引擎發(fā)現(xiàn),匿名函數(shù)代碼在動(dòng)態(tài)生成的過(guò)程中,由三部分構(gòu)成,分別是prepended, this.source, append三部分,而這三個(gè)部分的代碼又受到了渲染選項(xiàng)的影響

逐步跟進(jìn)匿名函數(shù)代碼的生成,首先可以看到outputFunctionName, localsName, destructuredLocals這個(gè)幾個(gè)渲染選項(xiàng)可影響prepended代碼的生成,但是這些選項(xiàng)都不在可控的范圍內(nèi)。

繼續(xù)跟進(jìn)函數(shù)代碼的生成過(guò)程,如下所示,compileDebug是在可控范圍內(nèi),filename也在可控范圍內(nèi),但是filename被JSON.stringify進(jìn)行轉(zhuǎn)換了,無(wú)法逃逸出來(lái),因此也無(wú)法污染函數(shù)代碼。

接下來(lái)繼續(xù)跟進(jìn),可以看到在如下的代碼中,compileDebug和filename都是我們可控的,并且最后filename也被直接拼接進(jìn)匿名函數(shù)的代碼中,因此在這里就可以通過(guò)控制filename選項(xiàng)注入惡意的代碼。

5.3 惡意代碼的注入
直接編寫(xiě)如下的代碼,看看最終生成的匿名函數(shù)的源碼是怎么樣的。

如下所示,可以看到/etc/passwd最終被插入到一個(gè)注釋的行中:

此時(shí)對(duì)payload進(jìn)行修改,加入換行符逃逸注釋。

得到的結(jié)果結(jié)果如下,可以看到通過(guò)換行符已經(jīng)成功逃逸了注釋?zhuān)牵瑢?duì)于這樣一個(gè)函數(shù),其正常的流程走到try的代碼塊時(shí),除非出現(xiàn)異常,否則的話(huà)直接走到return代碼就執(zhí)行結(jié)束了,壓根不會(huì)走到我們注入的代碼。除非能觸發(fā)異常繼續(xù)走下去,但是看try塊里面的代碼,基本沒(méi)有可觸發(fā)異常的邏輯。

5.4 finally逃逸try catch限制
在常見(jiàn)的編程語(yǔ)言中,try catch實(shí)際上還可以再接一個(gè)finally,無(wú)論最終try cath如何,都會(huì)走到finally中的代碼塊,但是return的情況下,還能執(zhí)行finally嗎,paylaod如下:

來(lái)看看最終生成的代碼:

試了一下,還是能走到finally代碼塊的,最終注入的代碼被引擎所執(zhí)行

實(shí)際上,采用動(dòng)態(tài)生成函數(shù)進(jìn)行渲染的引擎中,除了ejs,還有pug,nunjuck等,那么其他引擎是否也存在同樣的問(wèn)題?無(wú)恒實(shí)驗(yàn)室也對(duì)其他的模板引擎進(jìn)行了分析,發(fā)現(xiàn)其他引擎雖然也采用了這種方式進(jìn)行渲染,但是較好的控制住了渲染選項(xiàng)對(duì)函數(shù)代碼的影響。
5.5 影響版本
在實(shí)際的測(cè)試過(guò)程中,發(fā)現(xiàn)在較老的ejs版本上,并不存在該漏洞,影響版本范圍為:
2.7.2(包括2.7.2)到最新版(3.1.5)。
5.6 利用難度
如上的漏洞實(shí)際上要求服務(wù)端的代碼直接拿用戶(hù)可控的json數(shù)據(jù)進(jìn)行渲染,包括key和value。對(duì)于正常的服務(wù)端框架來(lái)說(shuō),框架中間件是能將傳入的json數(shù)據(jù)轉(zhuǎn)換為json對(duì)象的,比較少有開(kāi)發(fā)會(huì)直接拿整個(gè)json數(shù)據(jù)進(jìn)行渲染,但是也存在研發(fā)直接使用JSON.parse解析用戶(hù)數(shù)據(jù)進(jìn)行模板渲染的用法。
5.7 臨時(shí)修復(fù)方案
發(fā)現(xiàn)了該漏洞之后,無(wú)恒實(shí)驗(yàn)室及時(shí)與ejs的作者取得聯(lián)系,ejs的作者闡述這實(shí)際上屬于模板引擎本身的特性,用戶(hù)可控的json數(shù)據(jù)不應(yīng)該被直接傳入進(jìn)行使用,暫無(wú)提供修復(fù)版本的計(jì)劃。
無(wú)恒實(shí)驗(yàn)室對(duì)該問(wèn)題進(jìn)行披露,希望能夠幫助受影響的業(yè)務(wù)避免潛在的安全風(fēng)險(xiǎn)。若有線(xiàn)上業(yè)務(wù)代碼符合漏洞的觸發(fā)條件,因目前ejs暫時(shí)沒(méi)有修復(fù)的版本,無(wú)恒實(shí)驗(yàn)室提供如下的幾個(gè)臨時(shí)的修復(fù)方案:
○ 若無(wú)版本要求,建議使用不在影響范圍內(nèi)的版本,如2.7.1。
○ 若在受影響版本范圍內(nèi),建議不直接獲取用戶(hù)的json數(shù)據(jù)進(jìn)行模板渲染。
○ 若需要直接獲取用戶(hù)數(shù)據(jù)進(jìn)行模板渲染,確保用戶(hù)數(shù)據(jù)中不存在compileDebug和filename選項(xiàng)或者這些選項(xiàng)的值不可控。可采用如下的安全檢測(cè)函數(shù)。

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對(duì)創(chuàng)新互聯(lián)的支持。
網(wǎng)站題目:怎么分析Nodejs中模板引擎渲染原理與潛在隱患探討
標(biāo)題路徑:http://www.chinadenli.net/article20/iehgco.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供面包屑導(dǎo)航、網(wǎng)站收錄、網(wǎng)站維護(hù)、網(wǎng)站排名、云服務(wù)器、手機(jī)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)