zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    CodeForces 659F Polycarp and Hay
    CodeForces 713C Sonya and Problem Wihtout a Legend
    CodeForces 712D Memory and Scores
    CodeForces 689E Mike and Geometry Problem
    CodeForces 675D Tree Construction
    CodeForces 671A Recycling Bottles
    CodeForces 667C Reberland Linguistics
    CodeForces 672D Robin Hood
    CodeForces 675E Trains and Statistic
    CodeForces 676D Theseus and labyrinth
  • 原文地址:https://www.cnblogs.com/caiyy/p/10509525.html
Copyright © 2011-2022 走看看