從跨域引發的Web安全思考?

熊貓7發表於2019-04-15

面試時常會出現跨域的解決方式,那麼從為什麼會產生跨域思考,就會有很多相關的知識跳出來了。也會幫助把以前不相關的知識點串聯起來。

web 瀏覽器中包含javascript直譯器,也就是說,一旦載入Web頁面,就可以讓任意的JavaScript程式碼在計算機裡執行。這就造成了很大的安全隱患。攻擊者可能會讀取或修改資料、盜取隱私等。

基礎安全知識

XSS (Cross Site Scripting, 跨指令碼攻擊)、SQL隱碼攻擊和CSRF(Cross-site Resquest Forgery,跨站請求偽造)

XSS

攻擊者向目標web站點注入HTML片段或指令碼,通常是由於帶有頁面可解析內容的資料未經處理直接插入到頁面上解析導致的。

XSS 分為:儲存型XSS、反射性XSS、MXSS(也叫 DOM XSS)三種。

  • 儲存型XSS: 常常是前端提交的資料未經過處理直接儲存到資料庫然後從資料庫讀取出來,直接插入到頁面中所導致的。
  • 反射性XSS: 可能是網頁中的URL引數中注入了可解析內容的資料而導致的,如果直接獲取URL中不合法的引數並且插入頁面中,可能出現頁面上的XSS攻擊。
  • MXSS:在渲染DOM屬性的時候將攻擊指令碼插入DOM屬性中被解析而導致的。

XSS的主要方法是驗證輸入到頁面上所有內容來源資料是否安全,如果可能含有指令碼標籤等內容必須要對使用者輸入的資料進行處理:

  • 將重要的cookie標記為http only, Javascript 中的document.cookie語句就不能獲取到cookie了
  • 只允許使用者輸入我們期望的資料。 例如: 年齡的textbox中,只允許使用者輸入數字。 而數字之外的字元都過濾掉。
  • 對資料進行Html Encode 處理。
  • 過濾或移除特殊的Html標籤。

SQL隱碼攻擊

頁面提交資料到服務端後,在伺服器端未進行資料驗證就將資料直接拼接到SQL語言中執行,因此產生執行與預期不用的現象。

主要防範措施就是對前端網頁提交的資料進行嚴格的檢查校驗。

CSRF

非源站點按照源站點的資料請求格式提交非法資料給源站點服務的一種攻擊方法。

網路請求劫持

網路請求劫持一般指網站資源請求過程中,因為人為的攻擊導致沒有載入到預期的資源內容。

網路請求劫持主要分為2種: DNS劫持和HTTP劫持。

DNS劫持

攻擊者劫持了DNS伺服器,且取得了某域名的解析記錄控制權,進而修改了此域名的解析結果,導致使用者對該域名地址的訪問由原IP地址轉入到修改後的制定IP地址。

DNS劫持的結果就是訪問www.a.com,出現的確實www.b.com,因為DNS伺服器www.a.com域名解析結果指向的是www.b.com網站指向的IP地址。

HTTP劫持

在使用者瀏覽器與訪問的目的伺服器之間所建立的網路資料傳輸通道中從閘道器或防火牆層監視特定的資料資訊,當滿足一定條件時,就會在正常的資料包中插入或修改成為攻擊者設計的網路資料包。

HTTP 劫持的目的是讓使用者瀏覽器解釋“錯誤”的資料,或者彈出新視窗的形式在使用者瀏覽器介面上展示宣傳性廣告或者直接顯示模組其它的內容。

這種情況一般使用者請求源網站的IP地址和指令碼都是正確的,但是在網站請求返回過程中,可能被ISP(網際網路服務提供商)劫持修改,最終在瀏覽器頁面上新增顯示一些廣告內容等。

請求劫持的唯一可行預防就是儘量使用 https 協議來訪問目標網站。

web 安全設定

設定 Header

通過某些特定的head頭配置,就可以完成瀏覽器前端的安全設定。下面是幾個典型的安全訊息頭設定:

X-XSS-Protection,啟用 XSS 過濾

