新聞中心
一、 前言

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶,將通過(guò)不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊(cè)、網(wǎng)絡(luò)空間、營(yíng)銷軟件、網(wǎng)站建設(shè)、海原網(wǎng)站維護(hù)、網(wǎng)站推廣。
在信息安全領(lǐng)域,可信系統(tǒng)(Trusted system)是一個(gè)讓人心動(dòng)的目標(biāo),它指的是一個(gè)通過(guò)實(shí)施特定的安全策略而達(dá)到一定可信程度的系統(tǒng)。
在計(jì)算機(jī)中,可信平臺(tái)模塊(Trusted Platform Module,TPM)已經(jīng)投入使用,它符合可信賴計(jì)算組織(Trusted Computing Group,TCG)制定的TPM規(guī)范,是為了實(shí)現(xiàn)可信系統(tǒng)目標(biāo)的而打造的一款安全芯片。作為可信系統(tǒng)的信任根,TPM是可信計(jì)算的核心模塊,為計(jì)算機(jī)安全提供了強(qiáng)有力的保障。
而在我們的web系統(tǒng)中,想打造一個(gè)可信系統(tǒng)似乎是個(gè)偽命題,"永遠(yuǎn)不要相信客戶端的輸入"是基本的安全準(zhǔn)則。實(shí)際上,在可信系統(tǒng)中的可信也并不是說(shuō)真的是絕對(duì)安全,維基上對(duì)其的解釋為:“可信的”(Trusted)未必意味著對(duì)用戶而言是“值得信賴的”(Trustworthy)。確切而言,它意味著可以充分相信其行為會(huì)更全面地遵循設(shè)計(jì),而執(zhí)行設(shè)計(jì)者和軟件編寫(xiě)者所禁止的行為的概率很低。
從這個(gè)角度講,我們把其當(dāng)做一個(gè)美好的愿景,我們希望能夠構(gòu)造一個(gè)web系統(tǒng)中的TPM,可以把惡意行為控制在一定的概率之內(nèi),從而實(shí)現(xiàn)一個(gè)相對(duì)可信的web系統(tǒng)。
二、可信前端
在可信系統(tǒng)中,TPM的一個(gè)重要作用就是鑒別消息來(lái)源的真實(shí)性,保障終端的可信。在web系統(tǒng)中,我們的消息來(lái)源就是用戶。隨著撞庫(kù)、惡意注冊(cè)、薅羊毛等產(chǎn)業(yè)的蓬勃發(fā)展,在越來(lái)越多的場(chǎng)景我們需要鑒別請(qǐng)求數(shù)據(jù)是否來(lái)自真實(shí)的用戶,保護(hù)真實(shí)用戶的數(shù)據(jù)安全。
所以想要構(gòu)造一個(gè)web系統(tǒng)中的TPM,首要問(wèn)題就是需要保證輸入數(shù)據(jù)安全,打造一個(gè)相對(duì)可信的前端環(huán)境。但是由于web的開(kāi)放特性,前端作為數(shù)據(jù)采集的最前線,js代碼始終暴露在外,在這種情況下,防止惡意偽造請(qǐng)求變得非常困難,可信前端也就成了無(wú)稽之談。
在反復(fù)對(duì)抗中,代碼保護(hù)也就是通常意義上的js代碼混淆的重要性逐漸彰顯出來(lái)。今天我就想和大家聊一聊js混淆的問(wèn)題。
1、為什么需要js混淆
顯而易見(jiàn),是為了保護(hù)我們的前端代碼邏輯。
在web系統(tǒng)發(fā)展早期,js在web系統(tǒng)中承擔(dān)的職責(zé)并不多,只是簡(jiǎn)單的提交表單,js文件非常簡(jiǎn)單,也不需要任何的保護(hù)。
隨著js文件體積的增大,為了縮小js體積,加快http傳輸速度,開(kāi)始出現(xiàn)了很多對(duì)js的壓縮工具,比如 uglify、compressor、clouser。。。它們的工作主要是
· 合并多個(gè)js文件
· 去除js代碼里面的空格和換行
· 壓縮js里面的變量名
· 剔除掉注釋
壓縮后的代碼
雖然壓縮工具出發(fā)點(diǎn)都是為了減少js文件的體積,但是人們發(fā)現(xiàn)壓縮替換后的代碼已經(jīng)比源代碼可讀性差了很多,間接起到了代碼保護(hù)的作用,于是壓縮js文件成為了前端發(fā)布的標(biāo)配之一。但是后來(lái)市面上主流瀏覽器chrome、Firefox等都提供了js格式化的功能,能夠很快的把壓縮后的js美化,再加上現(xiàn)代瀏覽器強(qiáng)大的debug功能,單純壓縮過(guò)的js代碼對(duì)于真正懷有惡意的人,已經(jīng)不能起到很好的防御工作,出現(xiàn)了"防君子不防小人"的尷尬局面。
chrome開(kāi)發(fā)者工具格式化之后的代碼
而在web應(yīng)用越來(lái)越豐富的今天,伴隨著瀏覽器性能和網(wǎng)速的提高,js承載了更多的工作,不少后端邏輯都在向前端轉(zhuǎn)移,與此同時(shí)也讓更多的不法分子有機(jī)可乘。在web模型中,js往往是不法分子的第一個(gè)突破口。知曉了前端邏輯,不法分子可以模擬成一個(gè)正常的用戶來(lái)實(shí)施自己的惡意行為。所以,在很多登錄、注冊(cè)、支付、交易等等頁(yè)面中,關(guān)鍵業(yè)務(wù)和風(fēng)控系統(tǒng)依賴的js都不希望被人輕易的破解,js混淆應(yīng)運(yùn)而生。
2、js混淆是不是紙老虎
這是一個(gè)老生常談的問(wèn)題。實(shí)際上,代碼混淆早就不是一個(gè)新鮮的名詞,在桌面軟件時(shí)代,大多數(shù)的軟件都會(huì)進(jìn)行代碼混淆、加殼等手段來(lái)保護(hù)自己的代碼。Java和.NET都有對(duì)應(yīng)的混淆器。黑客們對(duì)這個(gè)當(dāng)然也不陌生,許多病毒程序?yàn)榱朔床闅?,也?huì)進(jìn)行高度的混淆。只不過(guò)由于js是動(dòng)態(tài)腳本語(yǔ)言,在http中傳輸?shù)木褪窃创a,逆向起來(lái)要比打包編譯后的軟件簡(jiǎn)單很多,很多人因此覺(jué)得混淆是多此一舉。
NET混淆器dotFuscator
其實(shí)正是因?yàn)閖s傳輸?shù)木褪窃创a,我們才需要進(jìn)行混淆,暴露在外的代碼沒(méi)有絕對(duì)的安全,但是在對(duì)抗中,精心設(shè)計(jì)的混淆代碼能夠給破壞者帶來(lái)不小的麻煩,也能夠?yàn)榉朗卣郀?zhēng)取更多的時(shí)間,相對(duì)于破解來(lái)說(shuō),混淆器規(guī)則的更替成本要小得多,在高強(qiáng)度的攻防中,可以大大增加破解者的工作量,起到防御作用。從這個(gè)角度來(lái)講,關(guān)鍵代碼進(jìn)行混淆是必不可少的步驟。
3、如何進(jìn)行js混淆
js混淆器大致有兩種:
· 通過(guò)正則替換實(shí)現(xiàn)的混淆器
· 通過(guò)語(yǔ)法樹(shù)替換實(shí)現(xiàn)的混淆器
第一種實(shí)現(xiàn)成本低,但是效果也一般,適合對(duì)混淆要求不高的場(chǎng)景。第二種實(shí)現(xiàn)成本較高,但是更靈活,而且更安全,更適合對(duì)抗場(chǎng)景,我這里主要講一下第二種。基于語(yǔ)法層面的混淆器其實(shí)類似于編譯器,基本原理和編譯器類似,我們先對(duì)編譯器做一些基本的介紹。
名詞解釋
token: 詞法單元,也有叫詞法記號(hào)的,詞法分析器的產(chǎn)物,文本流被分割后的最小單位。
AST: 抽象語(yǔ)法樹(shù),語(yǔ)法分析器的產(chǎn)物,是源代碼的抽象語(yǔ)法結(jié)構(gòu)的樹(shù)狀表現(xiàn)形式。
編譯器VS混淆器
編譯器工作流程
簡(jiǎn)單的說(shuō),當(dāng)我們讀入一段字符串文本(source code),詞法分析器會(huì)把它拆成一個(gè)一個(gè)小的單位(token),比如數(shù)字1 是一個(gè)token, 字符串'abc'是一個(gè)token等等。接下來(lái)語(yǔ)法分析器會(huì)把這些單位組成一顆樹(shù)狀結(jié)構(gòu)(AST),這個(gè)樹(shù)狀結(jié)構(gòu)就代表了token們的組成關(guān)系。比如 1 + 2 就會(huì)展示成一棵加法樹(shù),左右子節(jié)點(diǎn)分別是token - 1 和token - 2 ,中間token表示加法。編譯器根據(jù)生成的AST轉(zhuǎn)換到中間代碼,最終轉(zhuǎn)換成機(jī)器代碼。
對(duì)編譯器更多細(xì)節(jié)感興趣的同學(xué)可以移步龍書(shū):編譯原理
混淆器工作流程
編譯器需要把源代碼編譯成中間代碼或者機(jī)器碼,而我們的混淆器輸出其實(shí)還是js。所以我們從語(yǔ)法分析之后往下的步驟并不需要。想想我們的目標(biāo)是什么,是修改原有的js代碼結(jié)構(gòu),在這里面這個(gè)結(jié)構(gòu)對(duì)應(yīng)的是什么呢?就是AST。任何一段正確的js代碼一定可以組成一顆AST,同樣,因?yàn)锳ST表示了各個(gè)token的邏輯關(guān)系,我們也可以通過(guò)AST反過(guò)來(lái)生成一段js代碼。所以,你只需要構(gòu)造出一顆AST,就能生成任何js代碼!混淆過(guò)程如上右圖所示
通過(guò)修改AST生成一個(gè)新的AST,新的AST就可以對(duì)應(yīng)新的JavaScript代碼。
規(guī)則設(shè)計(jì)
知道了大致的混淆流程,最重要的環(huán)節(jié)就是設(shè)計(jì)規(guī)則。我們上面說(shuō)了,我們需要生成新的AST結(jié)構(gòu)意味著會(huì)生成和源代碼不一樣的js代碼,但是我們的混淆是不能破壞原有代碼的執(zhí)行結(jié)果的,所以混淆規(guī)則必須保證是在不破壞代碼執(zhí)行結(jié)果的情況下,讓代碼變得更難以閱讀。
具體的混淆規(guī)則各位可以自行根據(jù)需求設(shè)計(jì),比如拆分字符串、拆分?jǐn)?shù)組,增加廢代碼等等。
參考:提供商業(yè)混淆服務(wù)的jscramble的混淆規(guī)則
實(shí)現(xiàn)
很多人看到這里就望而卻步,因?yàn)樵~法分析和文法分析對(duì)編譯原理要求較高。其實(shí)這些現(xiàn)在都有工具可以幫助搞定了,借助工具,我們可以直接進(jìn)行最后一步,對(duì)AST的修改。
市面上JavaScript詞法和文法分析器有很多,比如其實(shí)v8就是一個(gè),還有mozilla的SpiderMonkey, 知名的esprima等等,我這里要推薦的是uglify,一個(gè)基于nodejs的解析器。它具有以下功能:
- parser,把 JavaScript 代碼解析成抽象語(yǔ)法樹(shù) code generator,通過(guò)抽象語(yǔ)法樹(shù)生成代碼
- scope analyzer,分析變量定義的工具
- tree walker,遍歷樹(shù)節(jié)點(diǎn)
- tree transformer,改變樹(shù)節(jié)點(diǎn)
對(duì)比下我上面給出的混淆器設(shè)計(jì)的圖,發(fā)現(xiàn)其實(shí)只需要修改語(yǔ)法樹(shù) 這一步自己完成。
實(shí)例
說(shuō)了這么多,可能很多人還是一頭霧水,為了幫助各位理解,我準(zhǔn)備了一個(gè)簡(jiǎn)單的例子,假設(shè)我們的混淆規(guī)則是想把 var a = 1; 中的數(shù)字1換成16進(jìn)制,我們?cè)撊绾卧O(shè)計(jì)混淆器呢。首先對(duì)源代碼做詞法分析和語(yǔ)法分析,uglify一個(gè)方法就搞定了,生成一顆語(yǔ)法樹(shù),我們需要做的就是找到語(yǔ)法樹(shù)中的數(shù)字然后修改成16進(jìn)制的結(jié)果,如下圖所示:
實(shí)例代碼:
- var UglifyJS = require("uglify-js");
- var code = "var a = 1;";
- var toplevel = UglifyJS.parse(code); //toplevel就是語(yǔ)法樹(shù)
- var transformer = new UglifyJS.TreeTransformer(function (node) {
- if (node instanceof UglifyJS.AST_Number) { //查找需要修改的葉子節(jié)點(diǎn)
- node.value = '0x' + Number(node.value).toString(16);
- return node; //返回一個(gè)新的葉子節(jié)點(diǎn) 替換原來(lái)的葉子節(jié)點(diǎn)
- };
- });
- toplevel.transform(transformer); //遍歷AST樹(shù)
- var ncode = toplevel.print_to_string(); //從AST還原成字符串
- console.log(ncode); // var a = 0x1;
上面的代碼很簡(jiǎn)單,首先通過(guò)parse方法構(gòu)建語(yǔ)法樹(shù),然后通過(guò)TreeTransformer遍歷語(yǔ)法樹(shù),當(dāng)遇到節(jié)點(diǎn)屬于UglifyJS.AST_Number類型(所有的AST類型見(jiàn)ast),這個(gè)token具有一個(gè)屬性 value 保存著數(shù)字類型的具體值,我們將其改成16進(jìn)制表示,然后 return node 就會(huì)用新的節(jié)點(diǎn)代替原來(lái)的節(jié)點(diǎn)。
效果展示
貼一個(gè)我自己設(shè)計(jì)的混淆器混淆前后代碼:
4、混淆對(duì)性能的影響
由于增加了廢代碼,改變了原有的AST,混淆對(duì)性能肯定會(huì)造成一定的影響,但是我們可以通過(guò)規(guī)則來(lái)控制影響的大小。
· 減少循環(huán)混淆,循環(huán)太多會(huì)直接影響代碼執(zhí)行效率
· 避免過(guò)多的字符串拼接,因?yàn)樽址唇釉诘桶姹綢E下面會(huì)有性能問(wèn)題
· 控制代碼體積,在插入廢代碼時(shí)應(yīng)該控制插入比例,文件過(guò)大會(huì)給網(wǎng)絡(luò)請(qǐng)求和代碼執(zhí)行都帶來(lái)壓力
我們通過(guò)一定的規(guī)則完全可以把性能影響控制在一個(gè)合理的范圍內(nèi),實(shí)際上,有一些混淆規(guī)則反而會(huì)加快代碼的執(zhí)行,比如變量名和屬性名的壓縮混淆,會(huì)減小文件體積,比如對(duì)全局變量的復(fù)制,會(huì)減少作用域的查找等等。在現(xiàn)代瀏覽器中,混淆對(duì)代碼的影響越來(lái)越小,我們只需要注意合理的混淆規(guī)則,完全可以放心的使用混淆。
5、混淆的安全性
混淆的目的是保護(hù)代碼,但是如果因?yàn)榛煜绊懥苏9δ芫蜕岜局鹉┝恕?/p>
由于混淆后的AST已經(jīng)和原AST完全不同了,但是混淆后文件的和原文件執(zhí)行結(jié)果必須一樣,如何保證既兼顧了混淆強(qiáng)度,又不破壞代碼執(zhí)行呢?高覆蓋的測(cè)試必不可少:
· 對(duì)自己的混淆器寫(xiě)詳盡的單元測(cè)試
· 對(duì)混淆的目標(biāo)代碼做高覆蓋的功能測(cè)試,保證混淆前后代碼執(zhí)行結(jié)果完全一樣
· 多樣本測(cè)試,可以混淆單元測(cè)試已經(jīng)完備了的類庫(kù),比如混淆 Jquery 、AngularJS 等,然后拿混淆后的代碼去跑它們的單元測(cè)試,保證和混淆前執(zhí)行結(jié)果完全一樣
三、總結(jié)
· 可信web系統(tǒng)是我們的愿景
· 可信web系統(tǒng)離不開(kāi)可信的前端環(huán)境
· js混淆在對(duì)抗中必不可少
· 實(shí)現(xiàn)一款自己的混淆器并沒(méi)有那么難
· 混淆器對(duì)性能的影響是可控的
四、 參考
https://en.wikipedia.org/wiki/Trusted_Platform_Module
https://en.wikipedia.org/wiki/Trusted_system
http://lisperator.net/uglifyjs
http://esprima.org
文章標(biāo)題:可信前端之路-代碼保護(hù)
當(dāng)前URL:http://m.fisionsoft.com.cn/article/dpipcch.html


咨詢
建站咨詢
