HTTP Cookies探索實踐

HTTP Cookies

HTTP Cookie(也叫 Web Cookie 或瀏覽器 Cookie)是服務器發送到用戶瀏覽器並保存在本地的一小塊數據,它會在瀏覽器下次向同一服務器再發起請求時被攜帶併發送到服務器上。Cookie 使基於無狀態的HTTP協議記錄穩定的狀態信息成爲了可能。html

Cookies特性前端

  • 前端數據存儲
  • 後端經過http頭設置
  • 請求時經過http頭傳給後端
  • 前端可讀寫
  • 遵照同源策略 (協議、域名、端口)
  • 有效期
  • 路徑 (做用於網站的哪一級,網站url的層級)
  • http-only
  • secure

建立Cookie

當服務器收到HTTP請求時,服務器端經過在響應頭中添加Set-Cookie選項設置cookie。瀏覽器收到響應後一般會自動保存Cookie,以後每一次對該服務器的請求中都會經過Cookie請求頭將Cookie信息發送給服務器。node

Set_cookie響應頭部配置

Set-Cookie: <cookie名>=<cookie值>web

服務器使用Set-Cookie響應頭向用戶代理(瀏覽器)發送Cookle信息算法

瀏覽器添加Cookie請求頭部

GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry後端

對該服務器發起的每一次請求瀏覽器都會自動將以前保存的Cookie信息經過Cookie請求頭髮給目標服務器api

定義Cookie的生命週期

會話期Cookie

瀏覽器關閉會自動刪除,僅在會話期有效。瀏覽器

持久性Cookie

持久性Cookie的生命週期取決於過時時間(Expires)或有效期(Max-Age)指定的時間週期安全

設置cookie值得有效時間是2021年5月6日20點53分服務器

Set-Cookie: id=a3fWa; Expires=Thu, 06 May 2021 12:53:27 GMT;  
複製代碼

Expires的值設置的是GMT的時間格式,比當前時間少了8小時。
以當前時間爲例,轉換以下:

let time = new Date()
time.toGMTString()
//"Thu, 06 May 2021 12:53:27 GMT"
複製代碼

提示:當Cookie的過時時間被設定時,設定的日期和時間只與客戶端相關,而不是服務端。

cookie失效

若是Cookie沒有設置expires屬性值,那麼 cookie 的生命週期只是在當前的會話中, 關閉瀏覽器意味着此次會話的結束,此時 cookie 隨之失效。

Set-Cookie: id=a3fWa; Expires=Thu, 05 May 2021 12:53:27 GMT;
複製代碼

expires設置一個過去的時間點,那麼這個cookie 會被當即刪掉(失效)

限制訪問Cookie

Secure屬性

標記爲 Secure 的 Cookie 只應經過被 HTTPS 協議加密過的請求發送給服務端

HttpOnly屬性

JavaScript Document.cookie API 沒法訪問帶有 HttpOnly 屬性的cookie;此類 Cookie 僅做用於服務器。
示例:

Set-Cookie: id=abed; Expires=Thu, 06 May 2021 12:53:27 GMT; Secure; HttpOnly
複製代碼

Cookie的做用域

DomainPath標識定義了Cookie的做用於:即容許Cookie應該發送給那些URL。

Domain屬性

Domain 指定了哪些域名(主機)能夠保存 Cookie。若是不指定,默認爲 origin,不包含子域名。若是指定了Domain,則通常包含子域名。

例如,若是設置 Domain=example.com,則 Cookie 也包含在子域名中(如developer.example.com)。

Path屬性

Path 標識指定了域名(主機)下的哪些路徑能夠保存 Cookie(該 URL 路徑必須存在於請求 URL 中)。以字符 %x2F ("/") 做爲路徑分隔符,子路徑也會被匹配。
例如:設置Path=/list,如下路徑都會給匹配:

  • /list
  • /list/detail
  • /list/detail/main

SameSite屬性

SameSite Cookie 容許服務器要求某個 cookie 在跨站請求時不會被髮送  
複製代碼

示例:

Set-Cookie: key=value; SameSite=Strict

SameSite有如下三種值:

  • None

瀏覽器會在同站請求、跨站請求下繼續發送 cookies,不區分大小寫。

  • Strict

瀏覽器將只在訪問相同站點時發送 cookie。

  • Lax

與 Strict 相似,但用戶從外部站點導航至URL時(例如經過連接)除外。 在新版本瀏覽器中,爲默認選項,Same-site cookies 將會爲一些跨站子請求保留,如圖片加載或者 frames 的調用,但只有當用戶從外部站點導航到URL時纔會發送。