這個head頭設定主要是用來防止瀏覽器中的反射性 XSS 攻擊。

語法如下:

X-XSS-Protection: 0;  // 禁止XSS過濾
X-XSS-Protection: 1;  // 啟用XSS過濾

// 啟用XSS過濾。 如果檢測到攻擊,瀏覽器將不會清除頁面,而是阻止頁面載入
X-XSS-Protection: 1; mode=block 

// 啟用XSS過濾。 如果檢測到跨站指令碼攻擊,瀏覽器將清除頁面並使用CSP report-uri指令的功能傳送違規報告。
X-XSS-Protection: 1; report=<reporting-uri>  
複製程式碼

Strict-Transport-Security

Strict-Transport-Security(STS)是一種用來配置瀏覽器和伺服器之間安全通訊的機制,主要用來防止中間者攻擊,因為它強制所有的通訊都使用HTTPs,在普通的HTTP報文請求中配置STS是沒有作用的,而且攻擊者也可以改變這些值。

Access-Control-Allow-Origin

這個設定決定哪些網站可以訪問當前伺服器資源。 一般用作跨域共享設定的一種實現方式。

HTTPS

對稱加密

缺點:加密和解謎是同一個金鑰。每個傳輸物件的金鑰都不同,如何傳遞金鑰是個問題。

從跨域引發的Web安全思考?

RSA:非對稱加密

RSA的金鑰分為:公鑰和私鑰。 用公鑰加密的資料只有對應的私鑰才能解密;用私鑰加密的資料只有對應的公鑰加密。

從跨域引發的Web安全思考?

缺點:RSA演算法加密時間長,對稱機密時間短

非對稱加密 + 對稱加密

非對稱加密傳輸對稱加密的金鑰,之後使用對稱加密傳輸。 解決了非對稱加密時間過久和對稱加密的金鑰傳輸問題。

缺點: 中間人劫持

中間人劫持

在公鑰傳送的過程中,中間人劫持了A傳送的公鑰,將自己的公鑰傳送給了B,這樣就完全劫持了A和B的傳輸。

數字證照

個人將個人的公鑰和個人資訊等其它資訊使用hash演算法生成一個訊息摘要

訊息摘要用CA(公信力的認證中心)的私鑰對訊息加密,形成數字簽名

數字簽名個人的公鑰和個人資訊等其它資訊這個原始資訊合併,形成全新的東西,叫做數字證照

瀏覽器一般內建了根證照。

HTTPS原理

從跨域引發的Web安全思考?

同源策略

同源策略限制了從同一個源載入的文件或指令碼如何與來自另一個源的資源進行互動。這是一個用於隔離潛在惡意檔案的重要安全機制。

跨域問題

同源策略限制了惡意檔案的攻擊,但是也導致我們某些特殊的需求需要解決跨域問題。

目前常用的解決方案有以下幾種:

  • JSONP
  • CROS
  • Nginx
  • ...

session 和 cookie

Cookie和Session是為了在無狀態的HTTP協議之上維護會話狀態,使得伺服器可以知道當前是和哪個客戶在打交道。

因為HTTP協議是無狀態的,即每次使用者請求到達伺服器時,HTTP伺服器並不知道這個使用者是誰、是否登入過等。現在的伺服器之所以知道我們是否已經登入,是因為伺服器在登入時設定了瀏覽器的Cookie!Session則是藉由Cookie而實現的更高層的伺服器與瀏覽器之間的會話。

cookie

cookie的機制

從跨域引發的Web安全思考?

cookie的安全隱患

Cookie提供了一種手段使得HTTP請求可以附加當前狀態, 現今的網站也是靠Cookie來標識使用者的登入狀態的:

  • 使用者提交使用者名稱和密碼的表單,這通常是一個POST HTTP請求。
  • 伺服器驗證使用者名稱與密碼,如果合法則返回200(OK)並設定Set-Cookie為authed=true。
  • 瀏覽器儲存該Cookie。
  • 瀏覽器傳送請求時,設定Cookie欄位為authed=true。
  • 伺服器收到第二次請求,從Cookie欄位得知該使用者已經登入。 按照已登入使用者的許可權來處理此次請求。

