24 年了,終於有人發現 curl 的這個 Bug 了

這是一個關於 cookie、互聯網代碼和 CVE(通用漏洞披露)的故事。

本文最初發佈於 Daniel Stenberg 的個人博客。

curl 作者 Daniel Stenberg 近日在個人博客分享了一個存在 23.9 年的 curl 漏洞。curl 是常用的命令行工具,用來請求 Web 服務器,於 1997 年首次發行。

據 Stenberg 透露,這個漏洞是在 curl 發佈後的第 201 天引入的,但是直到第 8930 天,漏洞才修復好。一個持續了 23.9 年的漏洞背後有着怎樣的故事?

一切還得從 1998 年說起。

curl 4.9 與 cookie

1998 年 10 月,Stenberg 帶領團隊推出了 curl 4.9 版本。當時,聽過或用過 curl 的人還少得可憐。幾個月之後,curl 官網才宣佈新版本的下載量達到了 300。那時,無論從何種意義上講, curl 都還很小衆。

curl 4.9 作爲第一個帶有 “cookie 引擎” 的版本,可以接收 HTTP cookie、解析、識別,並在後續的請求中把 cookie 正確地返回。在 curl 中,處理 cookie 的大部分代碼都是 Stenberg 編寫的。

那會,cookie  還沒有明確的規範,僅有的一份描述 cookie 工作原理的規範,是一份由 Netscape 管理的文檔 cookie_spec(感興趣的同學可以戳鏈接查看文檔副本:https://curl.se/rfc/cookie_spec.html)。這份文檔並不完善,有不少信息需要通過查看其它客戶端才能瞭解到。

Stenberg 在實現處理 cookie 的代碼時,就是參考了這份文檔以及當時瀏覽器的大致處理方式。

此後十年,IETF(互聯網工程任務組)一直在努力創建 cookie 規範,但均以失敗而告終。這些早期 cookie 規範的創建者可能覺得,他們創建了標準,世界就會情不自禁地遵守它們,但事實並非如此。cookie 的特殊之處在於,有很多不同的作者、代碼庫和網站實現了它。因此,很難從根本上改變它們的工作方式。

直到 2011 年,cookie RFC 正式發佈了,它記錄並解釋了 cookie 實際的使用方式,這可以說是真正意義上的 cookie 規範。Stenberg 本人也參與了規範的制定過程,並在其中闡述了自己的觀點和意見。對於這份規範的內容,雖然 Stenberg 並不完全贊同,但與此前的各種 cookie 規範相比,cookie RFC 的確是一個巨大的進步。

cookie 雙重語法帶來的挑戰

一開始,新的 cookie 規範並沒有給 Stenberg 造成困擾,但很快,規範的特殊編寫方式讓 Stenberg 倍感頭疼:它針對服務器如何發送 cookie 提供了一種字段語法,而針對客戶端應該接受什麼樣的 cookie 提供了另一種語法。也就是說,同樣的 cookie,需要兩種語法。

這有兩個很直接的缺點:

  1. 規範很難閱讀。你很容易就停留在其中一種語法上,以爲那就是適合自己用例的,但卻沒有意識到角色描述是錯誤的。

  2. 定義如何發送 cookie 的語法其實並不重要,因爲如何接收和處理 cookie 都是由客戶端決定的。現有的大型 cookie 解析器(瀏覽器)有一定程度的自由決定自己接受什麼,所以沒人注意,也沒人關心服務器是否嚴格遵守了規範中的語法。與此同時,cookie 規範也在持續更新。從幾年前開始,IETF 就一直在修訂和更新 2011 年的 cookie 規範,計劃將世界上一些已實際投入使用的 cookie 擴展添加到規範中。這項 cookie 規範更新工作被稱爲 6265bis。

curl 也同步進行更新,以確保符合 RFC 6265bis 草案版本的規定。

但是,雙重語法仍然是 cookie 規範文檔中懸而未決的問題。

隨着時間的推移,cookie 的發展變得緩慢。在過去的幾十年裏,HTTP 規範也就更新了有限的幾次,但值得一提的是,HTTP 服務器實現已經實施了更嚴格的解析策略:

如果傳入的 HTTP 請求看上去 “非法” 或格式不正確,那麼 HTTP 服務器就會提前拒絕,把它們擋在門外。對於請求中的控制代碼尤其如此。如果你試圖將一個包含控制代碼(這裏的控制代碼指的是介於 1 到 31 之間的字節值,不包括 9,9 是 TAB)的請求發送到一個相當新的 HTTP 服務器,那麼服務器很可能會拒絕,並返回 400 響應代碼。從 2016 年 12 月發佈的 2.4.25 版本開始,HTTP 服務器 Apache httpd 就默認啓用了此行爲。最新版本的 Nginx 似乎也是這樣做的。

如果是現在設計 cookie,那麼肯定會有所不同。

設置 cookie 的網站把 cookie 發送到客戶端,對於其發送的每個 cookie,它都會設置多個屬性。尤其是當需要客戶端發回 cookie 時,它會設置匹配參數。

在 cookie 的這些參數中,其中有一個是 domain,客戶端發送 cookie 時要匹配它。服務器 www.example.com 可以設置 cookie 的有效範圍爲整個 example.com 域,這時,客戶端在訪問 second.example.com 時也會發送 cookie。也就是說,服務器可以將 cookie 設置爲適用於 “兄弟站點”。

值得一提的是,1998 年添加到 curl 中的 Cookie 代碼在接受內容方面相當自由,當然,多年來也經過了不少調整和完善,不過它始終與現實世界的網站保持了兼容。對於那部分代碼,Stenberg 修改的主要動力始終是爲了使 curl 的 Cookie 處理方式與其他已有的使用 cookie 的代理保持基本一致,並可以互操作。

curl 的 Bug 詳情與修復方案

2022 年 6 月底,Stenberg 收到了一份報告,報告懷疑 curl 中存在安全問題。正是這份報告促使 curl 發佈了 CVE-2022-35252。

事實證明,源於 1998 年的舊 cookie 代碼,會接受包含控制代碼的 cookie。控制代碼可以是名稱或內容的一部分,如果用戶啓用了 “cookie 引擎”,那麼 curl 就會存儲那些 cookie,並在後續的請求中將它們發送回來。

例如,curl 會接受下面這樣的 cookie:

Set-cookie: name^a=content^b; domain=.example.com

^a 和 ^b 表示控制代碼。由於域可以將 cookie 標記爲適用於其他主機,、所以發送到域中所有主機的請求都會包含這個 cookie。

當 curl 將類似那樣的一個 cookie 發送到 HTTP 服務器時,它的外發請求中會包含下面這樣一個 header 字段:

cookie: name^a=content^b

對此,Apache httpd 及其他服務器的默認配置都會返回 400。一個腳本或應用程序在收到這樣的 cookie 後,如果後續的請求中還繼續發送 cookie,就會遭到拒絕。

Stenberg 覆盤後發現,cookie 規範 RFC 6265 5.2 節確實說了,客戶端應該丟棄包含控制代碼的 cookie,但這部分對用戶來說理解起來比較難,需要對文檔有深入的研究才能發現。此外,規範並沒有提及 “控制代碼” 或是字節值範圍。

Stenberg 認爲,要弄清楚主流瀏覽器是怎麼做的還是比較容易的,因爲它們的源代碼很容易獲得。事實證明,Chrome 和 Firefox 都已經忽略了包含以下任何字節的傳入 cookie:

%01-%08 / %0b-%0c / %0e-%1f / %7f

其中不包含 %09(TAB)和 %0a / %0d(行結束符)。

Bug 修復方面,Stenberg 表示,curl 的修復補丁處理方式非常簡單:拒絕包含一個或多個禁用字節值的 cookie 字段。Stenberg 認爲,這種修改基本是沒有風險的。

寫在最後

推算起來,有漏洞的代碼從 curl 4.9 版本開始就一直存在,curl 7.85.0 版本才完成修復。整個歷程有 8729 天(23.9 年)。也就是說,這個 Bug 是在項目發佈的第 201 天引入的,到第 8930 天才修復。

Stenberg 認爲,代碼在發佈時是沒什麼問題的,並且在用戶的使用過程中,也基本沒有產生什麼問題。它的問題出在,HTTP 服務開始拒絕可能的惡意 HTTP 請求時。如此一來,這段代碼就變成了一種拒絕服務,這或多或少會帶來一些副作用。

或許,這個 Bug 誕生於 RFC 6265 發佈之時。或許,它誕生於 HTTP 服務器開始拒絕這些請求時。不管怎樣,這個 Bug 創造了一個新的項目記錄:它是第四個被發現之前存在了 8000 多天的 Bug。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/3H2gzCaSOFQkH_0rELgsQg