Cookie浅析

Cookie

 翻阅了好久关于Cookie的博客及文档,感觉一直有一块结没有解开,所以一直难以在脑中形成一个顺畅的知识脉络。最后实在是遭不住,拉上我的大神朋友在食堂里坐了3个小时,问了个底朝天!总算形成了清晰的知识脉络了。

 学习知识和技术一定要知道

  1. 是什么需求或场景导致了该知识或技术的产生
  2. 该知识或技术的实现原理是什么
  3. 该技术是否有什么缺陷,能否有更好的解决方案或者补救措施

 啊,闲话说太多了,让我们来开始正题吧。

 以一个场景为例:

 你登录网站A的账号,想要在这次输入用户名和密码登录之后,在一段时间内,重新打开该页面的时候,不需要再次输入账号名密码就能够登录账号,并收到该网站给你的推送信息。

 很正常的需求,那么让我们从程序员的角度来看看究竟是如何实现的。


## 进化之路

 保存用户登录状态的思路是这样一步步进化的:(以下都是假设使用https协议进行通信,也就是就算被数据抓包,攻击者也得不到有效的登录信息)

无状态的纯https通信 -> 每次登录账号都需要重新输入账号密码 -> 服务端使用一个flag变量来判断登录态 -> 服务端使用纯Cookie来判断登录态 -> 服务端使用session配合Cookie来判断登录态。(当然,可能还会有更安全更高效的方法,限于我才疏学浅,待我学习到了再与大家分享!)


### 无状态的纯https通信

 大家都知道的是,https是无状态的通信协议。

 也就是说,你这次与服务端通信,通信结束了,断开TCP连接,等到下次你再与该服务器进行通信,服务器就完全不认识你了,也就无从谈起登录状态保存了。

 该协议这样设计,确实减小了通信的压力,但对于想要保存登录状态的我们,实在是让人困扰。


### 每次登录账号都需要重新输入账号密码

 这是我们最不想看到的方式,由于https没有记忆性,所以每次登录我们都需要重新输入账号密码再传输给服务端文件,然后服务端文件根据传过来的账号密码在数据库中检索,若检索到则判断用户登录。

 每次都需要输入账号密码才能登录账号,这当然不行。我们继续。


### 使用一个flag变量来判断登录态

 第一次或登录态过期后再一次在ajax里带上用户的用户名与密码了,服务端接受到请求后,解析出带过来的数据,然后在数据库中进行搜索,是否存有该用户名及密码,如有,则创建一个变量flag(假设flag值等于1,为登录态有效。为0登录态无效),返回给用户。往后用户再与服务器进行交互时,都带上flag,服务端根据这个flag进行判断登录态是否有效。

 但是,这个方法本质上就是无法执行的。

 因为flag只是一个临时变量,重新打开一个tab页面时临时变量就都会被清空。也就是说,flag无法在客户端做到存储,也就无从谈起每次与服务端交互时都带上这个数据了。

 虽然说,该方式不可行,但其思路是有借鉴的意义的——只要我们能够将flag数据长久地存储在客户端中,那么就能够做到每次与服务端交互都带上这个数据了。

 这个客户端存储工具就是——Cookie。

 可能有的同学就说了:“那我们就想办法不要每次检查登录态都检索一次数据库,这样做——当用户第一次(或者是登录态过期后再一次)输入用户名密码,将这些信息通过ajax传给服务端,服务端接受到后,在数据库里检索,如果检索到了,则生成一个flag发给客户端,往后每次客户端与该服务器通信时都带上这个flag信息,只要该flag信息意义为有效,则用户登录态为有效。”

 非常聪明,这就是“使用纯Cookie来判断登录态”的基本思路了。至于该思路的问题我们接下来讲。


### 使用纯Cookie来判断登录态

 思路是这样的:

 当用户第一次(或者是登录态过期后再一次)输入用户名密码,将这些信息通过ajax传给服务端,服务端接受到后,在数据库里检索,如果检索到了,则使用set-Cookie生成一个Cookie(为了使服务端不光能够判断该登录态有效且能够知道用户的身份,所以该Cookie里得保存能够表明用户身份的信息,如账号,密码。)发给客户端,往后每次客户端与该服务器通信时都在请求头带上这个Cookie信息,只要该Cookie信息意义为有效,则用户登录态为有效,并返回该用户有关的信息,也就不需要再次输入登录的账号密码。

 这个Cookie的意义就是flag。

 这个思路成功地解决了长久存储flag的问题,不过随之而来了新的问题。

  1. Cookie是一个文本文件,并且直接明文保存在浏览器中,可以直接通过inspect->Application->Cookies(以Chrome浏览器为例)中获得明文的Cookie的信息,若Cookie保存的是保密信息(如银行的登录用户名密码),有人使用你的电脑的话,则会被直接看到了,极其不安全。不过,也可以将cookie值进行加密,这样就算别人看到了你浏览器中的cookie,也无法得到其有效信息
  2. 可以通过js脚本的document.Cookies()来获得Cookie,如果有别有用心的人利用这一点,使用XSS攻击(稍后会讲)的话,那么你的Cookie就会被他们获取到了,你的信息也就完全暴露给了他们。如果你想禁止这个操作,则在服务器设置Set-Cookie请求头时,设置httpOnly为true

 所以说这个思路也是有很大问题的,不过若是你的网站安全要求不高,甚至说没有安全要求,那么用这个思路也是可以的。


