HTTP Cookies
HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。Cookie 使基于无状态的HTTP协议记录稳定的状态信息成为了可能。
Cookies特性
- 前端数据存储
- 后端通过http头设置
- 请求时通过http头传给后端
- 前端可读写
- 遵守同源策略 (协议、域名、端口)
- 有效期
- 路径 (作用于网站的哪一级,网站url的层级)
- http-only
- secure
创建Cookie
当服务器收到HTTP请求时,服务器端通过在响应头中添加Set-Cookie
选项设置cookie
。浏览器收到响应后通常会自动保存Cookie
,之后每一次对该服务器的请求中都会通过Cookie
请求头将Cookie
信息发送给服务器。
Set_cookie响应头部配置
Set-Cookie: <cookie名>=<cookie值>
服务器使用Set-Cookie
响应头向用户代理(浏览器)发送Cookle信息
浏览器添加Cookie请求头部
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
对该服务器发起的每一次请求浏览器都会自动将之前保存的Cookie信息通过Cookie
请求头发给目标服务器
定义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的作用域
Domain
和Path
标识定义了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的作用当然也不止笔者介绍的这些。
本文记录的是笔者在开发过程中遇到问题引发的思考和探索。可供有类似问题的读者参考。
其他安全方面的文章笔者会持续更新,欢迎各位读者提出意见和建议。