5. CSRF
在學完 XSS的借刀殺人後,接著就來談談同樣常年在 OWASP Top 10 榜單上的常客——CSRF(Cross-Site Request Forgery,跨網站請求偽造)。
如果說 XSS 是駭客在你的網站裡「下毒」,那麼 CSRF 就是駭客利用使用者的信任,在背後對網站發動「跨空偷襲」。
Stateless
在深入 CSRF 攻擊之前,我們必須先認識一個關於 HTTP 協定的本質:HTTP 是一個 Stateless(無狀態)的協定。
什麼是「無狀態」?簡單來說,HTTP 伺服器就像是一個「嚴重失憶症的秒忘人」。對於伺服器而言,你剛剛發出的 Request 和你現在發出的 Request,是兩個完全獨立、毫無瓜葛的事件。你前一秒才剛成功輸入帳號密碼登入,後一秒點擊「看期末成績」時,伺服器一翻兩瞪眼,當場又忘記你是誰了。為什麼要這樣設計?
因為「不記住人」其實有兩個巨大的好處:
- 極省資源:伺服器不需要花費珍貴的記憶體,去死記活背現在線上有哪 10 萬個使用者正在連線、每個人剛才做了什麼。
- 高併發超快:反正來一個請求就處理一個,處理完就兩清,這讓伺服器能以極快的速度處理海量連線。
但就算有好處這樣也超不方便阿!誰想要每點一個分頁,就要重新輸入一次帳號密碼?
為了解決這個問題,工程師發明了 Cookie(數位身份印章)。當你第一次成功登入後,伺服器會發給你一個寫著你身份的 Cookie,並蓋在你的瀏覽器上。
因為 HTTP 是 stateless 架構,所以為了讓伺服器知道你是誰、不讓你每看一個新網頁就重新登入一次,瀏覽器只要發出 Request,就會自動、順便、貼心地把你存在該網域的 Cookie 一併打包帶上。
這個體貼的自動化設計,完美解決了 Stateless 的困境,讓我們上網變得無比順暢。然而,聰明的駭客,正是盯上了這個能進入伺服器的機會…
簡介
「盜用你的身份,做你想不到的事。」,CSRF的核心原理建立在瀏覽器的自動化機制上。