### 使用session配合Cookie来判断登录态

 一步步解决问题,一步步进化,我们终于来到了这一步。不过不是说该方法是完美的,他也有很多问题漏洞,等下就讲。

 既然我不想我的保密信息直接暴露在浏览器中,那么我们就在上一种思路的基础上,修改一个地方——我在Cookie中不直接保存用户的保密信息了,而是将该保密信息存在session(session可以理解为一个对象,每个session都存储在session文件中)中,生成一个session_id(很长的随机字符串)来标识该session,session_id设置在Set-Cookie中。

 我们来捋一遍过程:

 当用户第一次(或者是登录态过期后再一次)输入用户名密码,将这些信息通过ajax传给服务端,服务端接受到后,在数据库里检索,如果检索到了,创建一个session对象,在该对象中保存用户的登录信息,并生成一个session_id来作为该session的标识,再使用set-Cookie生成一个Cookie(Cookie内容为session_id)发给客户端,往后每次客户端与该服务器通信时都在请求头带上这个Cookie信息,只要该Cookie中保存的session_id能够匹配到session,则用户登录态为有效,并返回该用户有关的信息。

 这样的方式虽然解决了Cookie明文存储登录信息的问题(因为客户端中的Cookie保存的是很长的随机字符串,不用担心别人看到你的Cookie内容了就知道你的账号密码是多少了)。

 但还有个问题没有解决——可以通过js脚本的document.Cookies()来获得Cookie。如果你下载的页面中有攻击者嵌入的攻击代码,该代码就能读取到你的Cookie,然后再将得到的Cookie传给攻击者自己的服务器。

 那么,攻击者是如何嵌入攻击代码的呢?该html页面的脚本不都是开发者自己编写的吗,攻击者无法直接修改html文件的内容呀,总不可能开发者自己写了攻击代码攻击自己的网站吧?

 这就涉及到了XSS攻击。


### XSS攻击

 在讲XSS攻击之前,我们先提一下同源策略,这是Web安全的一大根基。


#### 同源策略(拒绝对html的dom结构的操作,访问别的浏览器不能带上不同源的cookie,ajax请求不能发送)

 同源策略:限制了从一个源加载的文档或脚本与另一个源的资源进行交互。(同源的意思为:相同协议,域名,端口)

 会禁止如下跨域操作:

  1. js使用ajax请求不同源的脚本文件
  2. js操作不同源的dom
  3. js获得不同源的cookie,localStorage,sessionStorage信息

 但不禁止如下跨域操作:

  1. 使用script,link,img标签时,跨域发起请求(即指定的src即使是不同源的,对应的后端文件也会响应请求)

 例如说不同源的网站A向网站B发起ajax请求,由于两者不同源,网站B会拒绝响应网站A的请求。这样的话,就防止了无权限的恶意请求,也就是说,网站B只响应有权限的请求(即同源文件的请求),而同源文件都是由可信赖的合作人编写的,所以比较安全。

 举个例子:

 攻击者发现了银行修改金额信息只需要使用ajax,在携带的信息中带上修改的金额数,后端文件接收到该请求会根据该数据来修改金额(当然实际情况不可能这么简单),那么攻击者只要从自己的服务器使用对应的url带上金额信息,如果没有同源策略的话,银行的服务器会处理该请求,也就修改了银行的数据库中的金额数据。这显然是不安全的。

 既然这样,那么我们就规定,一个源中的文件只响应同一个源下的请求,从根源上杜绝不安全代码生效。

 不过,世上没有密不透风的墙,你不响应不同源的请求,你只响应同源的请求是吧。那么我就让你同源的文件为我服务,获取到我想要的信息并发给我的服务器。

 这我们就回到了XSS攻击。

 在一般情况下,html的内容都是由开发人员设置的。但如果这是个论坛呢?(只要是能够渲染出用户的输入内容的网站就行)

 是这样的,当你输入发言内容,服务器将你的发言内容存在了数据库中,等到别人再次访问会将你的输入内容渲染出来,如果说正常文本没有关系,但如果是带有script标签呢?那么渲染到页面上的时候,就会被认为是js脚本,并乖乖地执行该脚本,给坏人做事。

 这样,攻击者就能够在正常的html页面中嵌入他的攻击代码。如果我们想要做防范措施的话,就是过滤用户的输入文本,禁止输入非法字符。

 ok,这个时候攻击者在你的html页面嵌入了攻击者的攻击代码,并且你的html页面执行了他的攻击代码帮他做了坏事,由于是同源的,所以该攻击代码相当于拥有了所有权限为所欲为。现在攻击者已经拿到了他想要的信息了,不过他还有个问题要解决——如何才能跨域将数据传输到他的服务器中。(因为攻击者的服务器绝对是跟你的服务器是不同源的,所以必须得解决跨域问题,否则攻击者的服务器接受不到从别的源中攻击得到的数据)

 一个最简单粗暴的方法——在嵌入的攻击代码中,执行插入img标签,并设置其src属性,设置该属性的值为攻击者服务器的域名并带上得到的数据作为url参数。

 因为img标签不受同源策略的限制,所以不同源的文件可以互相响应。

 就这样,用户得到了你的信息,并将数据传到了他的数据库中为他所用。

 归根结底的防范方法就是:过滤输入文本,设置字符黑名单。

 不过这还是有漏洞的,例如说将<设置为黑名单了,但我使用<的转义字符,该转义字符不在黑名单中,还是有漏洞。还是那句话世上没有不透风的墙。


