怎么分析Nodejs中模板引擎渲染原理與潛在隱患探討,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
鄲城ssl適用于網(wǎng)站、小程序/APP、API接口等需要進行數(shù)據(jù)傳輸應用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
此前,實驗室成員在對nodejs原型鏈污染漏洞進行梳理時,發(fā)現(xiàn)原型鏈污染漏洞可結合模板引擎的渲染達到遠程命令執(zhí)行的效果。為什么原型鏈污染能結合模板引擎能達到這樣的效果?模板引擎究竟是如何工作的?除了原型鏈污染,還有其他方式也能達到同樣的效果嗎?帶著這樣的疑問,實驗室成員決定對nodejs模板引擎的內(nèi)在機制進行一些探索。
目前JavaScript生態(tài)圈里面的模板引擎非常之多,各引擎的實現(xiàn)原理及特性都不盡相同,很難找到一款功能豐富、書寫簡單、前后端共用的模板引擎,因此有開發(fā)者設立了一個根據(jù)不同需求挑選不同引擎的網(wǎng)站garann.github.io/template-chooser。
根據(jù)npm的官方數(shù)據(jù),目前較受歡迎的模板引擎下載量分別如圖2所示。無恒實驗室根據(jù)已有的服務端引擎框架,挑選了目前下載量較大,開發(fā)者常用的幾種模板引擎進行分析,發(fā)現(xiàn)模板引擎的渲染原理基本上都差不多,只是在一些細節(jié)的處理方式不太一致。
大多數(shù)模板渲染引擎在工作時基本都對外提供了一個render函數(shù),這個函數(shù)一般至少需要兩個參數(shù),一個是渲染的模板template,一般都為字符串,另一個是要被渲染進模板的數(shù)據(jù)data,以object(即鍵值對)的形式傳入。render函數(shù)一般都具有以下的形式:
以下面的ejs渲染代碼為例,“./views/index.ejs”為模板文件的位置,{message: 'test'}為要渲染進模板中的數(shù)據(jù):
其中,“./views/test.ejs”文件內(nèi)容如下:
渲染結果如下,將message對應的值test渲染進模板中。
在render函數(shù)中,除了這兩個參數(shù)之外,多數(shù)模板引擎還提供了可控制渲染特性的參數(shù)options,用來對模板對渲染特性進行控制,比如是否開啟調(diào)試功能,是否開啟緩存機制,是否打印報錯信息,以ejs為例, 可通過設置compileDebug來開啟調(diào)試語句。
基本上所有模板引擎的渲染機制都包含兩個步驟:
步驟一:根據(jù)模板數(shù)據(jù)進行定位與分割,根據(jù)各模板定義的特殊符號找到要被替換的數(shù)據(jù)。
步驟二:根據(jù)提供的鍵值對和定位的結果進行值的替換、拼接,最終得到渲染的結果。
在本次的分析中,無恒實驗室主要對Mustache和ejs兩種引擎的渲染機制進行闡述,其他引擎的渲染過程大致相同。
4.1 定位
大多數(shù)渲染引擎都規(guī)定了要被替換的數(shù)據(jù)必須被某些符號所包裹,比如Mustache默認的符號是 {{ }} ,而ejs默認的符號是<%= %>,因此引擎在工作時,首先要在傳入的模板字符串中尋找到這些特殊符號。這一步不同引擎實現(xiàn)方式不同,如Mustache,Nunjucks是通過詞法解析也就是采用字符掃描的方式對模板字符串進行掃描,從而定位特殊符號的位置,以如下Mustache的渲染代碼為例:
Mustache相應的定位代碼如下,其中scanner負責對模板字符串進行掃描,掃描的結果為會存入多個token對象中。
最終會對每一個掃描到的token進行分類和相應值、位置的存儲,并放入到tokens中供后續(xù)的使用。
示例模板字符串“whoareyou {{title}}”的掃描結果如下所示,“whoareyou ”會被標記為text類型,也就是純文本,而"titile"會被標記為name類型,是要被替換的數(shù)據(jù)。
而ejs則是直接通過正則匹配的方式,循環(huán)定位特殊符號的位置。最終模板引擎可根據(jù)特殊符號的位置找到要被替換的原始數(shù)據(jù),以對如下的模板字符串進行定位為例:
ejs會調(diào)用parsteTemplateText函數(shù)進行定位,其中this.templateText為原始的模板字符串,this.regex為指定的特殊符號(也就是ejs指定的'<%'、'%>'等),接下來則會通過while循環(huán)不斷匹配將原始模板字符串分割。
最終分割示例模板字符串的結果如下所示,可以看到要被替換的數(shù)據(jù)message已經(jīng)被定位出來了。
4.2 替換
在定位到要被替換的原始數(shù)據(jù)后,模板引擎則開始根據(jù)輸入對原始數(shù)據(jù)進行替換操作。這一步不同引擎的替換方式也不一樣。Mustache(包括Handlebars)的替換方式非常簡單粗暴,就是直接替換,代碼如下所示:
如上所示,如果檢測到token為name類型,也就是要被替換的類型,則調(diào)用escapedValue函數(shù),該函數(shù)中會調(diào)用lookup,根據(jù)token找到對應的值。
context.lookup最終會尋找到對應的title的值,也就是joe。
Pug, Nunjucks, ejs等引擎的替換過程就略顯麻煩,ejs的render函數(shù)中,會先調(diào)用handleCache函數(shù)。
把如下的代碼拎出來,可以看到handleCache(opts, template)的結果是一個匿名函數(shù)
而result就是最終的渲染結果,這也就意味著,ejs的具體渲染過程是由函數(shù)進行控制的,最終會等價于:
而在handleCache函數(shù)中,這個匿名函數(shù)是經(jīng)過compile編譯而來的。
跟進compile函數(shù)中,可以看到最終調(diào)用了Function函數(shù)構造器動態(tài)構造了一個匿名函數(shù)。
以上面的ejs示例代碼為例,渲染引擎最終構造的匿名函數(shù)如下所示,通過__append得到最終要渲染的模板數(shù)據(jù),這里同樣會采用escapeFn防止xss,可以看到ejs的處理過程,實際上也是根據(jù)定位分割的結果進行處理拼接的過程。
從上面的分析可以看到,ejs的渲染,是通過 **“動態(tài)生成函數(shù)代碼 -> 生成匿名函數(shù) -> 調(diào)用匿名函數(shù)”**的方式去實現(xiàn)的(實際上Nunjucks, pug等許多引擎都是這么實現(xiàn)的),而這種方式是相當危險的,一旦動態(tài)生成的函數(shù)代碼中存在用戶可控的部分,惡意用戶就可以在匿名函數(shù)中插入惡意代碼并且會被渲染引擎所執(zhí)行,最終導致遠程代碼執(zhí)行漏洞。之前GYCTF2020 Ez_Express的題就是利用了ejs的這種特性。
其利用原理如下,ejs在渲染時會獲取渲染選項的某些值拼接進函數(shù)代碼中,其中就包括動態(tài)拼接outputFunctionName這個選項的值進函數(shù)代碼中,但是ejs的作者從一開始就禁止了用戶對渲染選項中outputFunctionName進行操控,同樣也包括其他的選項。既然無法從正面進行操控,可以利用javascript的原型鏈污染,往Object中注入outputFunctionName屬性進而操控。
5.1 渲染選項的污染
能不能不通過原型鏈污染的方式注入惡意代碼?
來看看ejs渲染的入口函數(shù)render,其接受三個參數(shù),第一個是template,為模板字符串,第二個為data,一般會是用戶可控的數(shù)據(jù),第三個為渲染選項opts,這個渲染選項可控制匿名函數(shù)代碼的生成。
ejs為了保持可用性,允許第二個參數(shù)(一般是用戶可控參數(shù))使用一些渲染選項,但是這些選項是有限定的,在代碼中被utils.shallowCopyFromList(opts, data, _OPTS_PASSABLE_WITH_DATA)函數(shù)所限定,“_OPTS_PASSABLE_WITH_DATA”指定了可控制的范圍,該范圍如下:
最終這些渲染選項會進入到compile函數(shù)也就是匿名函數(shù)代碼生成的過程中,也就是說,若用戶數(shù)據(jù)能夠控制傳入的data選項,那么惡意用戶就能污染opts中的“delimiter”, “scope”, “context”,“debug”,“compileDebug”, “client”,“_with”,“rmWhitespace”,“strict”,“filename”, “async”。
5.2 匿名函數(shù)代碼的生成
那么這些可污染選項,能對匿名函數(shù)代碼的生成造成什么影響呢?繼續(xù)跟進渲染引擎發(fā)現(xiàn),匿名函數(shù)代碼在動態(tài)生成的過程中,由三部分構成,分別是prepended, this.source, append三部分,而這三個部分的代碼又受到了渲染選項的影響
逐步跟進匿名函數(shù)代碼的生成,首先可以看到outputFunctionName, localsName, destructuredLocals這個幾個渲染選項可影響prepended代碼的生成,但是這些選項都不在可控的范圍內(nèi)。
繼續(xù)跟進函數(shù)代碼的生成過程,如下所示,compileDebug是在可控范圍內(nèi),filename也在可控范圍內(nèi),但是filename被JSON.stringify進行轉換了,無法逃逸出來,因此也無法污染函數(shù)代碼。
接下來繼續(xù)跟進,可以看到在如下的代碼中,compileDebug和filename都是我們可控的,并且最后filename也被直接拼接進匿名函數(shù)的代碼中,因此在這里就可以通過控制filename選項注入惡意的代碼。
5.3 惡意代碼的注入
直接編寫如下的代碼,看看最終生成的匿名函數(shù)的源碼是怎么樣的。
如下所示,可以看到/etc/passwd最終被插入到一個注釋的行中:
此時對payload進行修改,加入換行符逃逸注釋。
得到的結果結果如下,可以看到通過換行符已經(jīng)成功逃逸了注釋,但是,對于這樣一個函數(shù),其正常的流程走到try的代碼塊時,除非出現(xiàn)異常,否則的話直接走到return代碼就執(zhí)行結束了,壓根不會走到我們注入的代碼。除非能觸發(fā)異常繼續(xù)走下去,但是看try塊里面的代碼,基本沒有可觸發(fā)異常的邏輯。
5.4 finally逃逸try catch限制
在常見的編程語言中,try catch實際上還可以再接一個finally,無論最終try cath如何,都會走到finally中的代碼塊,但是return的情況下,還能執(zhí)行finally嗎,paylaod如下:
來看看最終生成的代碼:
試了一下,還是能走到finally代碼塊的,最終注入的代碼被引擎所執(zhí)行
實際上,采用動態(tài)生成函數(shù)進行渲染的引擎中,除了ejs,還有pug,nunjuck等,那么其他引擎是否也存在同樣的問題?無恒實驗室也對其他的模板引擎進行了分析,發(fā)現(xiàn)其他引擎雖然也采用了這種方式進行渲染,但是較好的控制住了渲染選項對函數(shù)代碼的影響。
5.5 影響版本
在實際的測試過程中,發(fā)現(xiàn)在較老的ejs版本上,并不存在該漏洞,影響版本范圍為:
2.7.2(包括2.7.2)到最新版(3.1.5)。
5.6 利用難度
如上的漏洞實際上要求服務端的代碼直接拿用戶可控的json數(shù)據(jù)進行渲染,包括key和value。對于正常的服務端框架來說,框架中間件是能將傳入的json數(shù)據(jù)轉換為json對象的,比較少有開發(fā)會直接拿整個json數(shù)據(jù)進行渲染,但是也存在研發(fā)直接使用JSON.parse解析用戶數(shù)據(jù)進行模板渲染的用法。
5.7 臨時修復方案
發(fā)現(xiàn)了該漏洞之后,無恒實驗室及時與ejs的作者取得聯(lián)系,ejs的作者闡述這實際上屬于模板引擎本身的特性,用戶可控的json數(shù)據(jù)不應該被直接傳入進行使用,暫無提供修復版本的計劃。
無恒實驗室對該問題進行披露,希望能夠幫助受影響的業(yè)務避免潛在的安全風險。若有線上業(yè)務代碼符合漏洞的觸發(fā)條件,因目前ejs暫時沒有修復的版本,無恒實驗室提供如下的幾個臨時的修復方案:
○ 若無版本要求,建議使用不在影響范圍內(nèi)的版本,如2.7.1。
○ 若在受影響版本范圍內(nèi),建議不直接獲取用戶的json數(shù)據(jù)進行模板渲染。
○ 若需要直接獲取用戶數(shù)據(jù)進行模板渲染,確保用戶數(shù)據(jù)中不存在compileDebug和filename選項或者這些選項的值不可控。可采用如下的安全檢測函數(shù)。
看完上述內(nèi)容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對創(chuàng)新互聯(lián)的支持。
網(wǎng)站題目:怎么分析Nodejs中模板引擎渲染原理與潛在隱患探討
標題路徑:http://jinyejixie.com/article20/iehgco.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供面包屑導航、網(wǎng)站收錄、網(wǎng)站維護、網(wǎng)站排名、云服務器、手機網(wǎng)站建設
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)