- 原文地址:Securing Cookies in Go
- 原文做者:Jon Calhoun
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:lsvih
- 校對者:tmpbook, Yuuoniy
在我開始學習 Go 語言時已經有一些 Web 開發經驗了,可是並無直接操做 Cookie 的經驗。我以前作過 Rails 開發,當我不得不須要在 Rails 中讀寫 Cookie 時,並不須要本身去實現各類安全措施。php
瞧瞧,Rails 默認就本身完成了大多數的事情。你不須要設置任何 CSRF 策略,也無需特別去加密你的 Cookie。在新版的 Rails 中,這些事情都是它默認幫你完成的。html
而使用 Go 語言開發則徹底不一樣。在 Golang 的默認設置中,這些事都不會幫你完成。所以,當你想要開始使用 Cookie 時,瞭解各類安全措施、爲何要使用這些措施、以及如何將這些安全措施集成到你的應用中是很是重要的事。但願本文能幫助你作到這一點。前端
注意:我並不想引發關於 Go 與 Reils 二者哪一種更好的論戰。二者各有優勢,但在本文中我但願能着重討論 Cookie 的防禦,而不是去爭論 Rails 和 Go 哪一個好。node
在進入 Cookie 防禦相關的內容前,咱們必需要理解 Cookie 到底是什麼。從本質上說,Cookie 就是存儲在終端用戶計算機中的鍵值對。所以,使用 Go 建立一個 Cookie 須要作的事就是建立一個包含鍵名、鍵值的 http.Cookie 類型字段,而後調用 http.SetCookie 函數通知終端用戶的瀏覽器設置該 Cookie。react
寫成代碼以後,它看起來相似於這樣:android
func someHandler(w http.ResponseWriter, r *http.Request) {
c := http.Cookie{
Name: "theme",
Value: "dark",
}
http.SetCookie(w, &c)
}複製代碼
http.SetCookie
函數並不會返回錯誤,但它可能會靜默地移除無效的 Cookie,所以使用它並非什麼美好的經歷。但它既然這麼設計了,就請你在使用這個函數的時候必定要牢記它的特性。ios
雖然這好像是在代碼中「設定」了一個 Cookie,但其實咱們只是在咱們返回 Response 時發送了一個 "Set-Cookie"
的 Header,從而定義須要設置的 Cookie。咱們不會在服務器上存儲 Cookie,而是依靠終端用戶的計算機建立與存儲 Cookie。git
我要強調上面這一點,由於它存在很是嚴重的安全隱患:咱們不能控制這些數據,而終端用戶的計算機(以及用戶)才能控制這些數據。github
當讀取與寫入終端用戶控制的數據時,咱們都須要十分謹慎地對數據進行處理。惡意用戶能夠刪除 Cookie、修改存儲在 Cookie 中的數據,甚至咱們可能會遇到中間人攻擊,即當用戶向服務器發送數據時,另有人試圖竊取 Cookie。golang
根據個人經驗,Cookie 相關的安全性問題大體分爲如下五大類。下面咱們先簡單地看一看,本文的剩餘部分將詳細討論每一個分類的細節問題與解決對策。
1. Cookie 竊取 - 攻擊者會經過各類方式來試圖竊取 Cookie。咱們將討論如何防範、規避這些方式,可是歸根結底咱們並不能徹底阻止設備上的物理類接觸。
2. Cookie 篡改 - Cookie 中存儲的數據能夠被用戶有意或無心地修改。咱們將討論如何驗證存儲在 Cookie 中的數據確實是咱們寫入的合法數據
3. 數據泄露 - Cookie 存儲在終端用戶的計算機上,所以咱們須要清楚地意識到什麼數據是能存儲在 Cookie 中的,什麼數據是不能存儲在 Cookie 中的,以防其發生數據泄露。
4. 跨站腳本攻擊(XSS) - 雖然這條與 Cookie 沒有直接關係,可是 XSS 攻擊在攻擊者能獲取 Cookie 時危害更大。咱們應該考慮在非必須的時候限制腳本訪問 Cookie。
5. 跨站請求僞造(CSRF) - 這種攻擊經常是因爲使用 Cookie 存儲用戶登陸會話形成的。所以咱們將討論在這種情景下如何防範這種攻擊。
如我前面所說,在下文中咱們將分別解決這些問題,讓你最終可以專業地將你的 Cookie 裝進保險櫃。
Cookie 竊取攻擊就和它字面意思同樣 —— 某人竊取了正經常使用戶的 Cookie,而後通常用來將本身假裝成那個正經常使用戶。
Cookie 一般是被如下方式中的某種竊取:
阻止中間人攻擊的終極方式就是當你的網站使用 Cookie 時,使用 SSL。使用 SSL 時,因爲中間人沒法對數據進行解密,所以外人基本上沒可能在請求的中途獲取 Cookie。
可能你會以爲「哈哈,中間人攻擊不太可能…」,我建議你看看 firesheep,這個簡單的工具,它足以說明在使用公共 wifi 時竊取未加密的 Cookie 是一件很輕鬆的事情。
若是你想確保這種事情不發生在你的用戶中,請使用 SSL!試試使用 Caddy Server 進行加密吧。它通過簡單的配置就能投入生產環境中。例如,你可使用下面四行代碼輕鬆讓你的 Go 應用使用代理:
calhoun.io {
gzip
proxy / localhost:3000
}複製代碼
而後 Caddy 會爲你自動處理全部與 SSL 有關的事務。
防範經過訪問硬件來竊取 Cookie 是十分棘手的事情。咱們不能強制咱們的用戶使用高安全性系統,也不能逼他們爲電腦設置密碼,因此總會有他人坐在電腦前偷走 Cookie 的風險。此外,Cookie 也可能被病毒竊取,好比用戶打開了某些釣魚郵件時就會出現這種狀況。
不過這些都容易被發現。例如,若是有人偷了你的手錶,當你發現表不在手上時你立馬就會注意到它被偷了。然而 Cookie 還能夠被複制,這樣任何人都不會意識到它已經丟了。
雖然不是萬無一失,但你仍是能夠用一些技術來猜想 Cookie 是否被盜了。例如,你能夠追蹤用戶的登陸設備,要求他們從新輸入密碼。你還能夠跟蹤用戶的 IP 地址,當其在可疑地點登陸時通知用戶。
全部的這些解決方案都須要後端作更多的工做來追蹤數據,若是你的應用須要處理一些敏感信息、金錢,或者它的收益可觀的話,請在安全方面投入更多精力。
也就是說,對於大多數只是做爲過渡版本的應用來講,使用 SSL 就足夠了。
請直面這種狀況 —— 可能有一些混蛋忽然就想看看你設的 Cookie,而後修改它的值。也可能他是出於好奇才這麼作的,可是仍是請你爲這種可能發生的狀況作好準備。
在一些情景中,咱們對此並不在乎。例如,咱們給用戶定義一種主題設置時,並不會關心用戶是否改變了這個設置。當這個 Cookie 過時時,就會恢復默認的主題設置,而且若是用戶設置其爲另外一個有效的主題時咱們可讓他正常使用那個主題,這並不會對系統形成任何損失。
可是在另外一些狀況下,咱們須要格外當心。編輯會話 Cookie 冒充另外一個用戶產生的危害比改個主題大得多。咱們毫不想看到張三僞裝本身是李四。
咱們將介紹兩種策略來檢測與防止 Cookie 被篡改。
對數據進行數字簽名,即對數據增長一個「簽名」,這樣能讓你校驗數據的可靠性。這種方法並不須要對終端用戶的數據進行加密或隱藏,只要對 Cookie 增長必要的簽名數據,咱們就能檢測到用戶是否修改數據。
這種保護 Cookie 的方法原理是哈希編碼 —— 咱們對數據進行哈希編碼,接着將數據與它的哈希編碼同時存入 Cookie 中。當用戶發送 Cookie 給咱們時,再對數據進行哈希計算,驗證此時的哈希值與原始哈希值是否匹配。
咱們固然不會想看到用戶也建立一個新的哈希來欺騙咱們,所以你可使用一些相似 HMAC 的哈希算法來使用祕鑰對數據進行哈希編碼。這樣就能防範用戶同時編輯數據與數字簽名(即哈希值)。
JSON Web Tokens(JWT) 默認內置了數字簽名功能,所以你可能對這種方法比較熟悉。
在 Go 中,可使用相似 Gorilla 的 securecookie 之類的 package,你能夠在建立 SecureCookie
時使用它來保護你的 Cookie。
// 推薦使用 32 字節或 64 字節的 hashKey
// 此處爲了簡潔故設爲了 「very-secret」
var hashKey = []byte("very-secret")
var s = securecookie.New(hashKey, nil)
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
encoded, err := s.Encode("cookie-name", "cookie-value")
if err == nil {
cookie := &http.Cookie{
Name: "cookie-name",
Value: encoded,
Path: "/",
}
http.SetCookie(w, cookie)
fmt.Fprintln(w, encoded)
}
}複製代碼
而後你能夠在另外一個處理 Cookie 的函數中一樣使用 SecureCookie 對象來讀取 Cookie。
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
if cookie, err := r.Cookie("cookie-name"); err == nil {
var value string
if err = s.Decode("cookie-name", cookie.Value, &value); err == nil {
fmt.Fprintln(w, value)
}
}
}複製代碼
以上樣例來源於 www.gorillatoolkit.org/pkg/securec….
注意:這兒的數據並非進行了加密,而只是進行了編碼。咱們會在「數據泄露」一章討論如何對數據進行加密。
這種模式還須要注意的是,若是你使用這種方式進行身份驗證,請遵循 JWT 的模式,將登陸過時日期和用戶數據同時進行簽名。你不能只憑 Cookie 的過時日期來判斷登陸是否有效,由於存儲在 Cookie 上的日期並未通過簽名,且用戶能夠建立一個永不過時的新 Cookie,將原 Cookie 的內容複製進去就獲得了一個永遠處於登陸狀態的 Cookie。
還有一種解決方案能夠隱藏數據並防止用戶造假。例如,不要這樣存儲 Cookie:
// 別這麼作
http.Cookie{
Name: "user_id",
Value: "123",
}複製代碼
咱們能夠存儲一個值來映射存在數據庫中的真實數據。一般使用 Session ID 或者 remember token 來做爲這個值。例如咱們有一個名爲 remember_tokens
的表,這樣存儲數據:
remember_token: LAKJFD098afj0jasdf08jad08AJFs9aj2ASfd1
user_id: 123複製代碼
在 Cookie 中,咱們僅存儲這個 remember token。若是用戶想僞造 Cookie 也會無從下手。它看上去就是一堆亂碼。
以後當用戶要登錄咱們的應用時,再根據 remember token 在數據庫中查詢,肯定用戶具體的登陸狀態。
爲了讓此措施正常工做,你須要確保你的混淆值有如下特性:
這種方法也有一個缺點,就是在用戶訪問每一個須要校驗權限的頁面時都得進行數據庫查詢。不過這個缺點不多有人注意,並且能夠經過緩存等技術來減少數據庫查詢的開銷。這種方法的升級版就是 JWT,應用這種方法你能夠隨時使會話無效化。
注意:儘管目前 JWT 收到了大多數 JS 框架的追捧,但上文這種方法是我瞭解的最經常使用的身份驗證策略。
在真正出現數據泄露前,一般須要另外一種攻擊向量 —— 例如 Cookie 竊取。然而仍是很難去正確地判斷並提防數據泄露的發生。由於僅僅是 Cookie 發生了泄露並不意味着攻擊者也獲得了用戶的帳戶密碼。
不管什麼時候,都應當減小存儲在 Cookie 中的敏感數據。毫不要將用戶密碼之類的東西存在 Cookie 中,即便密碼已經通過了編碼也不要這麼作。這篇文章 給出了幾個開發者無心間將敏感數據存儲在 Cookie 或 JWT 中的實例,因爲(JWT 的 payload)是 base64 編碼,沒有通過任何加密,所以任何人均可以對其進行解碼。
出現數據泄露但是犯了大錯。若是你擔憂你不當心存儲了一些敏感數據,我建議你使用如 Gorilla 的 securecookie 之類的 package。
前面咱們討論瞭如何對你的 Cookie 進行數字簽名,其實 securecookie
也能夠用於加密與解密你的 Cookie 數據,讓你的數據不能被輕易地解碼並讀取。
使用這個 package 進行加密,你只須要在建立 SecureCookie
實例時傳入一個「塊祕鑰」(blockKey)便可。
var hashKey = []byte("very-secret")
// 增長這一部分進行加密
var blockKey = []byte("a-lot-secret")
var s = securecookie.New(hashKey, blockKey)複製代碼
其它全部東西都和前面章節的數字簽名中的樣例一致。
再次提醒,你不該該在 Cookie 中存儲任何敏感數據,尤爲不能存儲密碼之類的東西。加密僅僅是一項爲數據增長一部分安全性,使其成爲」半敏感數據「數據的技術而已。
跨站腳本(Cross-site scripting)也常常被記爲 XSS,及有人試圖將一些不是你寫的 JavaScript 代碼注入你的網站中。但因爲其攻擊的機理,你沒法知道正在瀏覽器中運行的 JavaScript 代碼究竟是不是你的服務器提供的代碼。
不管什麼時候,你都應該儘可能去阻止 XSS 攻擊。在本文中咱們不會深刻探討這種攻擊的具體細節,可是以防萬一我建議你在非必要的狀況下禁止 JavaScript 訪問 Cookie 的權限。在你須要這個權限的時候你能夠隨時開啓它,因此不要讓它成爲你的網站安全性脆弱的理由。
在 Go 中完成這點很簡單,只須要在建立 Cookie 時設置 HttpOnly
字段爲 true 便可。
cookie := http.Cookie{
// true 表示腳本無權限,只容許 http request 使用 Cookie。
// 這與 Http 與 Https 無關。
HttpOnly: true,
}複製代碼
CSRF 發生的狀況爲某個用戶訪問別人的站點,但那個站點有一個能提交到你的 web 應用的表單。因爲終端用戶提交表單時的操做不經由腳本,所以瀏覽器會將此請求設爲用戶進行的操做,將 Cookie 附上表單數據同時發送。
乍一看彷佛這沒什麼問題,可是若是外部網站發送一些用戶不但願發送的數據時會發生什麼呢?例如,badsite.com 中有個表單,會提交請求將你的 100 美圓轉到他們的帳戶中,而 chase.com 但願你在它這兒登陸你的銀行帳戶。這可能會致使在終端用戶不知情的狀況下錢被轉走。
Cookie 不會直接致使這樣的問題,不過若是你使用 Cookie 做爲身份驗證的依據,那你須要使用 Gorilla 的 csrf 之類的 package 來避免 CSRF 攻擊。
這個 package 將會提供一個 CSRF token,插入你網站的每一個表單中,當表單中不含 token 時,csrf
package 中間件將會阻止表單的提交,使得別的網站不能欺騙用戶在他們那兒向你的網站提交表單。
更多關於 CSRF 攻擊的資料請參閱:
咱們要討論的最後一件事與特定的攻擊無關,更像是一種指導原則。我建議在使用 Cookie 時儘可能限制其權限,僅在你須要時開發相關權限。
前面討論 XSS 時我也簡單的提到過這點,但通常的觀點是你須要儘量限制對 Cookie 的訪問。例如,若是你的 Web 應用沒有使用子域名,那你就不該該賦予 Cookie 全部子域的權限。不過這是 Cookie 的默認值,所以其實你什麼都不用作就能將 Cookie 的權限限制在某個特定域中。
可是,若是你須要與子域共享 Cookie,你能夠這麼作:
c := Cookie{
// 根據主機模式的默認設置,Cookie 進行的是精確域名匹配。
// 所以請僅在須要的時候開啓子域名權限!
// 下面的代碼可讓 Cookie 在 yoursite.com 的任何子域下工做:
Domain: "yoursite.com",
}複製代碼
欲瞭解更多有關域的信息,請參閱 tools.ietf.org/html/rfc626…。你也能夠在這兒閱讀源碼,參閱其默認設置:golang.org/src/net/htt….
你能夠參閱 這個 stackoverflow 的問題 瞭解更多信息,弄明白爲何在爲子域使用 Cookie 時不須要提供子域前綴.此外 Go 源碼連接中也能夠看到若是你提供前綴名的話會被自動去除。
除了將 Cookie 的權限限制在特定域上以外,你還能夠將 Cookie 限制於某個特定的目錄路徑中。
c := Cookie{
// Defaults 設置爲可訪問應用的任何路徑,但你也能夠
// 進行以下設置將其限制在特定子目錄下:
Path: "/app/",
}複製代碼
還有你也能夠對其設置路徑前綴,例如 /blah/
,你能夠參閱下面這篇文章瞭解更多這個字段的使用方法:tools.ietf.org/html/rfc626….
就知道確定會有人提出這個問題,下面讓我簡單解釋一下。
可能有不少人和你說過,Cookie 的安全性與 JWT 同樣。但實際上,Cookie 與 JWT 解決的並非相同的問題。好比 JWT 能夠存儲在 Cookie 中,這和將其放在 Header 中的實際效果是同樣的。
另外,Cookie 可用於無需驗證的數據,在這種狀況下了解如何增長 Cookie 的安全性也是必要的。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。