Cookie 的安全隱患

Cookie 固然方便,但畢竟它存在著安全隱患(security pitfalls),使得我們無法隨意地將極機密的資訊保存在 Cookie。

RFC 6265 section 8 提到了 Cookie 的安全注意事項,這都是在做身分驗證需要考慮的問題。

Ambient Authority

RFC 裡提到,Cookie 是一種 Ambient Authority 的形式。如果太依賴 Cookie 做為身分驗證手段的話,會有安全漏洞。主要是因為 User Agent 發送請求時,只要符合 Set-Cookie 的條件,就會夾帶當初設定的資訊。這可能會讓使用者在不知情下,以該使用者的身分執行某些攻擊者想騙使用者做的事。

這問題所引發最常見的攻擊手法即為 Cross-site request forgery,也縮寫作 CSRFXSRF。比方說,Apple 買 iPhone 11 的連結如下:

https://apple.com/buy/iphone11?to=miles

攻擊者只要到處散布下面這個連結到各大社群,只要有人登入狀態還未清除,又不小心點擊到,攻擊者就會收到一隻新的 iPhone 11。

https://apple.com/buy/iphone11?to=attacker

這例子對攻擊者來說,執行起來非常簡單;相對地,對服務器來說,即便做了傳輸層加密,同時也做了 Cookie 屬性設置,依然還是會讓攻擊者得逞。

解決此問題的基本概念是:不能完全依賴 Cookie 作為身分認證方法,必須要額外加上其他認證方法。比方說,在官方的輸入表單產生一個攻擊者無法取得的亂數驗證碼如下:

<input type="hidden" name="_csrf" value="A12DD3BA-2C9B-4D3E-BC3A-8B3143E45B83"/>

在接收表單的時候去確認此亂數驗證碼是剛剛產生的。這樣若是不小心點擊到攻擊者散布的連結,會發生驗證碼比對錯誤,即可避免使用者踩中攻擊者的陷阱。

Clear Text

除非有使用安全通道(secure channel),不然 Cookie 的內容會是用明文傳輸。如果明文傳輸就會有以下問題:

  1. 敏感資訊會暴露給竊聽者
  2. 中間人攻擊可以很輕易的竄改資訊
  3. User Agent 也可以很輕易的竄改送出的表頭資訊

RFC 裡的建議是,服務器應該對 Cookie 做加密(encrypt)和簽章(sign),即便是使用安全通道也是。只是即便加密和簽章,也無法防止重送攻擊。

需注意的是,如果有使用安全通道的話,則 Cookie 就得加上 Secure 屬性,不然安全通道的保護就沒有意義了。RFC 舉的例子是,攻擊者只要攔截 HTTP 請求,然後重導向使用者到沒有 Secure 屬性站台,並使用 HTTP 協定。即便站台沒有開啟 HTTP 連接埠,User Agent 還是會將該 HTTPS 的 Cookie 夾帶到 HTTP 的請求裡,攻擊者再攔截下來重送到該服務器上,就能偽造該使用者身分進入服務了。時序圖如下:

@startuml
participant Alice
participant Attacker
participant Bob
Alice -> Bob: Request
Bob -> Alice: Response: Set-Cookie: SID=31d4d96e407aad42
Alice --> Attacker: Intercept HTTP Request
Attacker --> Alice: Response: Redirect to Bob by HTTP
Alice --> Attacker: Intercept HTTP Request with Cookie
Attacker --> Bob: Request with Alice Cookie
@enduml

Session Identifier

所有狀態都放在 Cookie,並不是很恰當,一來 Cookie 有容量限制,二來有些機密資訊不適合被保存到 User Agent。因此有一種做法是,第一次來網站,就先發給 User Agent 一個 Cookie 與值,代表一個鑰匙。服務器拿到鑰匙就去打開背後對應的儲存空間,並把機器資料放裡這個儲存空間裡。這時只要儲存空間外面是接觸不到的,就能提高機密資訊的安全性。

時序圖如下:

@startuml
Alice -> Bob: Request: SID=31d4d96e407aad42
Bob -> Storage: Key: 31d4d96e407aad42
Storage -> Bob: Value: Alice secret
@enduml

只是即便如此,還是無法避免 CSRF 或重送攻擊等攻擊手法,而且並且會有其他的攻擊手法如下:

  • 猜測 Session ID(Session Prediction)
  • 竊取 Session ID(Session Hijacking)
  • 固定 Session ID(Session Fixation)

第一種最簡單好理解,當攻擊者猜到 Session ID 就可以立馬以該使用者的身分使用服務。提升安全性的做法是使用適合的亂數產生器與適當的長度,提升攻擊者猜測的難度。

第二種是偷別人的 Session ID,只要偷到了,就能代表該使用者使用服務。經典的攻擊手法像是 Cross-site scripting(XSS) 讓使用者在不知不覺中執行攻擊者的指令碼;而指令碼實際做的事可能就是把 Session ID 傳送給攻擊者的主機。提升安全性的做法是,使用安全通道加上適當的 Cookie 屬性(如 HttpOnly)與 Content Security Policy(CSP) 限制網頁執行 Javascript 的範圍。

第三種則是利用系統 Session ID 固定不變的機制,來欺騙使用者使用攻擊者的 Session ID 登入,使用者登入後,攻擊者即可用使用者登入過的 Session ID 進入系統。攻擊手法的時序圖如下:

@startuml
participant Alice
participant Attacker
participant Bob
Attacker --> Bob: Request
Bob --> Attacker: Response: Set-Cookie: SID=31d4d96e407aad42
Attacker --> Alice: Fake link to using SID=31d4d96e407aad42 login
Alice -> Bob: Request: Cookie: SID=31d4d96e407aad42
Bob -> Alice: Response: SID=31d4d96e407aad42 Login success
Attacker --> Bob: Request: Cookie: SID=31d4d96e407aad42
Bob --> Attacker: Response: SID=31d4d96e407aad42 Login success
@enduml

提升安全性的做法是:系統在未登入轉換到登入後,應該要原本的 Session ID 消滅(如上例的 SID=31d4d96e407aad42),並產生新的 Session ID。如此一來,攻擊者嘗試使用原本的 Session ID 將會得不到任何資料。

Weak Confidentiality

若想在 Cookie 放機敏資訊,RFC 這裡給的提醒是--千萬不要這麼做!Cookie 不能保證存在裡面的東西不會洩露出去。

RFC 裡面舉的例子就很清楚:雖然不同的 domain 或 path 可以設定不同的 Cookie,但不同的連接埠則會共用同一份 Cookie。這代表不能在同個 domain 上,執行兩個不能互相信任且不同連接埠的服務。

Weak Integrity

Cookie 對於主網域與子網域的資訊,無法保證其完整性。

RFC 裡提到的一個例子是:foo.example.com 可以設定 example.com 的 Cookie,而在 bar.example.com 在取得 Cookie 時,會無法確認這個 Cookie 是 foo 子網域設定的還是 bar 子網域設定的。在這前提下,foo 子網域就能利用這個漏洞對 bar 子網域發出攻擊。

雖然加密或簽章能保護資料不被竄改,但一樣還是會有重送攻擊的問題。

Reliance on DNS

簡介 Cookie 提了許多設定的例子,以及上面討論的某些問題,都是在 domain 上設定 Cookie,代表 Cookie 協定對 DNS 有某種程度的依賴關係。因此 DNS 只要被攻破,Cookie 的資料將會傳送到攻擊者的服務器裡。

小結

Cookie 設定非常方便,但也帶來許多資安問題。好在 RFC 對安全注意事項都會說明的非常清楚,在決定使用某項協定前,預先看過一輪是必要的,尤其是做身分驗證。

參考資料

傳輸層安全性協定 - 维基百科,自由的百科全书