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的做用固然也不止笔者介绍的这些。 本文记录的是笔者在开发过程当中遇到问题引起的思考和探索。可供有相似问题的读者参考。 其余安全方面的文章笔者会持续更新,欢迎各位读者提出意见和建议。

相关文章
相关标签/搜索