Skip to content

[Security] Same-site cookie 與 CSRF 防禦、same-site 攻擊以及 cookie bomb

· 28 min

前言#

承接上篇 [Security] 認識 CSRF 與常見防禦方式,此篇主要敘述的是《Beyond XSS:探索網頁前端資安宇宙》 4–5~4-7 章節的筆記,若有錯誤歡迎大家回覆告訴我~

在提 XSS 的防禦時,我們有提到 CSP,加上 CSP 後,瀏覽器就會幫你把不符規則的擋下。那有沒有加上什麼設定後,瀏覽器就會幫我們阻止 CSRF ?
有,那就是 same-site cookie,加上 same-site cookie 後就能阻止 CSRF。(後面會討論是真的能阻止嗎)

Same-site cookie 就是只有在 same-site 狀況下才會送出的 cookie,使用方式就是設定 cookie 的 SameSite 屬性,SameSite 屬性可填入的值如下(ref:MDN SameSite):

SameSite=Strict#

https://api.monica.tw 的 cookie 設定 SameSite=Strict時,以下列出是否帶上 cookie 的舉例:

SameSite=Strict 的嚴格限制會影響使用者體驗,舉例來說,假設 Google 驗證使用者身分的 token 存在 same-site cookie,而有篇 Medium 文章內有超連結會連到 Google 搜尋頁,當使用者點連結後,因為不會帶上使用者目前 Google 的 token,開啟的 Google 畫面會是未登入狀態,這種「我明明有登入 Google,但打開連結卻是未登入」的不連貫情況會影響使用者體驗。

此時有兩種解法:

  1. 準備兩組 cookie
    • 一組用來維持登入狀態,不設 SameSite,無論從哪來,都是登入狀態,但只會用來維持登入,攻擊者即使有這 cookie 也不能做任何操作
    • 一組則是做敏感操作會用到,敏感操作如購買物品或設定帳戶,這組 cookie 設 SameSite 以避免 CSRF
  2. 調整 SameSite 屬性為 Lax

SameSite=Lax#

Lax 放寬Strict 的限制,在 top-level navigation 的時候會帶上 cookie,例如以下情況會帶 cookie:

這幾個允許的情況有些共通點:都是 GET且皆會觸發網頁跳轉(Navigation),這樣能避免 Strict 需重新登入的問題,也避免在瀏覽其他網站時毫不知情的送出 cookie。

而其他像是 POST 方法的 form 就不會帶 cookie。

另附上表格列出在 cross-origin 請求時,哪些會帶上 cookie,SameSite設為 None 就是都會送出,設為 Lax就是 top-level navigation 時才會送出,而設為 Strict就是都不會送出。

