CSRF攻擊:陌生鏈接不要隨便點

XSS 的攻擊方式是黑客往用戶的頁面中注入惡意腳本,然后再通過惡意腳本將用戶頁面的數(shù)據(jù)上傳到黑客的服務(wù)器上,最后黑客再利用這些數(shù)據(jù)進(jìn)行一些惡意操作。XSS 攻擊能夠帶來很大的破壞性,不過另外一種類型的攻擊也不容忽視,它就是我們今天要聊的 CSRF 攻擊。
相信你經(jīng)常能聽到的一句話:“別點那個鏈接,小心有病毒!”點擊一個鏈接怎么就能染上病毒了呢?
我們結(jié)合一個真實的關(guān)于 CSRF 攻擊的典型案例來分析下,在 2007 年的某一天,David 無意間打開了 Gmail 郵箱中的一份郵件,并點擊了該郵件中的一個鏈接。過了幾天,David 就發(fā)現(xiàn)他的域名被盜了。不過幾經(jīng)周折,David 還是要回了他的域名,也弄清楚了他的域名之所以被盜,就是因為無意間點擊的那個鏈接。
那 David 的域名是怎么被盜的呢?
我們結(jié)合下圖來分析下 David 域名的被盜流程:

首先 David 發(fā)起登錄 Gmail 郵箱請求,然后 Gmail 服務(wù)器返回一些登錄狀態(tài)給 David 的瀏覽器,這些信息包括了 Cookie、Session 等,這樣在 David 的瀏覽器中,Gmail 郵箱就處于登錄狀態(tài)了。 接著黑客通過各種手段引誘 David 去打開他的鏈接,比如 hacker.com,然后在 hacker.com 頁面中,黑客編寫好了一個郵件過濾器,并通過 Gmail 提供的 HTTP 設(shè)置接口設(shè)置好了新的郵件過濾功能,該過濾器會將 David 所有的郵件都轉(zhuǎn)發(fā)到黑客的郵箱中。 最后的事情就很簡單了,因為有了 David 的郵件內(nèi)容,所以黑客就可以去域名服務(wù)商那邊重置 David 域名賬戶的密碼,重置好密碼之后,就可以將其轉(zhuǎn)出到黑客的賬戶了。
以上就是 David 的域名被盜的完整過程,其中前兩步就是我們今天要聊的 CSRF 攻擊。David 在要回了他的域名之后,也將整個攻擊過程分享到他的站點上了,如果你感興趣的話,可以參考
什么是 CSRF 攻擊
CSRF 英文全稱是 Cross-site request forgery,所以又稱為“跨站請求偽造”,是指黑客引誘用戶打開黑客的網(wǎng)站,在黑客的網(wǎng)站中,利用用戶的登錄狀態(tài)發(fā)起的跨站請求。簡單來講,CSRF 攻擊就是黑客利用了用戶的登錄狀態(tài),并通過第三方的站點來做一些壞事
通常當(dāng)用戶打開了黑客的頁面后,黑客有三種方式去實施 CSRF 攻擊。
下面我們以極客時間官網(wǎng)為例子,來分析這三種攻擊方式都是怎么實施的。這里假設(shè)網(wǎng)站具有轉(zhuǎn)賬功能,可以通過 POST 或 Get 來實現(xiàn)轉(zhuǎn)賬,轉(zhuǎn)賬接口如下所示:

有了上面的轉(zhuǎn)賬接口,我們就可以來模擬 CSRF 攻擊了。
1. 自動發(fā)起 Get 請求
黑客最容易實施的攻擊方式是自動發(fā)起 Get 請求,具體攻擊方式你可以參考下面這段代碼:

這是黑客頁面的 HTML 代碼,在這段代碼中,黑客將轉(zhuǎn)賬的請求接口隱藏在 img 標(biāo)簽內(nèi),欺騙瀏覽器這是一張圖片資源。當(dāng)該頁面被加載時,瀏覽器會自動發(fā)起 img 的資源請求,如果服務(wù)器沒有對該請求做判斷的話,那么服務(wù)器就會認(rèn)為該請求是一個轉(zhuǎn)賬請求,于是用戶賬戶上的 100 極客幣就被轉(zhuǎn)移到黑客的賬戶上去了。
2. 自動發(fā)起 POST 請求
除了自動發(fā)送 Get 請求之外,有些服務(wù)器的接口是使用 POST 方法的,所以黑客還需要在他的站點上偽造 POST 請求,當(dāng)用戶打開黑客的站點時,是自動提交 POST 請求,具體的方式你可以參考下面示例代碼:

