跨站請求偽造—CSRF

  • 2019 年 11 月 27 日
  • 筆記

CSRF 介紹

CSRF,是跨站請求偽造(Cross Site Request Forgery)的縮寫,是一種劫持受信任用戶向服務器發送非預期請求的攻擊方式。

通常情況下,CSRF 攻擊是攻擊者藉助受害者的 Cookie 騙取服務器的信任,在受害者毫不知情的情況下以受害者名義偽造請求發送給受攻擊服務器,從而在並未授權的情況下執行在權限保護之下的操作。

CSRF 攻擊示例

這裡有一個網站,用戶可以看文章,登錄之後可以發評論。

如果用戶是登錄狀態,打開了這樣的頁面,

<!DOCTYPE html>  <html lang="zh-CN">    <head>      <meta charset="UTF-8">      <title>csrf攻擊</title>      <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" name="viewport" />  </head>    <body style="display: none;">      <form target="myIframe" id="csrf" action="https://www.kkkk1000.com/csrf/data/post_comment.php" metdod="POST">          <input type="text" name="content" value="csrf攻擊" />      </form>        <!-- iframe 用來防止頁面跳轉 -->      <iframe id="myIframe" name="myIframe"></iframe>        <script>          var form = document.querySelector('#csrf');          form.submit();      </script>  </body>    </html>

就會自動在文章下發一條評論,這樣就算完成了一次 CSRF 攻擊。 當然,如果你把這個頁面放服務器上,然後做成一個鏈接,用戶點擊這個鏈接,同樣可以完成攻擊。

從圖中可以看出,右邊和左邊的頁面是在不同站點下的,用戶打開的右邊的空白頁,就偷偷提交了一條評論,刷新左邊的頁面也確實看到了剛剛提交的評論。

我們來看看,這次的攻擊是怎麼成功的。

首先我們要知道一些關於 Cookie 的知識。

HTTP Cookie(也叫 Web Cookie或瀏覽器 Cookie)是服務器發送到用戶瀏覽器並保存在本地的一小塊數據,它會在瀏覽器下次向同一服務器再發起請求時被攜帶並發送到服務器上。通常,它用於告知服務端兩個請求是否來自同一瀏覽器,如保持用戶的登錄狀態。Cookie 使基於無狀態的 HTTP 協議記錄穩定的狀態信息成為了可能。

好的,我們繼續往下說。

因為用戶在網站登錄後,這個網站服務端會在 Cookie 中會放一個憑證,這個憑證是後端用來驗證用戶身份的。 在發評論的時候,提交評論的請求會帶上這個憑證,後端通過判斷這個憑證,來確認是哪個用戶。

登錄時,設置 Cookie:

提交評論時,攜帶 Cookie:

我們再來看看,發起攻擊的頁面里的內容。

<body style="display: none;">      <form target="myIframe" id="csrf" action="https://www.kkkk1000.com/csrf/data/post_comment.php" metdod="POST">          <input type="text" name="content" value="csrf攻擊" />      </form>        <!-- iframe 用來防止頁面跳轉 -->      <iframe id="myIframe" name="myIframe"></iframe>        <script>          var form = document.querySelector('#csrf');          form.submit();      </script>  </body>

這些代碼的意思就是,當用戶點擊這個鏈接,會自動提交 form 表單,而這個表單就是用來提交評論的,提交評論請求需要的參數,在 form 表單中也都已經準備好了,如果用戶登錄過網站,Cookie 中存儲的用戶的憑證,會隨着請求一起傳到服務器端。所以服務器端就會認為這是用戶要提交一條評論。

CSRF 特點

  • 攻擊一般發起在第三方網站,而不是被攻擊的網站。
  • 攻擊是利用受害者在被攻擊網站的登錄憑證,冒充受害者提交操作,僅僅是「冒用」,而不是直接竊取數據。
  • 攻擊者預測出被攻擊的網站接口的所有參數,成功偽造請求。

防禦方法

SameSite 屬性

Cookie 的 SameSite 屬性用來限制第三方 Cookie,從而減少安全風險,可以用來防止 CSRF 攻擊和用戶追蹤。

它可以設置三個值。

  • Strict
  • Lax
  • None

Strict

Strict最為嚴格,完全禁止第三方 Cookie,跨站點時,任何情況下都不會發送 Cookie。換言之,只有當前網頁的 URL 與請求目標一致,才會帶上 Cookie。

Set-Cookie: CookieName=CookieValue; SameSite=Strict;

這個規則過於嚴格,可能造成非常不好的用戶體驗。比如,當前網頁有一個 GitHub 鏈接,用戶點擊跳轉就不會帶有 GitHub 的 Cookie,跳轉過去總是未登陸狀態。

Lax

Lax 規則稍稍放寬,大多數情況也是不發送第三方 Cookie,但是導航到目標網址的 Get 請求除外。

Set-Cookie: CookieName=CookieValue; SameSite=Lax;

Chrome 計劃將 Lax 變為默認設置。這時,網站可以選擇顯式關閉 SameSite 屬性,將其設為 None 。不過,前提是必須同時設置 Secure 屬性(Cookie 只能通過 HTTPS 協議發送),否則無效。

下面的設置無效:

Set-Cookie: widget_session=abc123; SameSite=None

