可信前端之路-代碼保護

2021-02-08 JavaScript
轉自:https://jaq.alibaba.com/community/art/show?spm=a313e.7916642.220000NaN1.1.gDygs9&articleid=5030x00 前言

在信息安全領域,可信系統(Trusted system)是一個讓人心動的目標,它指的是一個通過實施特定的安全策略而達到一定可信程度的系統。

在計算機中,可信平臺模塊(Trusted Platform Module,TPM)已經投入使用,它符合可信賴計算組織(Trusted Computing Group,TCG)制定的TPM規範,是為了實現可信系統目標的而打造的一款安全晶片。作為可信系統的信任根,TPM是可信計算的核心模塊,為計算機安全提供了強有力的保障。

而在我們的web系統中,想打造一個可信系統似乎是個偽命題,"永遠不要相信客戶端的輸入"是基本的安全準則。實際上,在可信系統中的可信也並不是說真的是絕對安全,維基上對其的解釋為:「可信的」(Trusted)未必意味著對用戶而言是「值得信賴的」(Trustworthy)。確切而言,它意味著可以充分相信其行為會更全面地遵循設計,而執行設計者和軟體編寫者所禁止的行為的概率很低。

從這個角度講,我們把其當做一個美好的願景,我們希望能夠構造一個web系統中的TPM,可以把惡意行為控制在一定的概率之內,從而實現一個相對可信的web系統。

0x01 可信前端

在可信系統中,TPM的一個重要作用就是鑑別消息來源的真實性,保障終端的可信。在web系統中,我們的消息來源就是用戶。隨著撞庫、惡意註冊、薅羊毛等產業的蓬勃發展,在越來越多的場景我們需要鑑別請求數據是否來自真實的用戶,保護真實用戶的數據安全。

所以想要構造一個web系統中的TPM,首要問題就是需要保證輸入數據安全,打造一個相對可信的前端環境。但是由於web的開放特性,前端作為數據採集的最前線,js代碼始終暴露在外,在這種情況下,防止惡意偽造請求變得非常困難,可信前端也就成了無稽之談。

在反覆對抗中,代碼保護也就是通常意義上的js代碼混淆的重要性逐漸彰顯出來。今天我就想和大家聊一聊js混淆的問題。

1、為什麼需要js混淆

顯而易見,是為了保護我們的前端代碼邏輯。

在web系統發展早期,js在web系統中承擔的職責並不多,只是簡單的提交表單,js文件非常簡單,也不需要任何的保護。

隨著js文件體積的增大,為了縮小js體積,加快http傳輸速度,開始出現了很多對js的壓縮工具,比如 uglify、compressor、clouser。。。它們的工作主要是

    · 合併多個js文件

    · 去除js代碼裡面的空格和換行

    · 壓縮js裡面的變量名

    · 剔除掉注釋

壓縮後的代碼

雖然壓縮工具出發點都是為了減少js文件的體積,但是人們發現壓縮替換後的代碼已經比原始碼可讀性差了很多,間接起到了代碼保護的作用,於是壓縮js文件成為了前端發布的標配之一。但是後來市面上主流瀏覽器chrome、Firefox等都提供了js格式化的功能,能夠很快的把壓縮後的js美化,再加上現代瀏覽器強大的debug功能,單純壓縮過的js代碼對於真正懷有惡意的人,已經不能起到很好的防禦工作,出現了"防君子不防小人"的尷尬局面。

chrome開發者工具格式化之後的代碼

而在web應用越來越豐富的今天,伴隨著瀏覽器性能和網速的提高,js承載了更多的工作,不少後端邏輯都在向前端轉移,與此同時也讓更多的不法分子有機可乘。在web模型中,js往往是不法分子的第一個突破口。知曉了前端邏輯,不法分子可以模擬成一個正常的用戶來實施自己的惡意行為。所以,在很多登錄、註冊、支付、交易等等頁面中,關鍵業務和風控系統依賴的js都不希望被人輕易的破解,js混淆應運而生。

2、js混淆是不是紙老虎

