Open aszx87410 opened 5 years ago
splicing (接合)那段的意思應該是指(以下是舉例不是翻譯),如果你的 cookie 裡面存了整個 session 的資料,而且那個 session data 又可以在用戶端被修改(例如 session data 直接存在 cookie 值卻又沒 sign 過),那這樣使用者(攻擊者)就可以任意地去組合你的 session data 的資料,然後就可以藉此產生你原先設計中沒預期的操作流程
比方說
{"step1": true}
在 session data 裡面{"step1": true}
去判斷使用者是不是做過第一步了,然後讓使用者操作第二步{"step1": true, "step2": true}
在 session data 裡面{"step3": true}
去判斷使用者是不是做過第二步了,然後讓使用者操作第三步{"step3": true}
去觸發一些異常行為,例如跳過第一部和第二步?如果這些 session data 放在伺服器端,而 cookie 裡面只放 session ID,這個問題就不可能會以 session data 被使用者端修改的形式發生
fbcid 看到真的滿煩的,每次想複製網址存到筆記中,都還要手動把 fbcid 給刪掉 =_= 看完這篇後,決定找 Chrome 的附加元件來把 fbcid 擋掉 XD
@arthow4n 感謝解釋!
比較令我困惑的應該是「splicing」這個詞,因為以你的舉例來說看起來像是修改而已?
從原文的說法: "splicing" together cookie content from two interactions with the server, which could cause the server to behave unexpectedly.
,以及 splicing 的原意「剪接」看起來,似乎比較像是把兩個 cookie 的內容剪貼之後合在一起?例如說 cookieA 的部份內容是從 cookieB 來的之類的,而不只是單純的修改這麼簡單。
所以我才會說這一段有點看不太懂,因為如果只是單純的修改,應該會用竄改或是修改這個詞,而不是 splicing。我覺得 splicing 本身就蘊含了修改,但多一個限制是「修改的內容必須從其他地方而來」。
以你舉的例子來看,如果只是單純的「猜猜看 step3 傳 true」能不能過關,我認為應該不太算是 splicing。隨便舉個例子好了,假設現在有個演唱會買票系統,第一關是虛擬排隊間,會發一個排隊號碼:{"queueNumber": 999}
,排完隊之後第二關到了選位頁面,系統會發給你一個選位 ID:{"seatID": "a12345"}
,今天正在排隊的 A 把已經排完隊的 B 的 cookie 接上去,完整內容就是:{"queueNumber": 1024, "seatID": "a12345"}
,又有 queueNumber 又有 seatID,但這兩個資料在 server 端毫無關聯,因此可能造成預期之外的行為,不確定 server 會怎麼處理這種資料。這個例子比較接近我認為的 splicing,但原文又沒有再延伸解釋所以我也不敢保證XD
@ayugioh2003 我原本也想找但每次都想說刪個網址也不會花太久,既然你幫忙找好了我也來裝一下好了XDD 感謝
我的理解是修改這件事其實來源是哪不重要,因為重點是最後伺服器可能會看到一組他沒辦法正常按照設計者的想法去處理的資料。伺服器其實也不會知道你從哪裡組出甚至猜出那筆有問題的 payload,一個有問題的值不管是從什麼樣子的想法做出來(自己猜出來或者從另外一個 cookie 接過來),對於伺服器看起來都一樣
... 會特別提到 Netscape 是因為 Cookie 這東西最早是 Netscape 自己實作的,只可惜我能找到的連結都死掉了,無緣看到 Netscape 的 Cookie 規範長什麼樣子。
我今天在 MDN 文件查看 JavaScript 發展歷史時,有看到 Netscape Navigator 3.0 的歷史存檔文件,裡頭有提到 Netscape cookies。我不確定這是不是就算是規範了 @@
附上相關連結
@ayugioh2003 感謝提供資料,後來我拿 rfc 裡面提到的標題去找,找到有人備份了:https://curl.haxx.se/rfc/cookie_spec.html
fbcid 看到真的滿煩的,每次想複製網址存到筆記中,都還要手動把 fbcid 給刪掉 =_= 看完這篇後,決定找 Chrome 的附加元件來把 fbcid 擋掉 XD
因為談到 fbcid 就去研究了一下目前 FB 的行為, 發現 FB 很詐,你 mouse hover 的時候顯示的是你原始分享的網址,
但是你點下去打開,或右鍵點擊想複製網址時,
就會把原始網址變成 l.facebook.com
為起頭的 redirect url,再讓你複製或打開。
ex: 原始: https://www.playpcesor.com/2021/06/Google-Drive-file-links.html
複製完變成:
https://l.facebook.com/l.php?u=https%3A%2F%2Fwww.playpcesor.com%2F2021%2F06%2FGoogle-Drive-file-links.html%3F👉fbclid👈藏在這%3DIwAR0eYmrOmdLAknCODsYzstaSbL38LEoBRUGwY7ebVNWpHU-neHmeAf_URco&h=AT0r4hDQEOEqwJfkeS3eNd0UuyA2zV8QuoedrLw5BLsPC5Kco_O1lb-fpEFssUZZ-Dukx1J1oe8Kgmi2x7vX9JHzvoZiwuIQyn5vfoY_EWuWn-6SJJ522hjJ1B68A_eeG3pAqF0WVRz6F5EQmXYYPwU&__tn__=%2CmH-y-R&c[0]=AT3s5bNpIEM9BxeUUG0Y2O2A6-7JKzRrop-1NReiFZVdTSmT2kBSg9jphCkyN13rNm-uRkutDlbQDIqbwuDwv7VOwUhiWaTEXvu1kbQRVAWMbYZcC_MIYj4L9g64YCK5PVzCz4FOBdZmYnsOr44IO08H3h8XPWVhHc9fPjxfP0zU5oUtuTXD1sNG9Dq9H3tqpuVNnoxZACPBI1CJrWSr2G7gQ2049H8n7GG5eZaWlLiHVICBdokPc_UtMTyrlZETww
裡面還是藏了 fbcid
, 且造成一些去除 fbcid
的 chrome extension 失效。
一般擋廣告軟體也沒辦法擋,真的挺厲害的。
我知道跟主題是有些離題了啦,就當聊聊天囉。
給大家參考, 我找到一個可以確實避免 FB 這種偷天換日改連結行為的 Chrome Extension: Tracking & Ad Removal for Facebook™
我自己也有寫一個針對 Facebook tracking parameter 的擴充套件: https://addons.mozilla.org/zh-TW/firefox/addon/facebook-dont-track-me/
火狐的話還有Mozilla官方的: https://addons.mozilla.org/zh-TW/firefox/addon/facebook-container/
網址列問題也可搭配: https://addons.mozilla.org/zh-TW/firefox/addon/neat-url/
前言
這是一系列共三篇的文章,我稱之為 Session 與 Cookie 三部曲。系列文的目標是想要由淺入深來談談這個經典議題,從理解概念一直到理解實作方式。這是系列文的第二篇,三篇的完整連結如下:
在上一篇裡面,我們提到了 Session 的意思:
其實在寫這系列的時候,「Session 最明確的定義是什麼」困擾了我一陣子,而且我到現在還不能完全肯定到底怎樣才是對的。在我心中有兩個解釋都滿合理的,第一個解釋就是上一篇跟大家講的,Session 是一種讓 Request 變成 stateful 的「機制」,而 Session 的第二種解釋(也是比較貼近英文原文的解釋),就是「具有狀態的一段期間」,或者是「上下文」,所以在 Session 裡面的東西可以放在一起看。
有一種說法認為 Session 的原意的確是第二種,但是在 Web 領域中 Session 轉變成了一種「機制」,所以兩個意思都通。但我自己其實是比較傾向第二種才是唯一正確的解釋方法,從頭到尾第二種都是對的,第一種則是誤解。
舉個例子來說,如果你有用過 Google Analytics,裡面有個名詞叫做「工作階段」,英文原名就叫做 Session,而 Google 對 Session 的解釋是這樣的:
(來源:Analytics (分析) 定義網頁工作階段的方式)
它把 Session 定義為「指定期間內在網站上發生的多項使用者互動」,並且說可以把 Session 當作一個容器(Container)。雖然說 Google Analytics 的 Session 跟 Web 技術上所使用的 Session 本來就不同,但我認為多少可以互相參考。而這個 Session 的定義與我前面所說的「具有狀態的一段期間」或者是「上下文」其實是雷同的。
那為什麼儘管我比較偏向這個定義,卻在上一篇裡面隻字不提,還把 Session 定義成我眼中的「誤解」?
第一個原因是搞不好兩種解釋都說得通,所以有可能兩個都是對的。第二個原因是我所認為的 Session 精確定義非常不好解釋,因為概念太抽象了。我認為若是提了這個解釋,只會把你對 Session 的理解越搞越亂,因此上一篇才沒有提到這個。第三個原因是我認為解釋成機制也可以,而且比較好理解,就算它真的是錯誤的,造成的影響也沒那麼大。
總之呢,我認為對完全沒有基礎的人來說,把 Session 理解成一種機制就可以了。但是對於像我這種想要追根究底的人來說,我想知道的是最正確的理解,而且必須是有憑有據的。
要怎樣才叫做有憑有據呢?去看當年談論 Cookie 與 Session 的 RFC 文件應該夠有憑有據了吧?RFC 文件可是要經歷過一系列討論與審核之後才能誕生,我想不到有哪邊的解釋能比 RFC 更具有說服力。
在這篇文章中我們會來稍微讀一下三份 RFC:
為什麼要讀三份呢?因為這三份都是跟 Cookie 相關的文件,2109 是最早的一份,後來出現一些問題所以被新的 2965 取代,過了十年後有了 6265,是目前最新的標準。
我認為讀東西從最早期的時候開始讀能夠事半功倍,因為東西應該會最少,理解上也比較容易,找資料也好找。例如說要讀 React 原始碼我會推薦從最早的 0.xx 版本開始讀,讀 ECMAScript 也可以從 ES3 開始,還可以順便知道演進的過程。
前情提要大概就到這邊了,本文的目標就是來讀 RFC,看看裡面是怎麼說 Cookie 與 Session 的。裡面我會對原文做一些翻譯,但畢竟翻譯是項專業,我翻的很差而且一定有錯誤,拜託大家還是要看原文,翻譯只能當作輔助。如果有哪邊錯的很離譜歡迎指出,我會十分感謝。
RFC 2109
RFC 2109 發佈於 1997 年 2 月,那是個還沒有 Ajax 的年代,是個 Netscape 還稱霸瀏覽器市場的年代。
這份文件的標題叫做:「HTTP State Management Mechanism」,直翻就是 HTTP 狀態管理機制。
先來看摘要的部分:
(每次翻譯翻一翻就會不想翻了...因為總覺得自己翻譯得不夠精確,翻譯真滴難)
摘要寫得很明確了,簡單來說就是引入 Cookie 與 Set-Cookie 兩個 Header 來建立 Session。會特別提到 Netscape 是因為 Cookie 這東西最早是 Netscape 自己實作的,只可惜我能找到的連結都死掉了,無緣看到 Netscape 的 Cookie 規範長什麼樣子。
再來第二個部分 TERMINOLOGY 就是規定一些專有名詞的用法,可以稍微掃過去就好,重點在第三個部分 STATE AND SESSIONS:
這邊對於 Session 的定義就如同我前面所講的那樣,Session 是「具有狀態的一段期間」,或者是「上下文」,就是上面所提到的 context,在這個 context 裡面的 Request 與 Response 可以放在一起看,於是他們之間就有了狀態。
這邊就是稍微介紹了一下 Session 的特性而已。若是我們把 Session 理解為是一種「機制」,那該如何解釋上面的段落?「每個 Session 機制都是相對短暫的」?,聽起來有點怪怪的,所以這也是為什麼我會說 Session 當作機制來解有一點奇怪。
接下來第四個章節很多部分都是在講那些 Header 的規格,這邊我們跳過不看,只節選幾個我認為比較重要的段落出來:
簡單來說就是你把伺服器把狀態放在 Set-Cookie 這個 Header 裡面送去瀏覽器,而瀏覽器在之後的 Request 把 Cookie 帶上去,這樣子就成立一個 Session 了,因為後續的 Request 就有了狀態。
再來可以看一下第五個章節 EXAMPLES 的部分,我們來看其中一個例子,這邊的例子比較簡單,我就直接翻中文了,想看原文可以到這裡:5.1 Example 1。
第一步:瀏覽器 -> 伺服器
使用者透過表單登入。
第二步:伺服器 -> 瀏覽器
登入成功,伺服器發送 Set-Cookie Header 並設置資訊,儲存了使用者的身份。
第三步:瀏覽器 -> 伺服器
使用者把某個物品加入購物車。
第四步:伺服器 -> 瀏覽器
伺服器再設置一個 Cookie 來儲存剛剛加入購物車的東西。
第五步:瀏覽器 -> 伺服器
使用者利用表單選擇商品的運送方式。
第六步:伺服器 -> 瀏覽器
設置新的 Cookie 來儲存運送方式。
第七步:瀏覽器 -> 伺服器
使用者選擇結帳。
第八步:伺服器 -> 瀏覽器
根據瀏覽器帶上來的 Cookie 得知用戶資料、購買品項以及運送方式,交易完成!
上面這個範例大致說明了 Cookie 的運作方式,就是透過伺服器傳送 Set-Cookie 這個 header 來設置資訊,並且靠瀏覽器傳送 Cookie header 把之前儲存的資訊一併帶上來,這樣子就有了狀態,就開啟了一段 Session。
接著來看第六部分:IMPLEMENTATION CONSIDERATIONS,講到實作上的一些考量,這邊一樣截取片段:
其實這種方式就是我們在上一篇所提到的兩個不同的方法:Cookie-based session 以及 SessionID,前者的缺點就是存太多東西會變得笨重,後者則是需要把狀態放在 Server。
兩種方式其實各有優劣,但比較常使用的還是 SessionID 那種方式,也就是原文提到的:「session information to be a key to a server-side resource」。
好,其他都是有關安全性或是跟隱私有關的部分,跟我們這篇要談的議題有點差異,因此我就不特別提了。
讓我們先來整理一下上面所看到的東西。
首先,Cookie 就是為了要建立 Session 而生的,因為在這之前要建立 Session 只能透過我上一篇提到的那些方式,例如說用網址列帶資訊,或者是在 form 裡面放一個 hidden 的欄位。為了簡化這些行為才有了 Cookie。
而實際方式就是 Server 回傳 Set-Cookie 的 header,User agent 把這些資訊儲存起來之後,在後續的 Request 都加上一個 Cookie header,這就是我們上一篇中所提到的「紙條」,每次都會帶著這個紙條,就讓 Request 之間有了狀態。
至於要在 Cookie 裡放什麼狀態都行,但如果放的東西太多可以考慮把這些狀態移到 Server 去,只在 Cookie 裡放一個可以對應的 ID。這就是我們之前所說的 Session ID 與 Session Data。
RFC 2965
RFC 2965 誕生於 2000 年,不過它的內容跟 RFC 2109 其實相去不遠,大概有八成的內容都是一樣的。
為什麼呢?
在 RFC 2109 出來之後不久他們發現了 IE3 與 Netscape Navigator3 對於這份「新的」Cookie 標準(舊的指的是 Netscape 原本自己的那套規範)實作不同,例如說以下這一段:
在 IE 裡面會把 Cookie 設置成這樣:
Cookie: Max-Age=15552000
,在 Netscape Navigator 裡面則是我們預期的:Cookie: xx="1=2\&3-4".
,同一段 Header 卻產生了不同的結果,於是他們就要想辦法來修正這個行為。最後就有了 RFC 2965 的出現,解決方式是引入了兩個新的 Header:Cookie2 跟 Set-Cookie2,其餘部分都與 RFC 2109 差不多。
因此 2965 我們可以跳過不看,直接來看最新的 RFC 6265。
RFC 6265
RFC 6265 是 2011 年出現的文件,跟上一份相隔 11 年。
而這份文件可以說是把 Cookie 規則再翻新了一遍,修改的幅度很大,在 Introduction 裡面就有說明了:
有些我們現在在用的屬性,在 RFC 2965 都是不存在的,例如說 HttpOnly。這份規範把很多東西都定義的比較明確,有興趣的讀者可以自己去看。
接著我們來看一些有趣的地方好了,第一個是 3.1 Examples,裡面提到的範例直接使用了 SessionID:
底下還有提供更完整的範例,但有點長我就不翻了。其實我很推薦大家自己把這整份文件都看完,因為這整份文件定義的就是現在我們在使用的 Cookie 規格(基本上是啦,雖然還是有一點出入),你可以從規格裡面知道最正確的資訊。
例如說:
這邊我們就可以看到規格與實作的差異。規格只說了「什麼是安全由 user agent 自己定義」,而沒有強制規範說「一定要在 HTTPS 的時候才能傳輸」。所以一般我們所認知的「Secure 就是代表一定要 HTTPS 才會被傳送」其實指的是主流瀏覽器的實作,而不是 RFC 的規範。
所以如果想完整回答「設置 Secure 屬性代表什麼」這個問題,可以這樣回答:
再來我們來看跟我們切身相關的一個東西:
其實當初在 RFC 2109 就有談論過第三方 cookie 的議題,只是那時候叫做 Unverifiable Transactions,看到的時候我有嚇了一跳,在 1997 年剛有 cookie 的時候就已經提到了第三方 cookie 的問題。
畢竟這個問題感覺在近期才比較被廣泛討論,而且在近幾年 Safari 跟 Firefox 才預設阻擋第三方 cookie。甚至連 Facebook 之後的解法 dynamic URLs 都早已出現在 RFC 6265 上面(我超討厭那串 fbcid...)。
最後我們來看一些跟安全性相關的東西,都在 8.Security Considerations 裡面:
原文對固定 Session(Session fixation)的說明沒有很清楚,有興趣的朋友可以參考 HTTP Session 攻擊與防護,這篇講得比較清楚一點。
簡單來說就是讓受害者用你指定的 sessionID 登入,所以在 Server 端這個 sessionID 就會跟受害者的帳號綁在一起。接著你再用同樣的 sessionID,就可以用受害者的身份登入並且使用網站。
接著我們再來看另外一個安全性問題:
上面這一段在 4.1.2.5 The Secure Attribute 其實也有提到:
大意就是說 Secure 屬性沒辦法保障 cookie 的完整性。攻擊者可以從 HTTP 覆蓋掉 HTTPS 的 cookie。
看到這邊的時候我心頭一驚,這個不就是在講我之前寫過的:我遇過的最難的 Cookie 問題嗎?現在我也終於知道為什麼 Safari 跟 Firefox 都沒有擋這種行為,因為在規格裡面並沒有要求你一定要擋。
至於 Chrome 的話,它的實作參考了幾個不同的 RFC,在負責管理 Cookie 的 CookieMonster 裡面有寫到:
在 CookieMonster.cc 裡面也有寫到:
文中所提到的文件還在草稿階段,標題是:「Deprecate modification of 'secure' cookies from non-secure origins」,是由 Google 的員工所發起的草稿。在 Introduction 的地方寫的很明確了:
大意就是說跟我們剛剛在 RFC 6265 的 Section 8.5 與 8.6 看到的一樣,由於一些歷史因素,secure 的 cookie 可以被 non-secure 的來源蓋掉。而這份文件就是要試著阻止這種行為。
看到這邊,與 Session 跟 Cookie 相關的文件差不多都讀完了,讓我們做個簡單的總結。
總結
回到最開始的問題:到底 Session 是什麼?
從 RFC 裡面提到的各種 Session 相關的字眼,我會認為 Session 就是它英文的原意之一,代表著:「具有狀態的一段期間」或者是「上下文」,所以你想要開啟或是建立一個 Session,必要條件就是先有一個機制來建立及保留狀態。
這也是為什麼 Cookie 的 RFC 標題為:HTTP State Management Mechanism,狀態管理機制。在 Cookie 還沒出現以前,一樣可以建立 Session,可以把狀態資訊放在網址列上面或是藏在 form 表單中。但 Cookie 出現以後建立 Session 變成一件更容易的事,只要使用 Set-Cookie 與 Cookie 這兩個 header 就好了。
建立 Session 之後,所儲存的狀態就叫做 Session information,可以翻作 Session 資訊。若是選擇把這些資訊存在 Cookie 裡面,就叫做 Cookie-based session;還有另一種方法則是在 Cookie 裡面只存一個 SessionID,其他的 Session 資訊都存在 Server 端,靠著這個 ID 把兩者關聯起來。
除了 Session 以外,我們也在 RFC 裡面看見一些有趣的東西,例如說第三方 Cookie 的隱私疑慮以及與 Cookie 相關的安全性問題。這些也能加深你對於 Cookie 的理解。
在結束以前,我誠心推薦一篇文章:HTTP Cookies: Standards, Privacy, and Politics,網頁右邊可以下載 PDF 來看。這篇文章的作者就是 RFC 2109 與 2965 的作者。文章裡面把 Cookie 出現的歷史以及當初發生的事講的一清二楚,強烈建議大家都可以花點時間來看這篇文章,可以深入地理解 Cookie 與 Session 早期的歷史。
最後,別忘了這是系列文的第二篇,下一篇我們會來看一些主流框架如何處理 Session。
三篇的完整連結如下: