對於web系統而言,由於HTTP協議無狀態的特性,用戶登錄時需要服務端生成通行證返回給瀏覽器。瀏覽器保存該通行證並在接下來的請求中攜帶該通行證。通常來講,web系統使用http cookie來保存和傳輸通行證。本文介紹http cookie的原理、特性、並分析用其保存通行證可能遇到的安全問題。
本文假設使用cookie的客戶端是瀏覽器,雖然還有其它客戶端也使用cookie,但普通用戶使用更多的還是瀏覽器。
什么是Http Cookie?
一種Http狀態管理機制,最早由Lou Montulli發明於1994年,最新的標准是2011年發布的RFC 6265 - HTTP State Management Mechanism。因為保存信息到客戶端的本質,也有系統用cookie來緩存信息,但這並不是Http Cookie的主要用途。
Cookie既指整個狀態保存機制,也指用戶保存狀態的數據本身,由String類型的name和value以及若干屬性組成。追根溯源,其名字來源於fortune cookie,一種內含寫有幸運文字小紙條的餅干。
Cookie保存在哪,如何生成又怎樣傳輸?
Cookie由服務端生成,保存在瀏覽器。通過兩個Http Header:Set-Cookie和Cookie進行傳輸。Set-Cookie Header用於服務端傳輸登錄通行證等cookie鍵值對及屬性給瀏覽器,瀏覽器收到並驗證合法性后會做相應保存。Cookie Header用於瀏覽器傳遞cookie鍵值對給服務端,服務端依次來鑒別用戶身份等狀態信息。
RFC6265定義了Cookie的一些屬性,包含Expires、Max-Age、Domain、Path、Secure、HttpOnly。也有一些還沒定義到RFC,但已經實際應用的屬性,比如SameSite。Cookie寫時有屬性讀時無屬性,屬性僅在設置時即Set-Cookie Header中指定,瀏覽器依此來決定是否接受該cookie如何使用該cookie,而一旦瀏覽器決定了將cookie放到Cookie Header中傳遞到服務端,那么瀏覽器不會傳遞更多的的信息到服務端,只會傳遞鍵值對name和value。
很多瀏覽器會提供讀Cookie和寫Cookie的api給運行在其上Javascript腳本使用,通常的api是操作docment.cookie:
document.cookie=“SID=31d4d; domain=example.com; path=/;”;
說說Domain和Path這兩個屬性
Domain、Path、Name三者唯一確定一個cookie。其它屬性僅用作讀寫時的權限控制,不作為cookie標識。在瀏覽器在使用cookie時:
- 不區分Http/Https
- 不區分端口號
這相比瀏覽器同源策略更加寬松,帶來很多安全問題。
Domain是向上通配的:
- 寫入(Set-Cookie)訪問www.example.com
Set-Cookie: sid1=a; domain=example.com; path=/; 接受
Set-Cookie: sid2=b; domain=www.example.com; path=/; 接受
Set-Cookie: sid3=c; domain=pay.example.com; path=/; 拒絕
- 讀取(Cookie)
訪問pay.example.com
Cookie: sid1=a
訪問www.example.com
Cookie: sid1=a; sid2=b;
很多系統寫cookie,習慣在域名前加一個點,寫成.example.com,以為這么寫才是通配的,其實這個點是多余的,沒有必要的。
Path是向下通配的:
- 寫入(Set-Cookie)
Set‐Cookie: sid1=a; domain=example.com; path=/;
Set‐Cookie: sid2=b; domain=example.com; path=/test/;
- 讀取(Cookie)
訪問http://example.com/
Cookie: sid1=a;
訪問http://example.com/test/
Cookie: sid1=a; sid2=b
Cookie的有效期過長導致通行證泄露
這兩個屬性指定cookie有效期。不指定有效期時,cookie默認為當前session有效,當前session有效指關閉瀏覽器時cookie失效。
Cookie的有效期過長可能導致通行證泄露。比如用戶使用了公用電腦,關閉瀏覽器時,沒有點擊注銷。這樣Cookie就留存在這台電腦上,其它人只需打開瀏覽器即可獲取其在網站上的cookie。
一般保存通行證的cookie應該設置為session有效的cookie,並在用戶選擇記住登錄狀態時,提醒用戶,不要再公用電腦上做這樣的勾選。
明文傳輸的HTTP流量
HTTP明文傳輸數據的特性,使得攻擊者可從網路上抓包獲取Cookie。
解決方案:
- 服務端使用HTTPS
- 指定cookie的secure屬性,該屬性使cookie只能在HTTPS請求中帶出。
利用XSS漏洞讀取保存在cookie的通行證
假如網站存在XSS漏洞,那么惡意JS可直接讀取Cookie中的通行證。可以通過指定Cookie的HttpOnly屬性。對於指定了HttpOnly的Cookie,瀏覽器會拒絕JS讀寫。
XSS仍有可能利用服務端漏洞獲取cookie:
- 服務端有可能在請求的正常響應中包含通行證。不要笑,這真的有,尤其現在很多公司app和web共用一套后台;
- 服務端可能有漏洞導致cookie包含在請求響應中,從而被竊取。比如:Apache CEV-2012-005
所以HttpOnly不是銀彈,XSS也有很多其它的危害。但至少,HttpOnly可以避免通行證在瀏覽器被JS直接讀取。
利用XSS漏洞寫cookie
- 使用戶退登
保存了通行證的cookie因為是HttpOnly,所以XSS無法讀取。但XSS還是可以寫Cookie,由於domain、path、name三者才唯一標識一個cookie,XSS可以寫一個跟通行證cookie name相同,但domain或path不同的cookie。
這樣當瀏覽器發送請求給服務端時,服務端就會收到兩個相同name的cookie。因為cookie讀時無屬性,所以服務端無從判斷哪一個才是正確的。這樣會使用哪一個cookie,就要看具體web server的實現,可能是第一個也可能是第二個。
這樣通過寫cookie,XSS可以輕易地使已登錄用戶退登。
- 更危險的情況,Cookie替換攻擊
惡意JS可針對特定的域名和path寫入一個攻擊者的cookie通行證。比如針對某電商網站的余額充值domain和path,寫入攻擊者的通行證。該攻擊者cookie在用戶正常購物的請求中不會帶出,當用戶充值時會帶出,從而使用戶充值到了攻擊者的賬號。
不安全的公共wifi
對於HTTP,公共wifi可劫持用戶所有的流量,竊取cookie通行證自然不在話下。
對於HTTPS,因為可以在DNS上做手腳,可以在路由器上截取和篡改流量,在公共wifi上很容易誘導用戶發出到網站的HTTP請求,幾遍該網站是全站HTTPS也是如此。
因此,對於XSS寫cookie的危害,公共wifi都能做到。而且能力更強,危害更大。
對於設置了secure屬性的cookie,雖然不會在http請求中帶出,但卻可被http請求的Cookie設置覆蓋。因此,設置了secure也不安全。
Although seemingly useful for protecting cookies from active network
attackers, the Secure attribute protects only the cookie's
confidentiality. An active network attacker can overwrite Secure
cookies from an insecure channel, disrupting their integrity
---- from RFC 6265
HSTS
公共wifi的危害,有一個關鍵點,就是誘導用戶對指定網站發出HTTP請求。這一點通過HSTS - HTTP Strict Transport Security可以避免。
HSTS通過設置strict‐transport‐security頭,來告知瀏覽器,對當前域名以及所有子域名,都只使用HTTPS訪問。
讓瀏覽器對當前域名及子域名,強制進行HTTPS訪問:
strict‐transport‐security: max‐age=15552000;includeSubDomains;
但HSTS在現實中難以被利用。因為:
- 囿於cookie的通配特性,必須對整個域名部署HSTS才能生效;
- 瀏覽器並非全支持這個特性。IE11+才開始支持;
所以,真正全站部署HSTS的網站,少之又少。
利用cookie大小和數量的限制來破壞安全
o At least 4096 bytes per cookie (as measured by the sum of the
length of the cookie's name, value, and attributes).
o At least 50 cookies per domain.
o At least 3000 cookies total.
---- From RFC 6265
- Cookie的大小和數量是有限制的;
- 每個瀏覽器,每種Web Server,限制都可能不一樣;
- 超出限制的行為,是未定義的。
設置超大cookie,超多cookie,都是典型的攻擊手段。上文提到的Apache CEV-2012-005漏洞,即是利用了Apache對Cookie大小的限制。
利用csrf漏洞偷用通行證
在訪問惡意網站時,網站的頁面代碼中,可能包含到你銀行賬號或是其它什么賬號的請求。由於這時瀏覽器會自動帶出到相應域名的cookie,所以用戶的登錄通行證,就很容易被惡意網站偷用。
這個問題,可以通過設置Cookie的SameSite屬性來解決。設置了SameSite屬性的cookie,只會在當前正在訪問(瀏覽器地址欄)的域名與cookie的domain可匹配時,才會帶出。
- 在設置SameSite時,可以指定嚴格還是不嚴格。在不嚴格的情況下,如果請求會導致瀏覽器地址欄發生變化,那么cookie也是允許帶出的;
- SameSite沒有包含在RFC 6265中,因此還不是標准。但Chrome 51(發布於2016年8月)已經支持了這個屬性。
如下截圖,是Chrome支持的cookie屬性:
讀取瀏覽器保存在硬盤上的cookie
有人會說,cookie不就是存放在硬盤,我直接從硬盤上竊取不可以嗎?
答案是可以。比如Chrome的cookie就存放在目錄:C:\Users<username>\AppData\Local\Google\Chrome\User Data\Default\下,是一個sqllite的數據庫文件,雖然cookie內容都進行了加密,但也是可以破解的。
只是,當你攻破一個用戶的電腦,破解了chrome的加密算法,最終只是竊取到了一個用戶的登錄憑證而已。這種攻擊,性價比很低。
舉個例子
Github推出的github pages功能,一開始的時候使用的是github.com主站域名,后來因為上面提到的cookie安全問題,不得不為github pages單獨啟動了github.io域名。
具體可看Github的官方blog,里面提到了上面部分的cookie安全問題。blog鏈接:Yummy Cookies across Domains
總結
- Cookie從機制上就有安全問題,使用時要小心再小心;
- 對於一些加強安全的Cookie屬性,要盡量在系統早期實施。越晚實施,代價越大,實施難度越高;
- 公共wifi不安全,即便網站使用了https,公共wifi也不安全。當離開一個公共wifi時,清除這段時間瀏覽器產生的cookie!