昨天我們說了 session 跟 JWT 的不同,兩者之所以不同,是因為它們不一樣。
6,又在講幹話,蔣幹都沒你會講幹話。
咳咳...…總之,今天是個探討如何保護 cookie 的好日子。
畢竟昨天 114514 都出現了,好日子肯定是要來臨了(喜)。
已經等不及了,快端上來罷!
我怎麼不記得一年前的自己有這麼臭(惱)。
回歸正題,保護 cookie 的重要性不亞於護食,正確的保護使用者的餅乾,你才能保住你身為 Web Engineer 的飯碗。
總之,先講第一招吧。
Cookie 被盜取的途徑有很多,其中一種,是網站先出現 XSS 漏洞,讓攻擊者的 JavaScript 跑進使用者的頁面。
接著,它就能透過 document.cookie 讀取未標記為 HttpOnly 的 Cookie,再經由 fetch、AJAX 或其他能發送 HTTP Request 的方式,將取得的資料送到攻擊者的伺服器。
整個 code 十分簡易,我即使不使用 AI 也能完成。
你的簡單是真的簡單嗎?
肯定阿,剛剛我說的整個流程有很複雜嗎?
確實,那面對這麼無賴的攻擊有什麼好的解法嗎?
嗯,還是有的。
而且解法簡單到有點無賴。
不給 JavaScript 看就好了。
???
你這跟「只要我看不到 Bug,就沒有 Bug」有什麼區別?
差很多好嗎。
Cookie 有一個屬性叫做 HttpOnly。
當我們替 Cookie 加上 HttpOnly 後,瀏覽器仍然會在符合條件的 HTTP Request 中自動帶上這顆 Cookie,但 JavaScript 就不能再透過 document.cookie 讀取它。
換句話說,原本攻擊者想做:
const cookie = document.cookie;
現在即使頁面真的被插入了惡意 JavaScript,標記為 HttpOnly 的 Cookie 也不會出現在 document.cookie 裡。
所以開了
HttpOnly,XSS 就解決了?
你真的有在聽嗎…
HttpOnly 保護的是 Cookie,不是你的網站。
XSS 還是 XSS。惡意 JavaScript 依然可以在使用者的瀏覽器裡執行,也可能直接用使用者的登入狀態發出請求。
HttpOnly 做的事情很單純:
即使 JavaScript 被攻擊者控制,也盡量不要讓它直接把認證 Cookie 順走。
所以與其說 HttpOnly 是 XSS 的解藥,不如說它是在 XSS 已經發生時,再加一道防線。
好,那 JavaScript 看不到了。
這樣 Cookie 總安全了吧?
還沒。
你只是防止 JavaScript 把它拿走,但如果這顆 Cookie 自己大搖大擺地走在 HTTP 上呢?
強制綁定 HTTPS?
Bingo。
Cookie 還有另一個屬性:Secure。
當一顆 Cookie 被設定 Secure 屬性後,瀏覽器只會透過 HTTPS 傳送這顆 Cookie,不會把它送進一般的 HTTP 連線。
也就是說:
Set-Cookie: session=114514; Secure
就這樣?
就這樣。
Secure 的工作其實非常單純:正式環境裡,這顆 Cookie 只能走 HTTPS。
不過這裡還是要注意,Secure 保護的是 Cookie 的傳輸條件,不代表只要加上它,整個網站的傳輸安全問題就全部解決了。
所以實際部署時,該把網站全面導向 HTTPS 還是得做。
當然,正式環境只有 HTTP 時加上它,登入 Cookie 就不會被送出,登入機制自然會壞掉。
不過本機開發是個例外:多數瀏覽器會把 localhost 視為安全情境,讓 Secure Cookie 能在 http://localhost 測試。這是開發上的特例,不是正式環境可以不用 HTTPS 的免死金牌。
好,所以現在 JavaScript 看不到它,HTTP 也不讓它走。
這次總安全了吧?
還沒。
甚至接下來這個問題,根本不需要把你的 Cookie 偷走。
因為——
瀏覽器會自己幫攻擊者帶上。
???那怎麼偽裝成我?
因為 Cookie 有個特性:瀏覽器會自動幫你帶上。
假設你登入數位校園後,又跑去逛了一個惡意網站。
這個網站不知道你的 Session Cookie 是什麼,但它可以想辦法讓你的瀏覽器向數位校園發出 Request。
如果這時瀏覽器允許帶上 Cookie:
攻擊者:幫我送個 Request 給數位校園。
瀏覽器:好,順便幫你把他的 Cookie 帶上。
數位校園:Cookie 對得上,是本人!
等等,你不是才說
HttpOnly之後攻擊者看不到 Cookie?
對啊。
但他根本不用看。
這種利用使用者既有登入狀態,誘使瀏覽器發出非預期請求的攻擊,就叫做 CSRF(Cross-Site Request Forgery,跨站請求偽造)。
所以我們得告訴瀏覽器:
別的網站叫你過來時,不要隨便把我的 Cookie 也帶過來。
這就輪到下一個屬性——SameSite。
SameSite 主要有三種設定:
Strict:最嚴格,跨站情境下不帶這顆 Cookie。Lax:稍微寬鬆,部分跨站導覽情境仍允許帶上 Cookie。None:允許跨站攜帶 Cookie,而且必須搭配 Secure。例如:
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax
那全部設
Strict不就好了?
如果世界這麼單純就好了。
有些網站本來就需要跨站使用 Cookie,而 Strict 也可能讓正常的跨站導覽體驗受到影響。
至於 Lax,它不是「跨站一律不帶」:使用者從別的網站以頂層導覽進來,而且是安全方法時,Cookie 仍可能會帶上,例如點連結進來的 GET。
所以
GET也有可能帶 Cookie?
對。所以別把「改資料、刪資料、改密碼」這種有副作用的操作塞進 GET。那是把家門鑰匙掛在門把上,還附一張「請勿自取」。
所以 SameSite 並不是越嚴越好,而是要按照使用情境選擇。
更重要的是,它只是縮小 CSRF 的攻擊面,不是 CSRF 的萬靈丹。重要的寫入操作,仍要搭配 CSRF Token,或在伺服器端驗證 Origin/Referer 等防線。
好,今天總算把 Cookie 保護好了吧?
差不多了。
HttpOnly 防止 JavaScript 直接讀取 Cookie,Secure 限制 Cookie 透過 HTTPS 傳送,而 SameSite 則限制跨站情境下攜帶 Cookie 的行為。
所以這三個加上去就天下太平了?
想得美。
這三個屬性只是 Cookie 安全防護的一部分,並不代表我們可以直接無視 XSS、CSRF 等其他問題。
不過,至少現在我們知道該怎麼保護自己的餅乾;正確護食,才能保住身為 Web Engineer 的飯碗。
至於怎麼把這些設定實際塞進 FastAPI,明天就來把餅乾端上桌。
收工!