### CSRF攻击

 XSS攻击是通过注入攻击代码来攻击获取你的信息权限,而CSRF攻击则是通过直接使用被攻击者的Cookie伪装为被攻击者,也就有了被攻击者的权限,从而进行攻击。

 说的太抽象了,老规矩,举个例子:

 假设你是个学生A,老师是老师B,他是你的大物老师,而你期末考试在他手底下挂了,你想要在正方教育系统中改成绩。但是,改成绩需要老师的账号密码来登陆获取修改权限,而你没有账号密码。但是,没有账号密码没有关系啊,你有他的Cookie就可以了,不就可以伪装成老师,来修改成绩了。

 当然,这个时候你也没有老师B的Cookie,这个时候你想到,如果老师访问了正方教育系统,然后没有退出该页面,即该网站的Cookie还是有效,那么只要老师再在同一个浏览器的不同tab页面再次访问正方教育系统,由于同源,不就会自动在请求头上加上Cookie值了吗?

 所以,只要我们知道正方教育系统对应路由的url,以及参数形式,不就能够做到伪装成老师B,并让服务器做出你想让他做的修改。

 ok,这时候你不知道从哪个渠道知道了正方教育系统修改成绩的url,如:http://域名/xxxx/xxxx/ChangeScore.jsp?id=学号&score=成绩

 这时候,你就自己写个网站,里面有个img,其src就为正方教育系统修改成绩的url,然后你给老师发封邮件,其中就是该网站的链接。只要老师在登录正方教育系统网站的同时,点开了你邮件中的链接,你的网站就会跨域发起GET请求,而又因为请求头中带有正方教育系统给老师B的Cookie(再多嘴一句,Cookie是在同一个浏览器,不同tab页面,访问同一个源时共享的),所以正方教育系统网站后台发现该Cookie的session_id能够找到对应的session,也就验证了该访问用户是权限用户,也就进行了该用户的权限操作,用传过来的score参数修改成绩。

 ok,CSRF攻击的过程就讲完了。

 CSRF攻击,攻击者不需要像XSS攻击一样要拿到用户信息才能攻击,他仅需要的是让用户自己(当然,用户并不知情)去使用Cookie访问目标网站,只是说,攻击的内容由攻击者确定。

 那么,如何防范CSRF呢?

 第一种思路,由于CSRF是自动进行的,被攻击者在不知情的情况下就自动地执行了恶意操作。那么我们在确认执行操作之前,添加一个确认机制呢?例如说验证码,这样不就阻止了自动的CSRF攻击吗?

 第二种思路,CSRF的攻击是无法得到Cookie信息的,那么如果网站要求url中还需要Cookie中保存的一个随机数呢?也就是服务器为每次登录状态生成的随机数,即为Token,会存储在Cookie中返回给用户。

 ok,所以就算是使用session加上cookie也不是万事大吉的,还是有很多漏洞可以钻。

 不过做好相应的防范措施,该方法确实是个不错的方法。

 到这里,已经为大家梳理好了知识脉络了,接下来我会大家稍微介绍一个相关的知识。


## 关于Cookie的知识

cookie的类型

 我们按该Cookie的过期时间分为两类:会话Cookie和持久Cookie。

 会话Cookie是一种临时型Cookie,存储在内存中,只要用户退出浏览器(不是删除该tab页面),会话Cookie就会被一次性删除。设置会话Cookie的方法是:在创建cookie不设置Expires即可。

 持久Cookie存储在硬盘中,不会因退出浏览器而清除持久Cookie。但他并不是真正永久型Cookie,set-Cookie中设置一个特定的过期时间(expires)或者有效期(Max-Age),当有效期过了,则持久Cookie失效,也就会清除。