在這段代碼中,我們可以看到黑客在他的頁面中構(gòu)建了一個隱藏的表單,該表單的內(nèi)容就是極客時間的轉(zhuǎn)賬接口。當(dāng)用戶打開該站點之后,這個表單會被自動執(zhí)行提交;當(dāng)表單被提交之后,服務(wù)器就會執(zhí)行轉(zhuǎn)賬操作。因此使用構(gòu)建自動提交表單這種方式,就可以自動實現(xiàn)跨站點 POST 數(shù)據(jù)提交。
3. 引誘用戶點擊鏈接
除了自動發(fā)起 Get 和 Post 請求之外,還有一種方式是誘惑用戶點擊黑客站點上的鏈接,這種方式通常出現(xiàn)在論壇或者惡意郵件上。黑客會采用很多方式去誘惑用戶點擊鏈接,示例代碼如下所示:

這段黑客站點代碼,頁面上放了一張美女圖片,下面放了圖片下載地址,而這個下載地址實際上是黑客用來轉(zhuǎn)賬的接口,一旦用戶點擊了這個鏈接,那么他的極客幣就被轉(zhuǎn)到黑客賬戶上了。
以上三種就是黑客經(jīng)常采用的攻擊方式。如果當(dāng)用戶登錄了極客時間,以上三種 CSRF 攻擊方式中的任何一種發(fā)生時,那么服務(wù)器都會將一定金額的極客幣發(fā)送到黑客賬戶。
到這里,相信你已經(jīng)知道什么是 CSRF 攻擊了。和 XSS 不同的是,CSRF 攻擊不需要將惡意代碼注入用戶的頁面,僅僅是利用服務(wù)器的漏洞和用戶的登錄狀態(tài)來實施攻擊
如何防止 CSRF 攻擊
了解了 CSRF 攻擊的一些手段之后,我們再來看看 CSRF 攻擊的一些“特征”,然后根據(jù)這些“特征”分析下如何防止 CSRF 攻擊。下面是我總結(jié)的發(fā)起 CSRF 攻擊的三個必要條件:
第一個,目標(biāo)站點一定要有 CSRF 漏洞; 第二個,用戶要登錄過目標(biāo)站點,并且在瀏覽器上保持有該站點的登錄狀態(tài); 第三個,需要用戶打開一個第三方站點,可以是黑客的站點,也可以是一些論壇。
滿足以上三個條件之后,黑客就可以對用戶進(jìn)行 CSRF 攻擊了。這里還需要額外注意一點,與 XSS 攻擊不同,CSRF 攻擊不會往頁面注入惡意腳本,因此黑客是無法通過 CSRF 攻擊來獲取用戶頁面數(shù)據(jù)的;其最關(guān)鍵的一點是要能找到服務(wù)器的漏洞,所以說對于 CSRF 攻擊我們主要的防護(hù)手段是提升服務(wù)器的安全性。
要讓服務(wù)器避免遭受到 CSRF 攻擊,通常有以下幾種途徑。
1. 充分利用好 Cookie 的 SameSite 屬性
通過上面的介紹,相信你已經(jīng)知道了黑客會利用用戶的登錄狀態(tài)來發(fā)起 CSRF 攻擊,而Cookie 正是瀏覽器和服務(wù)器之間維護(hù)登錄狀態(tài)的一個關(guān)鍵數(shù)據(jù),因此要阻止 CSRF 攻擊,我們首先就要考慮在 Cookie 上來做文章。
通常 CSRF 攻擊都是從第三方站點發(fā)起的,要防止 CSRF 攻擊,我們最好能實現(xiàn)從第三方站點發(fā)送請求時禁止 Cookie 的發(fā)送,因此在瀏覽器通過不同來源發(fā)送 HTTP 請求時,有如下區(qū)別:
如果是從第三方站點發(fā)起的請求,那么需要瀏覽器禁止發(fā)送某些關(guān)鍵 Cookie 數(shù)據(jù)到服務(wù)器; 如果是同一個站點發(fā)起的請求,那么就需要保證 Cookie 數(shù)據(jù)正常發(fā)送。
而我們要聊的 Cookie 中的 SameSite 屬性正是為了解決這個問題的,通過使用 SameSite 可以有效地降低 CSRF 攻擊的風(fēng)險。
那 SameSite 是怎么防止 CSRF 攻擊的呢?
在 HTTP 響應(yīng)頭中,通過 set-cookie 字段設(shè)置 Cookie 時,可以帶上 SameSite 選項,如下:

SameSite 選項通常有 Strict、Lax 和 None 三個值。
Strict 最為嚴(yán)格。如果 SameSite 的值是 Strict,那么瀏覽器會完全禁止第三方 Cookie。簡言之,如果你從極客時間的頁面中訪問 InfoQ 的資源,而 InfoQ 的某些 Cookie 設(shè)置了 SameSite = Strict 的話,那么這些 Cookie 是不會被發(fā)送到 InfoQ 的服務(wù)器上的。只有你從 InfoQ 的站點去請求 InfoQ 的資源時,才會帶上這些 Cookie。 Lax 相對寬松一點。在跨站點的情況下,從第三方站點的鏈接打開和從第三方站點提交 Get 方式的表單這兩種方式都會攜帶 Cookie。但如果在第三方站點中使用 Post 方法,或者通過 img、iframe 等標(biāo)簽加載的 URL,這些場景都不會攜帶 Cookie。 而如果使用 None 的話,在任何情況下都會發(fā)送 Cookie 數(shù)據(jù)。
對于防范 CSRF 攻擊,我們可以針對實際情況將一些關(guān)鍵的 Cookie 設(shè)置為 Strict 或者 Lax 模式,這樣在跨站點請求時,這些關(guān)鍵的 Cookie 就不會被發(fā)送到服務(wù)器,從而使得黑客的 CSRF 攻擊失效。
2. 驗證請求的來源站點
接著我們再來了解另外一種防止 CSRF 攻擊的策略,那就是在服務(wù)器端驗證請求來源的站點。由于 CSRF 攻擊大多來自于第三方站點,因此服務(wù)器可以禁止來自第三方站點的請求。那么該怎么判斷請求是否來自第三方站點呢?
這就需要介紹 HTTP 請求頭中的 Referer 和 Origin 屬性了。
Referer 是 HTTP 請求頭中的一個字段,記錄了該 HTTP 請求的來源地址。比如我從極客時間的官網(wǎng)打開了 InfoQ 的站點,那么請求頭中的 Referer 值是極客時間的 URL,如下圖:

雖然可以通過 Referer 告訴服務(wù)器 HTTP 請求的來源,但是有一些場景是不適合將來源 URL 暴露給服務(wù)器的,因此瀏覽器提供給開發(fā)者一個選項,可以不用上傳 Referer 值,具體可參考Referrer Policy
但在服務(wù)器端驗證請求頭中的 Referer 并不是太可靠,因此標(biāo)準(zhǔn)委員會又制定了Origin 屬性,在一些重要的場合,比如通過 XMLHttpRequest、Fecth 發(fā)起跨站請求或者通過 Post 方法發(fā)送請求時,都會帶上 Origin 屬性,如下圖:

從上圖可以看出,Origin 屬性只包含了域名信息,并沒有包含具體的 URL 路徑,這是 Origin 和 Referer 的一個主要區(qū)別。在這里需要補充一點,Origin 的值之所以不包含詳細(xì)路徑信息,是有些站點因為安全考慮,不想把源站點的詳細(xì)路徑暴露給服務(wù)器。
因此,服務(wù)器的策略是優(yōu)先判斷 Origin,如果請求頭中沒有包含 Origin 屬性,再根據(jù)實際情況判斷是否使用 Referer 值。
3. CSRF Token
除了使用以上兩種方式來防止 CSRF 攻擊之外,還可以采用 CSRF Token 來驗證,這個流程比較好理解,大致分為兩步。
第一步,在瀏覽器向服務(wù)器發(fā)起請求時,服務(wù)器生成一個 CSRF Token。CSRF Token 其實就是服務(wù)器生成的字符串,然后將該字符串植入到返回的頁面中。你可以參考下面示例代碼:

第二步,在瀏覽器端如果要發(fā)起轉(zhuǎn)賬的請求,那么需要帶上頁面中的 CSRF Token,然后服務(wù)器會驗證該 Token 是否合法。如果是從第三方站點發(fā)出的請求,那么將無法獲取到 CSRF Token 的值,所以即使發(fā)出了請求,服務(wù)器也會因為 CSRF Token 不正確而拒絕請求。
總結(jié)
要發(fā)起 CSRF 攻擊需要具備三個條件:目標(biāo)站點存在漏洞、用戶要登錄過目標(biāo)站點和黑客需要通過第三方站點發(fā)起攻擊。
根據(jù)這三個必要條件,我們又介紹了該如何防止 CSRF 攻擊,具體來講主要有三種方式:充分利用好 Cookie 的 SameSite 屬性、驗證請求的來源站點和使用 CSRF Token。這三種方式需要合理搭配使用,這樣才可以有效地防止 CSRF 攻擊。 再結(jié)合前面兩篇文章,我們可以得出頁面安全問題的主要原因就是瀏覽器為同源策略開的兩個“后門”:一個是在頁面中可以任意引用第三方資源,另外一個是通過 CORS 策略讓 XMLHttpRequest 和 Fetch 去跨域請求資源。 為了解決這些問題,我們引入了 CSP 來限制頁面任意引入外部資源,引入了 HttpOnly 機制來禁止 XMLHttpRequest 或者 Fetch 發(fā)送一些關(guān)鍵 Cookie,引入了 SameSite 和 Origin 來防止 CSRF 攻擊。
點個『在看』支持下?