這是一個老生常談的問題。實際上,代碼混淆早就不是一個新鮮的名詞,在桌面軟體時代,大多數的軟體都會進行代碼混淆、加殼等手段來保護自己的代碼。Java和.NET都有對應的混淆器。黑客們對這個當然也不陌生,許多病毒程序為了反查殺,也會進行高度的混淆。只不過由於js是動態腳本語言,在http中傳輸的就是原始碼,逆向起來要比打包編譯後的軟體簡單很多,很多人因此覺得混淆是多此一舉。

.NET混淆器dotFuscator

其實正是因為js傳輸的就是原始碼,我們才需要進行混淆,暴露在外的代碼沒有絕對的安全,但是在對抗中,精心設計的混淆代碼能夠給破壞者帶來不小的麻煩,也能夠為防守者爭取更多的時間,相對於破解來說,混淆器規則的更替成本要小得多,在高強度的攻防中,可以大大增加破解者的工作量,起到防禦作用。從這個角度來講,關鍵代碼進行混淆是必不可少的步驟。

3、如何進行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 就會用新的節點代替原來的節點。

效果展示

貼一個我自己設計的混淆器混淆前後代碼:

4、混淆對性能的影響

由於增加了廢代碼,改變了原有的AST,混淆對性能肯定會造成一定的影響,但是我們可以通過規則來控制影響的大小。

    · 減少循環混淆,循環太多會直接影響代碼執行效率

    · 避免過多的字符串拼接,因為字符串拼接在低版本IE下面會有性能問題

    · 控制代碼體積,在插入廢代碼時應該控制插入比例,文件過大會給網絡請求和代碼執行都帶來壓力

我們通過一定的規則完全可以把性能影響控制在一個合理的範圍內,實際上,有一些混淆規則反而會加快代碼的執行,比如變量名和屬性名的壓縮混淆,會減小文件體積,比如對全局變量的複製,會減少作用域的查找等等。在現代瀏覽器中,混淆對代碼的影響越來越小,我們只需要注意合理的混淆規則,完全可以放心的使用混淆。

5、混淆的安全性

混淆的目的是保護代碼,但是如果因為混淆影響了正常功能就捨本逐末了。

由於混淆後的AST已經和原AST完全不同了,但是混淆後文件的和原文件執行結果必須一樣,如何保證既兼顧了混淆強度,又不破壞代碼執行呢?高覆蓋的測試必不可少:

    · 對自己的混淆器寫詳盡的單元測試

    · 對混淆的目標代碼做高覆蓋的功能測試,保證混淆前後代碼執行結果完全一樣

    · 多樣本測試,可以混淆單元測試已經完備了的類庫,比如混淆 Jquery 、AngularJS 等,然後拿混淆後的代碼去跑它們的單元測試,保證和混淆前執行結果完全一樣

0x02 總結

    · 可信web系統是我們的願景

    · 可信web系統離不開可信的前端環境

    · js混淆在對抗中必不可少

    · 實現一款自己的混淆器並沒有那麼難

    · 混淆器對性能的影響是可控的

END

掘金是一個高質量的技術社區,從 ECMAScript 6 到 Vue.js,網站性能優化到開源類庫,讓你不錯過 Web 開發的每一個技術乾貨。長按圖片二維碼識別或者各大應用市場搜索「掘金」,技術乾貨盡在掌握中。


點擊「閱讀原文」,下載掘金