下面的設置有效:

Set-Cookie: widget_session=abc123; SameSite=None; Secure

要使用 SameSite 屬性,前提是用戶瀏覽器支持 SameSite 屬性,可以使用 caniuse 查看瀏覽器對於 SameSite 屬性的支持。

同源檢測

在 HTTP 協議中,每一個異步請求都會攜帶兩個 Header ,用於標記來源域名:

  • Origin Header
  • Referer Header

這兩個 Header 在瀏覽器發起請求時,大多數情況會自動帶上,並且不能由前端自定義內容。 服務器可以通過解析這兩個 Header 中的域名,確定請求的來源域。

通過校驗請求的該字段,我們能知道請求是否是從本站發出的。我們可以通過拒絕非本站發出的請求,來避免了 CSRF 攻擊。

驗證 Origin

如果 Origin 存在,那麼直接使用 Origin 中的字段確認來源域名就可以。

但是 Origin 在以下兩種情況下並不存在:

  • 1、 IE11同源策略: IE 11 不會在跨站 CORS 請求上添加 Origin 頭,Referer 頭將仍然是唯一的標識。最根本原因是因為IE 11對同源的定義和其他瀏覽器有不同,有兩個主要的區別,可以參考 MDN Same-origin_policy#IE_Exceptions
  • 2、 302重定向: 在302重定向之後 Origin 不包含在重定向的請求中,因為 Origin 可能會被認為是其他來源的敏感信息。對於302重定向的情況來說都是定向到新的服務器上的 URL ,因此瀏覽器不想將 Origin 泄漏到新的服務器上。

驗證 Referer

如果 Referer 存在,也可以用來確認 HTTP 請求的來源地址。

需要注意的是在以下情況下 Referer 沒有或者不可信:

  • 1.IE6、7下使用window.kk=url進行界面的跳轉,會丟失 Referer。
  • 2.IE6、7下使用window.open,也會缺失 Referer。
  • 3.HTTPS 頁面跳轉到 HTTP 頁面,所有瀏覽器 Referer 都丟失。
  • 4.點擊 Flash 上到達另外一個網站的時候,Referer 的情況就比較雜亂,不太可信。

總的來說,同源檢測是一個相對簡單的防範方法,能夠防範絕大多數的 CSRF 攻擊,但這並不是萬無一失的,對於安全性要求較高,或者有較多用戶輸入內容的網站,我們就要對關鍵的接口做額外的防護措施。

驗證碼

驗證碼的種類有很多,比如圖形驗證碼,基於人機之間知識差異的驗證碼,行為式驗證碼。

CSRF 攻擊往往是在用戶不知情的情況下成功偽造請求。而驗證碼會強制用戶必須與應用進行交互,才能完成最終請求,而且因為 CSRF 攻擊無法獲取到驗證碼,因此通常情況下,驗證碼能夠很好地遏制 CSRF 攻擊。 但驗證碼並不是萬能的,因為出於用戶體驗考慮,不能給網站所有的操作都加上驗證碼。因此,驗證碼只能作為防禦 CSRF 的一種輔助手段,而不能作為最主要的解決方案。

添加 Token 驗證

CSRF 攻擊之所以能夠成功,是因為攻擊者可以完全偽造用戶的請求,該請求中所有的用戶驗證信息都是存在於 Cookie 中,因此攻擊者可以在不知道這些驗證信息的情況下直接利用用戶自己的 Cookie 來通過安全驗證。 要抵禦 CSRF,關鍵在於在請求中放入攻擊者所不能偽造的信息,並且該信息不存在於 Cookie 之中。可以在 HTTP 請求中以參數的形式加入一個隨機產生的 Token,並在服務器端建立一個攔截器來驗證這個 Token,如果請求中沒有 Token 或者 Token 內容不正確,則認為可能是 CSRF 攻擊而拒絕該請求。

添加 Token 驗證的步驟:

1、服務器將 Token 返回到前端 用戶打開頁面時,前端發起請求,服務器會返回一個 Token,該 Token 通過加密算法對數據進行加密,一般 Token 都包括隨機字符串和時間戳的組合,同時 Token 會存在服務器的 Session 中。之後頁面加載完成時,使用 JS 遍歷整個 DOM 樹,在 DOM 中所有地址是本站的 aform 標籤中加入 Token,其他的請求就在編碼時手動添加 Token 這個參數。

2、前端發請求時攜帶這個 Token 對於 GET 請求,Token 將附在請求地址之後,這樣 URL 就變成 http://url?token=tokenvalue。 而對於 form 標籤發起的 POST 請求來說,要在 form 的最後加上:

<input type=」hidden」 name=」token」 value=」tokenvalue」/>

總之,就是前端發請求時把 Token 以參數的形式加入請求中。

3、服務器驗證 Token 是否正確 當前端得到了 Token ,再次提交給服務器的時候,服務器需要判斷 Token 的有效性,驗證過程是先解密 Token,對比加密字符串以及時間戳,如果加密字符串一致且時間未過期,那麼這個 Token 就是有效的。

總結

CSRF能夠攻擊成功的本質是:重要操作的所有參數都是可以被攻擊者猜測到的。 所以只要防止攻擊者成功的構造一個偽造請求,就可以杜絕攻擊了!