假設你登入了一個網站叫做WebA,卻不小心切換分頁時,誤進了駭客做的釣魚網站(例如:www.free-cat-images.com 看可愛貓咪)。結果這個貓咪網站暗藏了一行看不見的程式碼,在你點擊圖片的瞬間,偷偷幫你發送了一個 Request 給WebA的伺服器,宣稱:「我要轉帳 10,000 元給駭客」。
瀏覽器一看到這個 Request 是發給「WebA」的,基於本能,自動把當初登入時,從伺服器那裏拿到的Cookie給蓋了上去。WebA伺服器收到後,翻開一看:「印章沒錯,是 WebA本人發的!」於是轉帳成功,你就在看貓咪圖片的同時,不知不覺被盜刷了。
在整個 CSRF 攻擊過程中,駭客從頭到尾都沒有「偷走」你的 Cookie。駭客只是借用了瀏覽器會自動帶上 Cookie 的特性,盲目地幫你發出了一個你不想要的請求。
CSRF 攻擊的三大手法
駭客要在他的壞網站上,幫你發出「跨網站請求」,通常有以下三種包裝方式:
1. 利用 <img> 標籤(針對 GET 請求)
如果銀行的轉帳系統設計得很爛,是用 GET 方法來處理敏感操作:https://bank.com/transfer?to=hacker&amount=10000
駭客只需要在他的網頁裡塞一個外表看似無辜的圖片標籤:
<img src="https://bank.com/transfer?to=hacker&amount=10000" width="0" height="0" />
Code language: HTML, XML (xml)
瀏覽器在解析這個 HTML 時,為了要把圖片載入進來,會自動對該網址發出 GET 請求(並自動帶上 bank.com 的 Cookie)。受害者連點都不用點,一進網頁直接中彈。防範這種鳥招的最好方式就是符合REST原則來實作後端API,屬於後端領域,詳請參考:
2. 利用隱藏表單 <form>(針對 POST 請求)
如果系統比較聰明,限定用 POST 請求才能轉帳呢?駭客會在網頁裡寫一個隱藏的表單,並配上一段自動執行的 JavaScript:
<form id="csrfForm" action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="hacker" />
<input type="hidden" name="amount" value="10000" />
</form>
<script>
// 網頁一載入,完全不通知使用者,自動把表單送出去!
document.getElementById('csrfForm').submit();
</script>
Code language: HTML, XML (xml)
防範措施
要怎麼防範這種跨空偷襲?既然問題出在「瀏覽器會盲目地自動帶上 Cookie」,我們的防禦思路就是:讓伺服器有能力分辨,這個 Request 到底是用戶在我們官網上「心甘情願點擊」的,還是從隔壁駭客網站「隔空」送過來的。
SameSite Cookie 屬性
這是現代瀏覽器全面普及的物理外掛,也是最省心、最強大的防禦。後端在核發 Cookie 的時候,可以加上 SameSite 的設定:
SameSite=Strict(最嚴格):只要請求不是在我們自家網站發起的(例如從 Google 點擊連結過來、或從駭客網站發起),瀏覽器一律不准帶上這枚 Cookie。SameSite=Lax(現代瀏覽器預設):大部分跨網站的點擊不帶 Cookie。只有在使用者進行「安全的頂級導航」(例如點擊超連結切換網頁<a>)時才會帶上,如果是<img>或表單 POST 則完全封鎖。
一開啟
SameSite,駭客在隔壁分頁發動的跨站偷襲,送到銀行時就會因為「沒有帶上 Cookie」而被當場拒之門外。
CSRF Token
當使用者訪問我們自家的轉帳頁面時,Application Server 隨機生成一串只用一次的亂數密碼(CSRF Token),藏在我們前端的表單裡。
- 當使用者點擊轉帳時,Request 同時包含 Cookie 與表單裡的 Token。
- 駭客的網站雖然可以引導瀏覽器帶上 Cookie,但因為他沒辦法偷看你家網站的 HTML(受限於瀏覽器的同源政策 Same-Origin Policy,下章會講),所以他絕對猜不到、也拿不到當下這組隨機的 Token。
- 伺服器收到請求後,發現有 Cookie 但少了 Token,就知道這是跨站偽造的盜版請求,直接拒絕。
關鍵操作重新驗證
對於涉及財產、密碼變更的極度敏感操作,不要過度信任 Cookie。
- 做法:在按下轉帳或修改密碼前,強制彈出視窗要求輸入「簡訊驗證碼(OTP)」、「圖形驗證碼」或「再次輸入舊密碼」。
- 防禦效果:駭客在隔壁分頁只能盲目發送請求,他沒辦法幫受害者輸入手機剛收到的簡訊驗證碼。
問!
Harry 正在開發學校的「失物招領系統」。在前端他使用了 Next.js 框架,而後端 API 伺服器則是架設在另一個獨立的網域(例如:前端是
ntub.edu.tw,後端 API 是api-ntub.com)。在實作使用者登入功能時,Harry 面臨了兩個保存「登入憑證(Token)」的架構方案:
- 方案 A:將 Token 存在瀏覽器的
LocalStorage中,每次發送 API 請求時,用 JavaScript 手動把 Token 塞進 HTTP Header 的Authorization: Bearer <Token>裡面。- 方案 B:將 Token 存在
Cookie中,並開啟HttpOnly防禦 XSS。請試著用本章學到的「Stateless 與瀏覽器機制」思考並回答:
- 哪一個方案天生具備免疫 CSRF 攻擊的特性?為什麼?
- 哪一個方案雖然安全,但如果後端沒有設定好
SameSite屬性,就會暴露出 CSRF 的隱憂?
- 答案引導:
- 方案 A 天生免疫 CSRF。因為
LocalStorage裡面的資料絕對不會像 Cookie 一樣,被瀏覽器「自動、順便、貼心地一併打包帶上」。駭客在釣魚網站隔空發動請求時,瀏覽器不會幫忙帶上Authorization標頭,且駭客受限於同源政策(SOP,下章會講)也讀取不到 Harry 瀏覽器裡的LocalStorage,因此 CSRF 無法成立。 - 方案 B 存在 CSRF 隱憂。因為方案 B 使用了 Cookie,只要是 Cookie,瀏覽器在發出請求時就會自動帶上。如果後端忘記設定
SameSite=Lax/Strict,駭客就能利用這個自動化機制發動跨站偷襲。
- 方案 A 天生免疫 CSRF。因為
小結
這章節提到兩次SOP同源協定,究竟是何方神聖?我們下章揭曉!
剛學習這兩種攻擊,時常會搞混,以下整理一張表格:
| 漏洞名稱 | XSS (跨網站指令碼) | CSRF (跨網站請求偽造) |
| 攻擊本質 | 借刀殺人,在目標網站**「下毒(塞代碼)」**。 | 隔空打牛,利用瀏覽器機制**「冒充身份」**。 |
| Cookie 有沒有被偷? | 有。駭客直接把受害者的 Cookie 讀取並打包帶走。 | 沒有。駭客從頭到尾摸不到 Cookie,只是利用瀏覽器自動帶上它。 |
| 發生的戰場 | 100% 發生在受害者的瀏覽器內部執行。 | 發生在跨網站的請求傳遞過程中。 |
| 核心防禦核心 | 1. 字元跳脫(htmlentities)2. 敏感 Cookie 開啟 HttpOnly。 | 1. Cookie 設定 SameSite=Lax/Strict2. 引入 CSRF Token3. 關鍵操作加驗證碼。 |
