在瀏覽器中攻破JavaScript到底有多輕松?
譯文如何挫敗攻擊者的入侵企圖。
Jesus Rodriguez發問:
我的問題涉及JavaScript安全性。
請大家想象一下,如果您正在某種認證系統中使用JavaScript框架(例如Backbone或者AngularJS),而且需要端點驗證機制以保障安全。這樣的情景似乎沒什么難度,畢竟決定權始終掌握在服務器手中、而且在我們進行任何操作前都會通過驗證檢查其是否符合規定。
但是如果整個流程不涉及服務器,對安全性的要求還能不能成為現實?
舉例來說,如果您擁有一套客戶端路由系統而且打算通過保護具體線路來鞏固用戶登錄的安全性。在這種情況下,大家需要首先向服務器發送ping指令來查詢自己是否有權訪問受保護的線路,接著再執行后續操作。問題在于,當我們ping向服務器時,系統會將服務器響應結果保存為一個變量。這樣在下一次使用這條專有線路時,系統會自動檢查您是否已經登錄(而不再重復ping向服務器),而僅僅通過上一次響應結果決定您的申請是否通過。
聽起來似乎有機可乘,那么通過修改變量來達成訪問到底可不可行?
坦白講,我對于安全及JavaScript方面的知識了解不太多,但這樣變量只存在于模塊模式中的專有部分而非全局環境當中。這里只涉及獲取方而無關于設定方,如果看來,我們似乎有機會針對其加以破解。
發送機密數據
Joachim Sauer的回答(獲得66票支持):
破解過程非常簡單:任何依賴于客戶端的安全機制都會按我們的要求進行操作,因此只要攻擊者獲得控制權、接下來的入侵任務將手到擒來。
大家可以在客戶端上進行安全檢查,但檢查的對象實際上只涉及“緩存”內容(這是為了避免與服務器之間的高昂往返成本,如果客戶端已經了解到響應內容為‘否’,那么直接從緩存內讀取結果即可、無需再與服務器進行通信)。
如果大家希望保障一組用戶的安全,請確保這些用戶的客戶端永遠無法獲取服務器響應信息。因為一旦大家向客戶端發送“機密數據”,那么即使是加上了“請不要顯示其內容”的指示,攻擊者也能夠輕松通過禁用對應代碼對請求內容進行檢查。
如大家所見,這項答案并沒有真正提到任何與JavaScript及瀏覽器有關的細節。這是因為無論您所使用的是哪一種客戶端,其基本概念都是相通的。事實上,無論是胖客戶端(即傳統客戶端-服務器應用)、傳統的Web應用程序還是承載著大量客戶端JavaScript信息的單頁面應用,都適用于前面所提到的假設。
一旦數據離開服務器端,大家必須做好攻擊者有能力對其進行全面訪問的準備。
另辟蹊徑
Benjamin Gruenbaum的回答(獲得17票支持):
在首先閱讀Joachim的答案,然后再轉到這里。前一條答案涵蓋了客戶端安全漏洞的一般性出現原因,現在我們一起來看Benjamin對于解決這項問題的建議。
這是一項無需手動將每條請求發送至服務器端進行驗證的客戶端-服務器通信安全規劃:
大家仍然允許服務器擁有掌控權,服務器也仍然對客戶端發來的請求進行驗證,但整個過程都以透明化形式進行。
假設通過HTTPS協議防止MITM(即中間人)攻擊。
- 客戶端與服務器間進行首次握手時,服務器會針對客戶端生成一個公共密鑰,同時生成一個私有密鑰,從而構成一套非對稱式加密方案。客戶端在本地保存的是服務器的“公共”密鑰,并利用一條不會保存在任何位置的安全密碼進行加密。
- 客戶端現在已經離線。客戶端希望執行受信操作。客戶端通過輸入密碼以獲取服務器的公共密鑰。
- 客戶端現在會根據對數據的了解執行操作,并在服務器對應公共密鑰的輔助下對每項操作進行加密。
- 當客戶端處于在線狀態時,客戶端會向受公共密鑰加密的服務器發送自身ID以及所有相關操作。
- 服務器負責對操作進行加密,如果加密格式無誤,則信任這些操作確實來自客戶端。
備注:
- 我們絕不能將客戶端的密碼保存在任何位置,否則攻擊者將有機會取得密鑰并實施自己的惡意操作。這套方案的安全性依賴于服務器為客戶端所生成的密鑰的完整性。當需要使用該密鑰時,服務器仍然會對客戶端進行驗證。
- 我們實際上仍然需要仰仗服務器的安全機制而非客戶端,客戶端執行的每項操作都需要經過服務器的驗證。
- 我們可以在Web Workers中運行外部腳本。請注意,每一條JSONP請求如今都有可能引發嚴重的安全問題。我們需要不計一切代價保護密鑰。一旦密鑰丟失,攻擊者可以輕而易舉地偽裝成用戶并實施惡意活動。
- 這樣的處理方式符合“不ping向服務器”的要求。攻擊者無法簡單利用偽造數據模仿HTTP請求,只要密鑰沒有泄露、一切惡意活動都不可能實現。
- Joachim的回答仍然正確,我們事實上仍然將所有驗證工作留給了服務器。惟一的區別在于,現在我們不必在每次操作時都要求服務器驗證密碼。大家只需要在提交內容或者更新數據時才直接面向服務器。我們所做的一切工作都是為了在客戶端保存受信密鑰,并且保證客戶端重新驗證的順利進行。
- 對于單頁面應用(例如Angular)而言,這是一套相當通行的控制方案。
- 我之所以將服務器的公共密鑰稱為“公共”,是因為RSA等方案使用過同樣的形容字眼,而且意味著該密鑰屬于敏感信息、應該受到嚴格保護。
- 我不會把密碼保存在任何存儲機制當中。我需要確保用戶每一次運行離線代碼時都必須提交“離線”密碼。
- •不要使用自己的加密機制:請務必使用知名度較高的可靠加密庫,例如斯坦福大學的認證機制。
請認真閱讀并掌握這套推薦方案。在大家將此認證機制推向實際操作之前,請先向安全專家咨詢其可行性及潛在影響。這項問題非常嚴重,實施過程并不輕松而且很容易在執行中出現錯誤。
最關鍵的一點在于,千萬不要讓其它腳本介入到頁面當中,這意味著我們只能通過Web Workers引入外部腳本。我們絕不能輕信其它外部腳本,因為其中很可能潛伏著可能在用戶登陸時截獲密碼的惡意內容。
如果大家無法完全肯定腳本安全性或者推遲其執行(也就是說,該腳本不應該成為事件的訪問對象,而只作為同步代碼),請務必向用戶發出提示而且不要使用內嵌密碼字段。另外,不要把密碼內容保存在變量當中——再次強調,只有百分之百確信用戶的計算機不存在安全漏洞后才能這么做(當然,這又會涉及到服務器對客戶端的驗證)。
我還是要再次提醒各位,我們要以懷疑的態度審視客戶端,這一點在Joachim的回答中也已經表達得非常清楚。在新機制下,我們只不過可以在工作開始之前擺脫ping服務器這一步驟。
任何人都不可信
nvoigt的回答(獲得7票支持):
游戲行業有一句名言:“客戶端掌握在敵人手中”。任何運行在外部安全區域(例如服務器)中的代碼都有遭到侵襲的可能。要不要真正運行我們提供的“安全代碼”完全由客戶端決定,用戶只能在有限的范圍內做出選擇。不過在本地代碼方面,我們至少可以通過自動混淆機制保護代碼內容;也就是說,必須是擁有出色編程能力的攻擊者才有可能跨過這道障礙,從混淆機制的重重迷霧下看清JS與純文本信息的真面目。
攻擊活動所需要的準備工作非常簡明:一臺代理服務器外加一個文本編輯器,基本上已經足夠了。誠然,攻擊者需要對編程擁有相當程度的了解,但利用文本編輯器修改腳本內容可比直接編寫一套可以執行的注入代碼簡單得多。