這裡面的問題在哪裡?

我們知道可以傳送HTTP請求的不只是瀏覽器,很多HTTP客戶端軟體(包括curl、Node.js)都可以傳送任意的HTTP請求,可以設定任何頭欄位。 假如我們直接設定Cookie欄位為authed=true併傳送該HTTP請求, 伺服器豈不是被欺騙了?這種攻擊非常容易,Cookie是可以被篡改的!

cookie 的防篡改機制

伺服器可以為每個Cookie項生成簽名,由於使用者篡改Cookie後無法生成對應的簽名, 伺服器便可以得知使用者對Cookie進行了篡改。一個簡單的校驗過程可能是這樣的:

  • 在伺服器中配置一個不為人知的字串(我們叫它Secret),比如:x$sfz32。
  • 當伺服器需要設定Cookie時(比如authed=false),不僅設定authed的值為false, 在值的後面進一步設定一個簽名,最終設定的Cookie是authed=false|6hTiBl7lVpd1P。
  • 簽名6hTiBl7lVpd1P是這樣生成的:Hash('x$sfz32'+'false')。 要設定的值與Secret相加再取雜湊。
  • 使用者收到HTTP響應並發現頭欄位Set-Cookie: authed=false|6hTiBl7lVpd1P。
  • 使用者在傳送HTTP請求時,篡改了authed值,設定頭欄位Cookie: authed=true|???。 因為使用者不知道Secret,無法生成簽名,只能隨便填一個。
  • 伺服器收到HTTP請求,發現Cookie: authed=true|???。伺服器開始進行校驗: Hash('true'+'x$sfz32'),便會發現使用者提供的簽名不正確。 通過給Cookie新增簽名,使得伺服器得以知道Cookie被篡改。然而故事並未結束。

因為Cookie是明文傳輸的, 只要伺服器設定過一次authed=true|xxxx我不就知道true的簽名是xxxx了麼, 以後就可以用這個簽名來欺騙伺服器了。因此Cookie中最好不要放敏感資料。 一般來講Cookie中只會放一個Session Id,而Session儲存在伺服器端。

Session

Session的實現機制

Session 是儲存在伺服器端的,避免了在客戶端Cookie中儲存敏感資料。 Session 可以儲存在HTTP伺服器的記憶體中,也可以存在記憶體資料庫(如redis)中, 對於重量級的應用甚至可以儲存在資料庫中。

我們以儲存在redis中的Session為例,還是考察如何驗證使用者登入狀態的問題。

  • 使用者提交包含使用者名稱和密碼的表單,傳送HTTP請求。

  • 伺服器驗證使用者發來的使用者名稱密碼。

  • 如果正確則把當前使用者名稱(通常是使用者物件)儲存到redis中,並生成它在redis中的ID。

  • 這個ID稱為Session ID,通過Session ID可以從Redis中取出對應的使用者物件, 敏感資料(比如authed=true)都儲存在這個使用者物件中。

  • 設定Cookie為sessionId=xxxxxx|checksum併傳送HTTP響應, 仍然為每一項Cookie都設定簽名。

  • 使用者收到HTTP響應後,便看不到任何敏感資料了。在此後的請求中傳送該Cookie給伺服器。

  • 伺服器收到此後的HTTP請求後,發現Cookie中有SessionID,進行防篡改驗證。

  • 如果通過了驗證,根據該ID從Redis中取出對應的使用者物件, 檢視該物件的狀態並繼續執行業務邏輯。

  • Web應用框架都會實現上述過程,在Web應用中可以直接獲得當前使用者。 相當於在HTTP協議之上,通過Cookie實現了持久的會話。這個會話便稱為Session。

但基於cookie機制,其實也不是很安全,容易被 CSRF。

後續

web安全相關還有很多可以深入討論。歡迎大家指正和分享,一起進步。

參考資料

  1. 《現代前端技術解析》
  2. Cookie/Session的機制與安全
  3. 《碼農翻身》

相關文章