### Cookie的属性内容 #### domain

 Cookie是跨域是被禁止的,例如a.b.com的Cookie不可以传输给c.b.com。

 但可以使用domain来做到同个顶域下的跨域传输。

 产生Cookie的服务器可以向set-Cookie响应首部添加一个Domain属性来控制与哪些主机通信时,会在请求头带上该Cookie。

 不过有几点要注意的:

  1. domain的值只能是自己的域或自己的顶域,不能是其他的域。如你不能设置domain值为baidu.com
  2. 不声明domain的话,只有本域才能获得
  3. 如果声明了domain,一般包含子域名

#### Path

 如果我们不想与该域下的所有路由进行http通信时都带上该Cookie,那么就在set-Cookie中设置对应的Path属性,来指定与该域下的哪个文件通信时,才带上Cookie头

Secure

 在set-Cookie中设置secure时,那么只有是https通信时,才会在请求头中带上Cookie。

HttpOnly(禁止使用JavaScript操作Cookie)

 一般来说,我们可以直接通过document.cookie来获取和设置Cookie值。

 但显然这非常的不安全呢,所以我们通过在set-Cookie中设置HttpOnly来禁止JavaScript操作Cookie。这也是防范XSS攻击的好办法。


### Cookie带来的效率问题

 有必要先解释一下什么是静态资源与动态资源:

  • 不由服务器执行的,而是由浏览器执行的就是静态资源(如html,js,css,图片文件)
  • 反之就是动态资源,一般是指的数据库资源

 一个网页中有许多的静态资源,这也是用户要访问的,这样的话,请求每个静态资源都需要在请求头加上cookie,但是,请求静态资源并不需要进行cookie验证,这样也就造成了严重的资源浪费,同时也降低了访问速度。

 所以对应的解决方案是:

多域名拆分。其思想是

 如服务器域名为base.com,那么我们将页面文件放在page.base.com这个主机中,将静态资源文件放在static.base.com这个主机中。

 这样的话,两者的域名不同。当访问static.base.com获取静态资源时,就不会传输page.base.com中的cookie。


### 关于session的知识

 当距离用户上一次使用session(即重新登录页面)的时间间隔大于了失效时间,服务端就会认为客户端已经停止了活动,就会把session删除以节省空间。

 session是被删除只有如下几种情况:

  1. 超时(一般在30分钟左右)
  2. 程序中主动删除
  3. 程序关闭

 也就是说,不会因为浏览器的关闭(也就是通常说的会话结束)而删除。而之所以大家会有这个错觉,是因为大部分session机制是使用会话Cookie来保存session_id的。

 只要Cookie(客户端维护),session(服务端维护)中有一者失效了,则登录态就失效了。

 SESSION的内容通常是与用户信息相关的信息: 1. 身份信息、登陆状态 2. 用户的个性配置、权限列表 3. 其他的一些通用数据(比如购物车)

 不过使用session+cookie来保存登录态有个问题:

  1. session保存在服务端中,当用户访问量变大时,服务端需要存储大量的session,对于服务器压力很大
  2. 当服务器为一个集群的时候,用户登录一个服务器,会将session保存在这个服务器中,而再次访问网站,可能登录的是另一个服务器,但却无法从该服务器中找到对应的session(因为该session在另一个服务器中存着呢),一般来说我们使用第三方服务器来做到缓存一致,不过不方便

### Token

 基于此,也就应运而生了token,token无需在服务端中存储,是用算法来判断登录态,过程如下:

  1. 客户端使用用户名跟密码请求登录
  2. 服务端收到请求,去验证用户名与密码
  3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
  4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
  6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

(全文完)


参考资料:
  1. 浅谈CSRF攻击方式:https://www.cnblogs.com/wangyuyu/p/3388169.html
  2. cookie的用途,它的优点和缺点:https://blog.csdn.net/shuidinaozhongyan/article/details/78242192
  3. Session和Token的区别:https://blog.csdn.net/qq_1290259791/article/details/81193914
  4. 有关什么情况下session会失效:https://blog.csdn.net/czh500/article/details/80211410
  5. 聊一聊 cookie:https://segmentfault.com/a/1190000004556040
  6. 关于cookie的安全性问题:https://segmentfault.com/q/1010000007347730?_ea=1318399
  7. 这一次带你彻底了解Cookie:https://www.cnblogs.com/zhuanzhuanfe/p/8010854.html
posted @ 2019-03-11 11:26  前端蔡蔡  阅读(535)  评论(0编辑  收藏  举报