相關焦點

  • 【第849期】如何讓前端更安全?——XSS攻擊和防禦詳解
    如果你認為這麼簡單,NO NO NO…因為瀏覽器解析中html和js編碼不一樣,以及上下文場景多樣,所以對於後臺輸出的變量,不同的上下文中渲染後端變量,轉碼不一樣。下面的HTML片段顯示了如何安全地在多種不同的上下文中渲染不可信數據。
  • 安全可信:Intel SGX和ARM TrustZone淺析
    因此它是一個可信版本的管理手段。安全啟動/可信啟動 是確保系統級別的安全手段,適用於有可信訴求的整機系統,終端設備如手機、STB、路由器等,當然也適用於通用伺服器設備。以上安全啟動和可信啟動的概念,網上以及公司W3有許多專家在討論,可以自行進一步理解。
  • 分享前端開發常用代碼片段
    四、懸停切換當用戶滑鼠懸停在可點擊的元素上時,可添加類到元素中,反之則移除類。如果沒有定義處理程序,其他的 jQuery 代碼或會就此罷工。定義一個全局的 Ajax 錯誤處理程序鏈式和高速緩存的方法都是 jQuery 中可以讓代碼變得更短和更快的最佳做法。
  • 分享 前端開發 常用代碼片段
    四、懸停切換當用戶滑鼠懸停在可點擊的元素上時,可添加類到元素中,反之則移除類。如果沒有定義處理程序,其他的 jQuery 代碼或會就此罷工。定義一個全局的 Ajax 錯誤處理程序鏈式和高速緩存的方法都是 jQuery 中可以讓代碼變得更短和更快的最佳做法。
  • 如何用前端防禦XSS及建立XSS報警機制
    雖然說前端防禦XSS比較麻煩,但是,不是一定不行。他只是寫的代碼比後端多了而已。而且前端防禦XSS比後端防禦XSS功能多,雖說後端也可以完成這些功能,但是代碼量會比前端代碼多很多很多。其實說了那麼多,交給nginx||apache||nodeJs||Python會更好處理。但是我不會C,也就沒辦法寫nginx模塊了。而且也不在本文章的範圍內,等我什麼時候學會C再說把。
  • 前端工程師的一大神器——puppeteer
    三、基本使用和常用功能該神器整體使用起來比較簡單,下面就開始我們的使用之路。3.1 啟動Browser核心函數就是異步調用puppeteer.launch()函數,根據相應的配置參數創建一個Browser實例。
  • 美軍如何在不可信設備上安全訪問國防部網絡?
    TENS(Trusted End Node Security,可信終端節點安全)是一個外部引導啟動、在內存運行的Linux定製系統,由美國空軍研究實驗室(AFRL)出品。它可以讓經授權的國防部或聯邦人員使用幾乎任何電腦遠程訪問軍方/政府網絡服務,最大程度降低不可信設備的安全風險。
  • 前端離線化探索
    比如,用戶點擊按鈕,前端更新數據狀態為成功,請求到達後臺,伺服器響應,更新前端數據。離線數據事實上,我們的大部分離線場景將是會在本地獨立 app 之中,藉助客戶端能力,我們可以把 web 代碼包提前內置到客戶端之中,然後使用一套代碼更新機制,前端代碼緩存問題可以得到解決。離線代碼加載和更新邏輯本身不複雜,下面是一個簡化圖,具體特定業務場景下還需要考慮比如是否灰度用戶,代碼版本和數據是否同步等問題。
  • 美國軍方可信終端節點安全(TENS)系統簡介
    不留運行痕跡,在非託管計算機的RAM中創建一個可信終端節點,當計算機關閉或重新啟動時,該節點消失。 每次加載時,都會將已知可信配置加載到內存中。 任何可能感染計算機的惡意軟體都只能存在於單次系統運行過程中。 通過定期重啟或在執行關鍵事務之前重啟系統,提高系統安全性。
  • 標準發布|可信數據服務 金融機構外部可信數據源評估標準
    2020年12月18日下午,中國信通院在北京召開2020年數據資產管理大會,中國信通院雲大所工程師、中國通信標準化協會TC601數據流通工作組副組長呂艾臨發布了《可信數據服務 金融機構外部可信數據源》的評估標準並進行了簡要的介紹。金融行業的核心競爭要素從資本、技術逐步轉變為數據,數據的融合應用已推動金融服務從被動式向主動式演變。
  • AST 代碼掃描實戰:如何保障代碼質量
    const parser = require('@babel/parser');const traverse = require('@babel/traverse').default;const visitor = {};const code = `var str = "hello world";`;const ast = parser.parse(code
  • 前端工程師必備技能匯總
    Git的歷史記錄也可以見證前端行業的一些變遷。儘管會變成文字的方式來維護這些內容,但是我承諾寫一個小工具幫大家生成更好玩的圖形(基於DataV項目)。前端開發知識結構●前端工程師●SVG/Canvas/VML●SVG: D3/Raphaël/Snap.svg/DataV●Canvas: CreateJS/KineticJS●知識管理/總結分享●溝通技巧/團隊協作●需求管理/PM●互動設計/
  • 從一個優質開源項目來看前端架構
    系統架構師負責設計系統整體架構,從需求到設計的每個細節都要考慮到,把握整個項目,使設計的項目儘量效率高,開發容易,維護方便,升級簡單等這是百度百科的答案大多數人的問題如何成為一名前端架構師?其實,前端架構師不應該是一個頭銜,而應該是一個過程。
  • 常用的幾款前端開發編輯器對比
    這次選擇了五款前端常用的編輯器(有的稱ide也好)進行測評(順序無關)。對著這樣的界面寫一天代碼,感受要比對著太亮或太暗的界面舒服很多。中文符號免幹擾。在編寫html、js、css代碼時,如果處於中文輸入法狀態,自動將必要的符號如:;。等轉為: ; .等。HBuilder JSDoc+規範。編寫JSDoc有助於代碼可讀性的提升,還可生成API手冊(主要是框架)。
  • 前端安全之 XSS 攻擊
    前端在顯示服務端數據時候,不僅是標籤內容需要過濾、轉義,就連屬性值也都可能需要。2. 後端接收請求時,驗證請求是否為攻擊請求,攻擊則屏蔽。PS:因為大部分文章是保存整個HTML內容的,前端顯示時候也不做過濾,就極可能出現這種情況。結論:後端儘可能對提交數據做過濾,在場景需求而不過濾的情況下,前端就需要做些處理了。開發安全措施:1. 首要是服務端要進行過濾,因為前端的校驗可以被繞過。
  • 前端安全之XSS攻擊
    前端在顯示服務端數據時候,不僅是標籤內容需要過濾、轉義,就連屬性值也都可能需要。2. 後端接收請求時,驗證請求是否為攻擊請求,攻擊則屏蔽。結論:後端儘可能對提交數據做過濾,在場景需求而不過濾的情況下,前端就需要做些處理了。開發安全措施:1. 首要是服務端要進行過濾,因為前端的校驗可以被繞過。2.
  • 可信代工|美國國防部提高與格芯(GlobalFoundries)可信代工合同的金額上限,保障晶片的安全供應
    雙方角色DMEA及其可信獲取項目辦公室(TAPO)本質上作為中間人的角色,以確保美國武器系統中使用的從先進到停產器件的穩定供應。 GlobalFoundries是美國軍方的長期晶片供應商,在佛蒙特州的伯靈頓(Fab 9)、紐約的東菲什基爾(Fab 10)和馬爾他(Fab 8)有可信代工廠。Fab 9和Fab10是在2015年從IBM收購。2020年5月,GlobalFoundries宣布將升級其Fab 8工廠,以符合美國對敏感技術的出口控制。
  • 透徹分析:常見的前端架構風格和案例
    針對不同的端去編寫多套代碼的成本非常高,這種需求催生了Taro這類框架的誕生. 使用 Taro,我們可以只書寫一套代碼, 通過編譯工具可以輸出到不同的端:,主要集中在前端工程化領域。微前端 前幾天聽了代碼時間上左耳朵耗子的一期節目, 他介紹得了亞馬遜內部有很多小團隊,亞馬遜網站上一塊豆腐塊大小的區域可能是一個團隊在維護,比如地址選擇器、購物車、運達時間計算... 大廠的這種超級項目是怎麼協調和維護的呢?這也許就是微前端或者微服務出現的原因吧。
  • 前端專題之npm包
    npm 出現之前:前端依賴項是保存到存儲庫中並手動下載的📁2012:npm 的使用量急劇增加——主要是由於 Browserifys 瀏覽器的支持🎉2012:npm 有了一個競爭對手 bower,它完全支持瀏覽器💻2012-2016:前端項目的依賴項數量成倍增加🤯2012-2016:構建和安裝前端應用變得越來越慢🐢2012-2016:大量(重複的)依賴項存儲在神奇的 node_modules
  • 前端中的 IoC 理念
    (給前端大全加星標,提升前端技能)作者:大板慄https://zhuanlan.zhihu.com/p/53832991