依據 SameSite 屬性和請求類型判斷 cookie 是否送出(資料來源:https://www.sjoerdlangkemper.nl/2016/04/14/preventing-csrf-with-samesite-cookie-attribute/)

SameSite=Lax的優點是保有彈性,使用者從其他網站連進你的網站時能維持登入狀態,同時防止 CSRF 攻擊,因為 cross-site 請求不會帶上 cookie,因此無法執行 CSRF 攻擊。

依時間順序,列出 Same-site cookie 的歷史如下:

由上可知,現在瀏覽器在沒設定 SameSite屬性的 cookie 情況下,預設值就是 Lax

中場思考一下,當我們有預設的 SameSite=Lax 後,CSRF 攻擊貌似就無效了嗎?有了預設的 SameSite=Lax 後,還需要 CSRF token 嗎?沒加會有問題嗎?什麼狀況會有問題?

SameSite Lax 無法保護的情境#

GET 型態的 CSRF#

CSRF 重點是「執行操作」,前面提的 CSRF 攻擊都以 POST 示範,因為 GET 通常不會用於執行操作,GET 只適合 idempotent 的操作,以 GET 執行操作不符 GET 語義,但不適合執行操作,不代表不行,例如可用 GET /delete?id=3 來實作刪除功能。

補充:Idempotent(ref:9.2.2. Idempotent Methods)
冪等性(Idempotent)指的是一個操作,即使重複執行多次,也不會產生額外影響或不同結果。PUTDELETE 和安全的請求方法(如 GETHEAD)是 Idempotent 的。

SameSite Lax 無法保護的情境 1:以 GET 執行操作,搭配頁面重導向#

使用頁面重導向且用 GET 執行操作時,SameSite=Lax 無法保護。

// lax 允許底下行為
location = 'https://api.monica.tw/delete?id=3'

SameSite Lax 無法保護的情境 2:method override#

HTML 表單的 method 屬性代表 request 送出時的 HTTP 方法,method 值只支援 GETPOST,若要用其他 HTTP 方法(如:PUTPATCHDELETE)可用兩種方式:

  1. 改用 fetch()
  2. 還是用表單,由後端實作 workaround,不少 framework 有支援,舉例來說 request 有 X-HTTP-Method-Override header 或 query string 上有 _method 參數,就以裡面的值作為請求方法,而非原先 HTTP 內方法。
    當我們想更新資料又只能用 POST,可用 _method 參數讓伺服器知道這是要 PATCH,程式碼如下:
<form action="/api/update/1" method="POST">
<input type=hidden name=_method value=PATCH>
<input name=title value=new_title>
</form>

第二種 workaround 方式可稱為 method override,而 method override 可應用在 CSRF 攻擊,像是 GET /api/deleteMyAccount?_method=POST 會被視為 POST 操作,且可繞過 SameSite lax 的保護,攻擊範圍是有支援 method 覆蓋的伺服器。支援 method 覆蓋的網頁框架可參考:Bypassing Samesite Cookie Restrictions with Method Override

same-site cookie 有一個規則是這樣(ref:Changes to SameSite Cookie Behavior — A Call to Action for Web Developers):

For any flows involving POST requests, you should test with and without a long delay. This is because both Firefox and Chrome implement a two-minute threshold that permits newly created cookies without the SameSite attribute to be sent on top-level, cross-site POST requests (a common login flow).

對沒 SameSite 屬性的 cookie,新寫入的兩分鐘內可突破部分的 lax 限制,允許「top-level 的 cross-site POST 請求」,這個「top-level 的 cross-site POST 請求」指的就是 <form method=POST>

此規則目的是為了不讓一些網站壞掉,但可能會有些問題。若使用者剛登入網站、驗證身分 cookie 剛寫入,當使用者開啟攻擊者網頁,且網頁有 CSRF 的 exploit,CSRF 攻擊就會成功。可看出當滿足一定條件(沒設 SameSite屬性的 cookie 剛寫入)時,可無視「預設 Lax」的限制。

CSRF 的 CS 代表 cross-site,但更多時候像 cross-origin,從 assets.monica.twmonica.tw 攻擊也稱作 CSRF,即使兩網站非 cross-site。Same-site cookie 防禦情況只是確保 cross-site 時 cookie 不會被送出,如果是 same site,就不是 Same-site cookie 防禦範圍。

但 same site CSRF 攻擊是存在的,假設 Facebook 主網站是 www.facebook.com,有個開發者測試環境 sandbox.facebook.com 且在上面有 XSS 漏洞,可利用 sandbox 的 XSS 對主網站發起 CSRF 攻擊,即使網站有 same-site cookie 也沒用,因為兩網站是 same site。

因此只用 same-site cookie 防禦 CSRF 是不安全的,建議開發者除 same-site cookie 外,也一併實作常見防禦方式(例如:CSRF token),same-site cookie 加 CSRF token 可更穩固的防禦攻擊。

實際案例#

書中提及了 2022 年 Grafana 的 CSRF 漏洞,問題根源是 Grafana 只用 SameSite=Lax 作為 CSRF 防護,只要是 same-site 請求,就一律可執行 CSRF 攻擊。但是攻擊者可利用 CORS 規格和 Grafana 伺服器的小 bug 繞過 preflight 檢查限制,繞過限制後,可用 fetch 從 same-site 網站發起 CSRF。而要如何從 same-site 網站發起 CSRF?假設 Grafana 在 https://grafana.huli.tw,就要找到 *.huli.tw 的 XSS 或掌控整個網域才能攻擊。

詳細的漏洞利用原理可參考書中說明,這裡不細講。

掌握 same-site 網站後可以做什麼?#

如果掌握了一個 same-site 網站,可以執行 CSRF 或 cookie tossing 攻擊(後面會提);而控制子網域的方式有幾種,一是找 same-site 網站的 XSS,二是Subdomain takeover。

Subdomain takeover#

Subdomain takeover 意思就是拿到整個 subdomain,擁有 subdomain 控制權。達到 Subdomain takeover 的方式依難度可分:

這裡要介紹的是利用雲端服務的方式,Amazon S3 是一種雲端儲存服務,可丟檔案上去並設權限、分享給他人,可用來放圖片或 host 靜態網站。每個 S3 儲存空間稱作 bucket,bucket 可定名稱,會對應它提供的子網域,例如bucket 是 monicatest,子網域就是 https://monicatest.s3.us-east-1.amazonaws.com,若不喜歡 S3 的子網域名稱 https://monicatest.s3.us-east-1.amazonaws.com,可自訂網域,自訂網域的流程如下:

  1. 把 bucket 名稱改成想要的網域名稱,例如 campaign.monica.tw
  2. 在 DNS 新增 CNAME 紀錄,將 campaign.monica.tw 指向 monicatest.s3.us-east-1.amazonaws.com
  3. 可用 https://campaign.monica.tw 作為自己網域名稱

Amazon S3 自訂網域名稱有什麼問題?刪除網頁的流程若未妥善處理會有些問題。

假設有個聖誕節活動頁放 S3,再用自訂網域 xmas.monica.tw 指向 https://monicatest.s3.us-east-1.amazonaws.com。當活動結束要刪除網頁時,需砍掉 S3 bucket 並刪除 DNS,若 S3 bucket 刪掉了,但 DNS 記錄未被妥善刪除,會造成 DNS 紀錄還在,但指向的地方已被刪除,xmas.monica.tw 仍指向 https://monicatest.s3.us-east-1.amazonaws.com,但 https://monicatest.s3.us-east-1.amazonaws.com這裡面 S3 bucket 的東西已被刪除。此時攻擊者可建立同名的 S3 Bucket,因 S3 的命名規則是全域唯一,當 xmas.monica.tw 這 bucket 被刪除後,該名稱即變為可用,攻擊者可建立新 S3 Bucket,名稱也為 xmas.monica.tw,並且攻擊者可在此 S3 Bucket 放自己的內容。

因 DNS 記錄仍有效,輸入 xmas.monica.tw 後是導向 https://monicatest.s3.us-east-1.amazonaws.com,而這現在已經指向攻擊者的 S3 Bucket,等同於攻擊者可控制 xmas.monica.tw 內容,達成 subdomain takeover。提供類似 S3 功能的服務都有這類問題,可參考 Can I take over XYZ 列出的清單。

防禦此類攻擊的方式是將 DNS 紀錄一起刪除(ref:防止 DNS 項目懸空並避免子網域接管)。

補充:CNAME(ref:What is a DNS CNAME record?
CNAME (Canonical Name) 記錄的主要功能是將一個網域或子網域(別名網域)指向另一個網域(標準網域),讓兩者共用相同的 IP 位址。
舉例來說,blog.example.com 的 CNAME 記錄值為 example.com,代表當 DNS 伺服器點擊 blog.example.com 的 DNS 記錄時,實際上會觸發另一個對 example.com 的 DNS 查閱,並傳回 example.com 的 IP 位址。此狀況下會說 example.comblog.example.com 的規範名稱(或真實名稱)。
簡單來說就是輸入 blog.example.com 的時候就像輸入 example.com 一樣,會導到一樣的 IP 地址。

獲取子網域控制權以後可以做的事#

有幾個案例,例如 Shockwave 發布的 npm 子網域接管問題,漏洞與 S3 bucket 問題相同,而被接管的網域是 assets.npmjs.com,攻擊影響力在於NPM(Node Package Manager) 是用來管理 JavaScript 套件的網站,開發者常用,攻擊者可在 assets.npmjs.com 上放惡意套件、欺騙開發者這沒問題,以此達到釣魚效果。

又例如 2022 年底 Smaran Chand 找到的 Medium 子網域漏洞,漏洞是Medium 子網域 platform.medium.engineering 指向 Medium,但沒有此部落格,攻擊者可開一個 Medium 部落格並要求連結到 platform.medium.engineering,以此進行社交攻擊,如:發假徵才文。

運用錯誤的安全假設#

CORS 檢查時的錯誤假設,會讓子網域有機會發起攻擊,伺服器動態調整 CORS origin 錯誤範例如下:

const domain = 'monica.tw'
if (origin === domain || origin.endsWith('.' + domain)) {
res.setHeader('Access-Control-Allow-Origin', origin)
}

若 origin 是 monica.tw 或以 .monica.tw 結尾就通過,此檢查假設一個安全前提:攻擊者無法掌握 monica.tw 的子網域。但如果攻擊者可掌握子網域,就能通過 CORS 檢查、從子網域發起攻擊。

最安全的 CORS 檢查還是準備清單,在清單內的 origin 才通過檢查,在 [Security] CORS 與跨來源資源安全性問題 有提過 CORS 正確設置方式。

掌握子網域可做的另一件事是 cookie tossing。以一個範例說明:

情境

攻擊方式

  1. 掌握 s3.monica.tw 子網域且可執行 XSS
  2. s3.monica.tw 寫入名稱是 csrf_token 的 cookie 到 monica.tw
  3. api.monica.twmonica.tw 有同名 token 時,瀏覽器會一起送出。根據 cookie path 屬性,較符合的在前面,例如:api.monica.tw 的 cookie 沒設 path,monica.tw 的 cookie 有設 path=/users,瀏覽器送 request 到 https://api.monica.tw/users 時送出的 cookie 會是csrf_token={value_of_monica_tw}&csrf_token={value_of_api_monica_tw}
  4. 後端通常預設拿第一個 cookie 值,因此拿到在 s3.monica.tw 寫入的 cookie,這代表攻擊者可覆蓋其他 same site 網域的 cookie

這種從子網域把 cookie 丟到別網域的行為,稱為 cookie tossing,此攻擊藉由寫入 cookie 來影響其他的 same-site domain。

cookie tossing 可以達到什麼攻擊?它可以覆蓋 cookie 中存的 csrf_token 值,等於知道 csrf_token 值,可進行 CSRF 攻擊,且 same-site cookie 和 CSRF token 防禦都無法阻止此類攻擊。

解決方法

把 CSRF token 的 cookie 名稱改成 __Host-csrf_token,加 __Host- prefix 的 cookie 不能有 path 和 domain 屬性,因此其他子網域無法寫入、覆蓋(ref:Cookie prefixes)。

補充:在寫入 cookie 時,可寫到更上層的 domain
例如 a.b.huli.tw 可寫入 cookie 到以下 domain:

  • a.b.huli.tw
  • b.huli.tw
  • huli.tw

same-site 攻擊小結#

設計系統應保持最小權限原則,避免不必要的安全假設,相信固定清單中的網域會更安全。瀏覽器對 same-site 網站預設就多了些權限,例如無視 same-site cookie,因此有些網站會將不可信任的檔案(e.g. 使用者上傳的檔案)、不重要的網站放到新 domain,與主站隔離,舉例來說主站在 www.monica.tw,活動頁在 campaign.monica.app,即使活動頁被駭也不影響主站。

Cookie bomb 是利用 cookie 引起的 client-side DoS 攻擊。簡要介紹 DoS 攻擊與 DDoS 攻擊:

DoS 與 DDoS 可依 OSI 模型分為不同層的攻擊,常見攻擊層級如:L3 網路層、L4 傳輸層。當攻擊者找到一種方法讓使用者無法存取網站,就是一種 DoS 攻擊,如果這方法屬於 L7 應用層,就是 L7 的 DoS 攻擊,而 cookie bomb 就是屬於 L7 應用層的 DoS 攻擊。

應用層的 DoS 攻擊舉例如下:

情境:某網站有 API 可查詢資料,預設 limit 是 100(預設查詢資料量 100),把 limit 改為 10000 後發現 server 要一分鐘後才回 response

攻擊方式:每兩秒送一個 request,持續傳送後發現網站變慢,最後網站掛掉只能回 500 Internal Server Error

Cookie bomb 攻擊成立前提是要能寫 cookie,達成「能寫 cookie」有兩種方式:

  1. 利用網站本身邏輯
    如:造訪頁面 https://example.com/log?uid=abc 後,網站會把 uid=abc 寫入 cookie,只要把網址改成 ?uid=xxxxxxxxxx,就可把 xxxxxxxxxx 寫入 cookie
  2. 掌控子網域且能執行 JavaScript 程式碼
    如:subdomain takeover 或其他方法

寫入 cookie 後如何達成攻擊?

  1. 寫一堆垃圾進 cookie,e.g. a1=o....*4000,最少需寫入兩個 cookie(寫入 8KB 資料)才能達成攻擊,因為一個 cookie 能寫的大小約為 4KB
  2. 回到主頁 https://example.com,依 cookie 特性會將這些 cookie 帶上給 server
  3. 此時 Server 會回覆 431 Request Header Fields Too Large

補充:HTTP 狀態碼中有兩個和 request 太大有關

  • 413 Payload Too Large
    e.g. 表單填了一百萬個字後送到 server,可能會收到 413 Payload Too Large 回應,因 payload 太大伺服器無法處理

  • 431 Request Header Fields Too Large
    e.g. 當 cookie 過多,requset header 中的 Cookie 會大到伺服器無法處理,可能會收到 431 Request Header Fields Too Large 回應。server 依實作不同可能會回不同的 HTTP 狀態碼(微軟會回 400 bad request

cookie bomb 攻擊方式就是將使用者的 cookie 塞爆,導致他無法正常存取服務,是一種由一堆 cookie 所引發的 DoS 攻擊,而利用的原理就是瀏覽器造訪網頁時,會自動把對應 cookie 帶上。

詳細攻擊流程如下:

背景
發現 https://example.com/log?uid=abc 可設置任意 cookie

攻擊步驟

  1. 寫入超過 8KB 的 cookie(似乎較多 server 限制是 8KB)
  2. 把這網址傳給攻擊目標,想辦法讓他點開
  3. 目標點網址,在自己瀏覽器上設了很大的 cookie
  4. 目標造訪網站 https://example.com,發現看不到內容,只能看到一片白或是錯誤訊息,攻擊成功

cookie bomb 解套方法就是更換瀏覽器、清除 cookie 或等 cookie 過期。

cookie bomb 攻擊對象是特定使用者(有點開連結的使用者),而攻擊成立前提有兩個:

  1. 找到一地方可設置任意 cookie
  2. 目標須點擊步驟一找到的網址

先插播一件事,看到 cookie bomb 時可能會困惑,subdomain 可往 root domain 設 cookie,那共用 subdomain 怎麼辦?GitHub Pages 每個人 domain 都是 username.github.io,是否可用 cookie bomb 炸到所有 GitHub Pages?例如可以在自己 subdomain 建惡意 HTML,裡面有設 cookie 的程式碼,再把這傳給任何人,其他人點擊後就無法訪問任何 *.github.io 資源,會被 server 拒絕。

而這攻擊成立前提是「可在*.github.iogithub.io 設置 cookie」,但這前提是不成立的,*.github.io 無法github.io 設 cookie。為何呢?

有些網站有類似 GitHub Pages 的狀況,不想要共同的上層 domain 可被設置 cookie,例如a.com.tw 不該設 .com.tw.tw 的 cookie。而解決方式就是之前提過的 public suffix list,瀏覽器決定能不能對某 domain 設 cookie 時,會參考 public suffix,public suffix list 上的 domain,其 subdomain 都無法直接設定該 domain 的 cookiegithub.io 在 public suffix list 上,所以在 userA.github.io 無法設置 github.io 的 cookie,無法執行 cookie bomb 攻擊。

攻擊面擴展#

cookie bomb 攻擊成立前提有兩個:

  1. 找到一地方可設置任意 cookie
  2. 目標須點擊步驟一找到的網址

若想讓攻擊更容易成立,可針對前提思考:

  1. 有沒有可能這地方很好找?
  2. 有沒有可能目標不需點連結就會中招?

有沒有可能目標不需點連結就會中招?可利用快取污染(Cache poisoning)#

快取污染意思就是污染快取內容,不同使用者可能會存取同一份快取,讓快取伺服器儲存壞掉的那個 response,其他人也都會拿到壞掉的檔案、看到相同錯誤,而這種讓大家共用壞掉的快取稱為 CPDoS(Cache Poisoned Denial of Service)。快取污染不一定和 cookie bomb 有關,可從自己電腦發起攻擊而不需 cookie。

快取污染對 cookie bomb 的影響

可輕易設 cookie 的地方例如共用的 subdomain,找到不在 public suffix list 的共用 subdomain,就可設置 cookie。大部分共用的 subdomain 已在 public suffix list 上,但可以找找不在 public suffix 內的服務。

書中有提到例如微軟的 Azure CDN azureedge.net 不在 public suffix 內,可利用 Azure CDN azureedge.net 達到 cookie bomb,不過我 2025年1月查看 public suffix list 時,azureedge.net 已在清單中,因此這方法應該不會成功了,若對實際程式測試還是有興趣的可自己看書中內容~

防禦方式 1:不要相信使用者的輸入

例如 https://example.com/log?uid=abc 不該把 abc 直接寫進 cookie,而要檢查格式或長度。

防禦方式 2:改用自訂網域,不使用服務提供的預設網域

例如不使用預設的 azureedge.net

防禦方式 3:引入資源時加 crossorigin 屬性 + server 允許跨來源請求

crossorigin 屬性的效果是瀏覽器會改用 cross-origin 方式存取資源,預設不會帶 cookie,程式碼如下:

<script src="https://test.azureedge.net/bundle.js" crossorigin></script>

而不會帶 cookie 就不會有 header too large 狀況,不過 server 需加 Access-Control-Allow-Origin 的 header 以允許跨來源請求。

實際案例#

書中有提幾個 cookie bomb 實際案例,例如:

篇幅關係就不細講,另還有 2019 年 filedescriptor 在 HITCON CMT 的演講 The cookie monster in your browsers 提出的漏洞,利用 XSS、cookie bomb 和 Google OAuth 登入流程達成的攻擊,提升了 cookie bomb 的影響力。

可從案例看出,利用 cookie 可作為執行 DoS 的手段,讓網頁無法載入,若結合其他漏洞,可再擴大影響力。


Reference:#

如有任何問題歡迎聯絡、不吝指教✍️