在信息安全領域,可信系統(Trusted system)是一個讓人心動的目標,它指的是一個經過實施特定的安全策略而達到必定可信程度的系統。前端
在計算機中,可信平臺模塊(Trusted Platform Module,TPM)已經投入使用,它符合可信賴計算組織(Trusted Computing Group,TCG)制定的TPM規範,是爲了實現可信系統目標的而打造的一款安全芯片。做爲可信系統的信任根,TPM是可信計算的核心模塊,爲計算機安全提供了強有力的保障。node
而在咱們的web系統中,想打造一個可信系統彷佛是個僞命題,「永遠不要相信客戶端的輸入」是基本的安全準則。實際上,在可信系統中的可信也並非說真的是絕對安全,維基上對其的解釋爲:「可信的」(Trusted)未必意味着對用戶而言是「值得信賴的」(Trustworthy)。確切而言,它意味着能夠充分相信其行爲會更全面地遵循設計,而執行設計者和軟件編寫者所禁止的行爲的機率很低。web
從這個角度講,咱們把其當作一個美好的願景,咱們但願可以構造一個web系統中的TPM,能夠把惡意行爲控制在必定的機率以內,從而實現一個相對可信的web系統。chrome
在可信系統中,TPM的一個重要做用就是鑑別消息來源的真實性,保障終端的可信。在web系統中,咱們的消息來源就是用戶。隨着撞庫、惡意註冊、薅羊毛等產業的蓬勃發展,在愈來愈多的場景咱們須要鑑別請求數據是否來自真實的用戶,保護真實用戶的數據安全。後端
因此想要構造一個web系統中的TPM,首要問題就是須要保證輸入數據安全,打造一個相對可信的前端環境。可是因爲web的開放特性,前端做爲數據採集的最前線,js代碼始終暴露在外,在這種狀況下,防止惡意僞造請求變得很是困難,可信前端也就成了無稽之談。數組
在反覆對抗中,代碼保護也就是一般意義上的js代碼混淆的重要性逐漸彰顯出來。今天我就想和你們聊一聊js混淆的問題。瀏覽器
顯而易見,是爲了保護咱們的前端代碼邏輯。安全
在web系統發展早期,js在web系統中承擔的職責並很少,只是簡單的提交表單,js文件很是簡單,也不須要任何的保護。網絡
隨着js文件體積的增大,爲了縮小js體積,加快http傳輸速度,開始出現了不少對js的壓縮工具,好比 uglify、compressor、clouser。。。它們的工做主要是ide
合併多個js文件
去除js代碼裏面的空格和換行
壓縮js裏面的變量名
剔除掉註釋
【壓縮後的代碼】
雖然壓縮工具出發點都是爲了減小js文件的體積,可是人們發現壓縮替換後的代碼已經比源代碼可讀性差了不少,間接起到了代碼保護的做用,因而壓縮js文件成爲了前端發佈的標配之一。可是後來市面上主流瀏覽器chrome、Firefox等都提供了js格式化的功能,可以很快的把壓縮後的js美化,再加上現代瀏覽器強大的debug功能,單純壓縮過的js代碼對於真正懷有惡意的人,已經不能起到很好的防護工做,出現了"防君子不防小人"的尷尬局面。
【chrome開發者工具格式化以後的代碼】
而在web應用愈來愈豐富的今天,伴隨着瀏覽器性能和網速的提升,js承載了更多的工做,很多後端邏輯都在向前端轉移,與此同時也讓更多的不法分子有隙可乘。在web模型中,js每每是不法分子的第一個突破口。知曉了前端邏輯,不法分子能夠模擬成一個正常的用戶來實施本身的惡意行爲。因此,在不少登陸、註冊、支付、交易等等頁面中,關鍵業務和風控系統依賴的js都不但願被人輕易的破解,js混淆應運而生。
這是一個老生常談的問題。實際上,代碼混淆早就不是一個新鮮的名詞,在桌面軟件時代,大多數的軟件都會進行代碼混淆、加殼等手段來保護本身的代碼。Java和.NET都有對應的混淆器。黑客們對這個固然也不陌生,許多病毒程序爲了反查殺,也會進行高度的混淆。只不過因爲js是動態腳本語言,在http中傳輸的就是源代碼,逆向起來要比打包編譯後的軟件簡單不少,不少人所以以爲混淆是畫蛇添足。
【.NET混淆器dotFuscator】
其實正是由於js傳輸的就是源代碼,咱們才須要進行混淆,暴露在外的代碼沒有絕對的安全,可是在對抗中,精心設計的混淆代碼可以給破壞者帶來不小的麻煩,也可以爲防守者爭取更多的時間,相對於破解來講,混淆器規則的更替成本要小得多,在高強度的攻防中,能夠大大增長破解者的工做量,起到防護做用。從這個角度來說,關鍵代碼進行混淆是必不可少的步驟。
js混淆器大體有兩種:
經過正則替換實現的混淆器
經過語法樹替換實現的混淆器
第一種實現成本低,可是效果也通常,適合對混淆要求不高的場景。第二種實現成本較高,可是更靈活,並且更安全,更適合對抗場景,我這裏主要講一下第二種。基於語法層面的混淆器其實相似於編譯器,基本原理和編譯器相似,咱們先對編譯器作一些基本的介紹。
名詞解釋
token: 詞法單元,也有叫詞法記號的,詞法分析器的產物,文本流被分割後的最小單位。
AST: 抽象語法樹,語法分析器的產物,是源代碼的抽象語法結構的樹狀表現形式。
【編譯器VS混淆器】
編譯器工做流程
簡單的說,當咱們讀入一段字符串文本(source code),詞法分析器會把它拆成一個一個小的單位(token),好比數字1 是一個token, 字符串'abc'是一個token等等。接下來語法分析器會把這些單位組成一顆樹狀結構(AST),這個樹狀結構就表明了token們的組成關係。好比 1 + 2 就會展現成一棵加法樹,左右子節點分別是token - 1 和token - 2 ,中間token表示加法。編譯器根據生成的AST轉換到中間代碼,最終轉換成機器代碼。
對編譯器更多細節感興趣的同窗能夠移步龍書:編譯原理
混淆器工做流程
編譯器須要把源代碼編譯成中間代碼或者機器碼,而咱們的混淆器輸出其實仍是js。因此咱們從語法分析以後往下的步驟並不須要。想一想咱們的目標是什麼,是修改原有的js代碼結構,在這裏面這個結構對應的是什麼呢?就是AST。任何一段正確的js代碼必定能夠組成一顆AST,一樣,由於AST表示了各個token的邏輯關係,咱們也能夠經過AST反過來生成一段js代碼。因此,你只須要構造出一顆AST,就能生成任何js代碼!混淆過程如上右圖所示
經過修改AST生成一個新的AST,新的AST就能夠對應新的JavaScript代碼。
規則設計
知道了大體的混淆流程,最重要的環節就是設計規則。咱們上面說了,咱們須要生成新的AST結構意味着會生成和源代碼不同的js代碼,可是咱們的混淆是不能破壞原有代碼的執行結果的,因此混淆規則必須保證是在不破壞代碼執行結果的狀況下,讓代碼變得更難以閱讀。
具體的混淆規則各位能夠自行根據需求設計,好比拆分字符串、拆分數組,增長廢代碼等等。
參考:提供商業混淆服務的 jscramble 的混淆規則
實現
不少人看到這裏就望而卻步,由於詞法分析和文法分析對編譯原理要求較高。其實這些如今都有工具能夠幫助搞定了,藉助工具,咱們能夠直接進行最後一步,對AST的修改。
市面上JavaScript詞法和文法分析器有不少,好比其實v8就是一個,還有mozilla的 SpiderMonkey , 知名的 esprima 等等,我這裏要推薦的是 uglify,一個基於nodejs的解析器。它具備如下功能:
parser,把 JavaScript 代碼解析成抽象語法樹
code generator,經過抽象語法樹生成代碼
scope analyzer,分析變量定義的工具
tree walker,遍歷樹節點
tree transformer,改變樹節點
對比下我上面給出的混淆器設計的圖,發現其實只須要修改語法樹 這一步本身完成。
實例
說了這麼多,可能不少人仍是一頭霧水,爲了幫助各位理解,我準備了一個簡單的例子,假設咱們的混淆規則是想把 var a = 1; 中的數字1換成16進制,咱們該如何設計混淆器呢。首先對源代碼作詞法分析和語法分析,uglify一個方法就搞定了,生成一顆語法樹,咱們須要作的就是找到語法樹中的數字而後修改爲16進制的結果,以下圖所示:
實例代碼:
var UglifyJS = require("uglify-js"); var code = "var a = 1;"; var toplevel = UglifyJS.parse(code); //toplevel就是語法樹 var transformer = new UglifyJS.TreeTransformer(function (node) { if (node instanceof UglifyJS.AST_Number) { //查找須要修改的葉子節點 node.value = '0x' + Number(node.value).toString(16); return node; //返回一個新的葉子節點 替換原來的葉子節點 }; }); toplevel.transform(transformer); //遍歷AST樹 var ncode = toplevel.print_to_string(); //從AST還原成字符串 console.log(ncode); // var a = 0x1;
上面的代碼很簡單,首先經過parse方法構建語法樹,而後經過TreeTransformer遍歷語法樹,當遇到節點屬於UglifyJS.AST_Number類型(全部的AST類型見 ast),這個token具備一個屬性 value 保存着數字類型的具體值,咱們將其改爲16進製表示,而後 return node 就會用新的節點代替原來的節點。
效果展現
貼一個我本身設計的混淆器混淆先後代碼:
因爲增長了廢代碼,改變了原有的AST,混淆對性能確定會形成必定的影響,可是咱們能夠經過規則來控制影響的大小。
減小循環混淆,循環太多會直接影響代碼執行效率
避免過多的字符串拼接,由於字符串拼接在低版本IE下面會有性能問題
控制代碼體積,在插入廢代碼時應該控制插入比例,文件過大會給網絡請求和代碼執行都帶來壓力
咱們經過必定的規則徹底能夠把性能影響控制在一個合理的範圍內,實際上,有一些混淆規則反而會加快代碼的執行,好比變量名和屬性名的壓縮混淆,會減少文件體積,好比對全局變量的複製,會減小做用域的查找等等。在現代瀏覽器中,混淆對代碼的影響愈來愈小,咱們只須要注意合理的混淆規則,徹底能夠放心的使用混淆。
混淆的目的是保護代碼,可是若是由於混淆影響了正常功能就捨本逐末了。
因爲混淆後的AST已經和原AST徹底不一樣了,可是混淆後文件的和原文件執行結果必須同樣,如何保證既兼顧了混淆強度,又不破壞代碼執行呢?高覆蓋的測試必不可少:
對本身的混淆器寫詳盡的單元測試
對混淆的目標代碼作高覆蓋的功能測試,保證混淆先後代碼執行結果徹底同樣
多樣本測試,能夠混淆單元測試已經完備了的類庫,好比混淆 Jquery 、AngularJS 等,而後拿混淆後的代碼去跑它們的單元測試,保證和混淆前執行結果徹底同樣
可信web系統是咱們的願景
可信web系統離不開可信的前端環境
js混淆在對抗中必不可少
實現一款本身的混淆器並無那麼難
混淆器對性能的影響是可控的
做者:莫念@阿里安全,更多安全類文章,請訪問阿里聚安全博客