Cookies做用

  • 存儲個性化設置(如用戶自定義設置、主題等)
  • 存儲未登陸時用戶惟一標識
  • 存儲已經登陸用戶的憑證
  • 存儲其餘業務數據

...
根據業務場景,靈活選用

Cookie-登陸用戶憑證

  • 用戶ID

示例:

userId=1; _ga=GA1.1.245187848.1620008273//userId->uid  
    userId=142389; _ga=GA1.1.245187848.1620008273//userId->工號
    ...
複製代碼

以上已經登陸的用戶cookie信息,攻擊者很容易推算出其餘用戶的cookie,從而冒用其餘用戶身份。

  • 用戶ID+簽名

攻擊者經過如上保存的cookie,很容易猜到userId保存的規律,採用某種手段更改已登陸的cookie信息,就能夠冒用其餘用戶的身份。
此時咱們會想到給userId簽名,可是簽名後的userId後端接口也沒法逆向簽名,並且簽名依然能夠被更改,接口又該怎麼識別尼?
正確的作法是:

簽名和真實的userId同時都透傳給接口,接口拿到簽名和userId後經過和前端一樣的簽名算法計算簽名結果和原簽名比較,最終確認用戶信息的準確性。  
複製代碼
// crypt.js
var crypt ={};
const key ='fs)3!dsg/8%';//key是本身隨便定義,越隨意約安全
crypt.cryptUerId = function(userId){
    var crypto = require('crypto');
    var sign = crypto.createHash('sha256',key);
    sign.update(userId+'');
    return sign.digest('hex')
}
module.exports = crypt;
複製代碼
// login.js
const crypt =require('../tools/crypt')
let sign = crypt.cryptUerId(user.id)
//這裏須要透傳userId和sign,由於接口沒法將sign逆向簽名成原userId,只能在利用一樣的簽名算法對userId再次進行簽名,校驗兩次簽名值
ctx.cookies.set('userId', user.id,{ httpOnly: false, sameSsecure: false });
ctx.cookies.set('sign', sign,{ httpOnly: false, sameSsecure: false });
複製代碼
// addComment.js
const crypt = require('../tools/crypt')
...
let userId = ctx.cookies.get('userId');
let sign=ctx.cookies.get('sign')
if(sign!==crypt.cryptUerId(userId)){
	throw new Error('有人在瞎搞!')
}
複製代碼
  • SessionId (讀者可參考類比JWT實現思路)

用戶在訪問目標主機後,主機接口根據某種加密算法生成sessionId,並保存在cookie中,之後的每一次請求中都會攜帶sessionId做爲用戶身份識別標誌。

//session.js
var session = {};
var cache = {};
session.set = async function (sessionId, obj) {
    var sessionId = Math.random();//隨機生成sessionId
    if(!cache[sessionId]){
        cache[sessionId]={}
    }
    cache[sessionId].content =obj;
    return sessionId;
}
session.get = function (sessionId) {
    return cache[sessionId]&& cache[sessionId].content
}
module.exports = session;
複製代碼
// login.js
const session = require('../tools/session')
...
var sessionId = session.set(user.id,{userId:user.id});
ctx.cookies.set('sessionId',sessionId,{ httpOnly: false, sameSite: "Lax", secure: false })
複製代碼
// addComment.js
...
var sessionId = ctx.cookies.get('sessionId');
var sessionObj = session.get(sessionId);
if(!sessionObj || !sessionObj.userId){
	throw new Error('session不存在')
}
...
複製代碼

Cookies和XSS的關係

  • XSS可能會偷取Cookies(document.cookies)
  • 設置http-only的Cookie不會被偷(cookie只能經過http請求傳輸)

Cookie和CSRF的關係

  • CSRF利用了用戶Cookies
  • 攻擊站點沒法讀寫Cookies
  • 最好能禁止第三方使用Cookies (使用same-site)

Cookies-安全策略

  • 簽名防篡改(驗證cookie是否被修改)
  • 私有變化(加密後保存)
  • http-only(防止XSS)
  • secure(僅在https下防止cookie被竊取)
  • same-site(禁用第三方使用Cookies)

參考資料:

MDN_HTTP cookies
node_crypto-加密
五分鐘帶你瞭解啥是JWT

結語

cookies做爲安全防護的重點,值得咱們去深刻研究。 cookie的做用固然也不止筆者介紹的這些。 本文記錄的是筆者在開發過程當中遇到問題引起的思考和探索。可供有相似問題的讀者參考。 其餘安全方面的文章筆者會持續更新,歡迎各位讀者提出意見和建議。

相關文章
相關標籤/搜索