Cookie 和会话是 Web 应用中的基础概念,有了会话的机制,Web 应用才能记住访问者的状态。在长连接的应用中(如 SSH),用户登录成功之后,服务端可以认为后续都是这个账号在操作,从登录成功到断开连接,整个过程称为一个会话。但是在 Web 应用中,我们一般通过 Cookie来实现会话,它关系到不同账号的状态,其中还涉及敏感数据,所以 Cookie 的安全性至关重要。
Cookie 和会话简介
简单来说,Cookie 是 Web 服务端发送给用户浏览器的一小段数据,浏览器会存储这些数据,数据里边可能会记录一些登录凭证的东西,比如之前提交过的表单,密码。cookie存放在浏览器中。并在后续发往服务器的请求中带上它们。
因为 HTTP 协议本身是无状态的,底层的 TCP 连接会断开,用户的 IP 地址也可能变化,但是 Web 应用的服务端需要记住每一个访问者的状态,例如用户登录一次之后执行其他操作时无须再登录,所以在这个过程中就需要存储一份数据来标识用户。Cookie 是一种将数据存储在客户端的方式,我们可以通过 Cookie 将用户标识存储在客户端,也有一些很老的 Web 应用是使用 URL 参数来存储这个标识的。但是将用户标识存放在 Cookie 或 URL 参数中都有个问题:在浏览器端,用户可以查看和篡改这些数据。
如果 Web 应用希望存储一些敏感数据或不希望被用户篡改的数据,最好的办法是将数据存储在服务端,并且为该用户的数据分配一个随机的 ID,在客户端仅存储这个 ID,然后用户每次访问服务器都要带上这个 ID,服务端用这个 ID 去查已存储的数据就知道当前的访问者是谁。过程如下所示
在这个过程中,服务端存储的这份数据称为 Session,分配给客户端的这个随机 ID 称为SessionID。在大多数场景中,SessionID 都是通过 Cookie 分发给客户端的,然后客户端每次访问服务器都会带上这个 Cookie。在一些很老的手机 WAP 应用中,考虑到有些功能机的浏览器不支持 Cookie,也有通过 URL 参数传输 SessionID 的,但现在已经非常少见了。
我们讲“会话”的概念一般是指从用户登录直到退出期间客户端与服务端的交互过程。“会话”对应的英文单词是 Session,在开发语言中服务端存储的会话数据也称为 Session,一般前者是基于后者实现的。严格来讲,这两个概念是有差异的,但大家已经习惯这样的说法了,读者要根据上下文来判断这个词的具体含义。
也有一些应用将会话数据加密后存放在 Cookie 中,服务端就不需要存储会话数据,而是每次收到请求后都解密 Cookie 中的数据,取出会话数据的内容,这样也达到了用户不可读和不可篡改的目的。
第一方 Cookie 和第三方 Cookie
第一方 Cookie (First-Party Cookie) 是指用户当前访问的网站直接植入的 Cookie,通常是网站用于正常功能的 Cookie,比如标识用户身份、记住用户的语言设置等,这些 Cookie 必须存在,否则用户只能以匿名身份访问,网站也无法记住用户的偏好设置。
当用户访问一个网站时,如果这个网站加载了其他网站的资源,此时由其他网站植入的Cookie 就称为第三方 Cookie (Third-Party Cookie)。比如一个网站嵌入了另一个网站的广告或者访问统计代码,通常这些第三方 Cookie 是用于追踪访问者,实现个性化广告投放的。
当不同的网站嵌入了同一个第三方网站资源时,用户访问这些网站时会带上相同的第三方Cookie 去加载第三方资源,所以第三方网站通过这个第三方 Cookie 就可以实现用户在不同网站的访问行为分析,从而实现更精准的广告投放。假设你经常访问体育相关的网站,那么你在其他的网站上可能也会看到体育相关的商品广告,因为这些网站嵌入了同一个广告提供商的代码。
第一方 Cookie 和第三方 Cookie 是相对的概念,我们根据用户是直接访问网站还是通过外部网站嵌入访问的,来决定该网站的 Cookie 是第一方的还是第三方的,而不是根据 Cookie 自身的属性决定。所以,一个 Cookie 在某些情况下是第一方 Cookie,换了个场景就可能就是第三方Cookie,这取决于当前用户所访问的网站。例如上图中,用户访问a.com 时,ad.com上的 Cookie 就是第三方 Cookie,而如果用户直接访问 ad.com,那么 ad.com 的 Cookie 就是第一方Cookie。(第一方还是第三方看具体情况)
所有浏览器都会接受第一方 Cookie,但是浏览器的隐私策略可能会阻止部分第三方 Cookie。下面要介绍的 SameSite 属性也会阻止第三方 Cookie。
cookie属性
在 Web 安全中,Cookie 相当于服务端颁发给用户的身份凭证,如果攻击者获取了用户的凭证,就相当于获取了用户的身份,即不需要账号与密码就可登录该账号,所以保证 Cookie 的安全至关重要。
Cookie 有多个属性,服务端在设置 Cookie 时可以设置相应的属性值,基本上每个属性都与安全有关系。
Domain 属性
Domain 属性用于指定 Cookie 在哪些域名中生效,即访问哪些域名时浏览器会发送这个Cookie,该属性也决定了哪些域名的网页可以通过 JavaScript 访问这个 Cookie。如果域名前面带一个点号“.”表示该 Cookie 对当前域名及其子域名都有效,浏览器访问这些子域名时都会带上这个 Cookie; 如果域名前不带点号,表示 Cookie 仅对当前域名有效。
在服务端通过 Set-Cookie 写入 Cookie,或者前端页面通过 JavaScript 设置 Cookie 时,根据RFC 6265@中的定义,可以将 Domain 的值指定为当前域名或者当前域名的父域名,但不能指定为当前域名的子域名,否则浏览器会拒绝写入。
如果在 Set-Cookie 中不指定 Domain 属性,Cookie 的生效范围仅限于当前域名 (即请求Host 头指定的域名),它被称为 Host-Only Cookie。目前主流的浏览器都遵循 RFC 6265 规范,在 Set-Cookie 或 JavaScript 中写入 Cookie 时只要加了 Domain 属性,即使没有点号“.”前缀,它也不是 Host-Only Cookie。例如在 Set-Cookie 中指定了 Domain 属性,即使指定Domain=example.com,Cookie 的生效范围也是.example.com,即对所有子域名也生效,这么做实际上是扩大了 Cookie 的生效范围,让子域名应用也能访问这个 Cookie。所以在没有与子域名网站共享登录状态的情况下,在 Set-Cookie 中不显式指定 Domain 属性是更安全的做法。
需要注意的是,Domain 属性不包含端口信息,即 Cookie 的域名隔离不受端口的限制,如果一个域名同时在不同的端口运行了多个 Web 应用,使用 Cookie 存储重要数据时也需要评估每个应用的安全性,防止 Cookie 通过其中某个应用的安全漏洞被泄露出去。
Path 属性
其他子域名的应用或者其他端口的应用除了可以读取当前应用的 Cookie,也能写入特定名称的 Cookie,从而干扰当前应用,让它读到错误的 Cookie 内容,常见的攻击方式是固定会话攻击。
Path 属性用于指定 Cookie 的生效路径,只有访问这个路径或其子路径时,浏览器才会发送这个 Cookie。如不设置,Path 属性的默认值就是当前页面所在的路径。如果一个域名中不同路径有很多不同的应用,同名的 Cookie 会造成干扰,这时可以设置 Cookie 的 Path 属性将它们区分开来。
但是不能依赖 Path 属性来做安全隔离,因为在浏览器中一个路径的页面可以通过 iframe 入另一个路径的页面,而这两个页面是同源的。所以它们之间的 DOM 可以互访问,一个路径的页面可以读取另一个路径页面的 Cookie。例如,有一个敏感的 Cookie 设置了 Path=/admin/,这个路径以外的其他页面可以通过如下代码获取这个敏感的 Cookie:
<iframe id="test" src="/admin/" width=0 height=0></iframe>
<script>window.onload = function() (
alert(document.getElementById('test').contentDocument.cookie);
</script>
通过这个做法,每个页面都能读取当前域名任何路径下的 Cookie,所以在一个域名的不同路径下运行相互不信任的 Web 应用也是危险的做法。这个安全问题比较常见,经常有网站在路径 example.com/forum/下运行了一个论坛系统,然后又在路径 example.com/wiki/下运行了一个wiki 系统,即使二者的 Cookie 都设置了 Path 属性,也起不到隔离作用,一个应用的漏洞会威胁另一个应用。更安全的做法是,将不同的应用部署在不同的域名或子域名下,让同源策略保证 Cookie 的安全性。
Expires 属性
服务端可以通过 Expires 属性来设置 Cookie 的有效期,浏览器会在这个 Cookie 到期后自动将其删除。没有指定 Expires 属性的 Cookie 叫“临时 Cookie”,关掉浏览器后将自动删除。有些地方也将临时 Cookie 称为“会话 Cokie”,即仅在当前会话中有效,可以实现关掉浏览器就自动结束会话,下次再打开网站则需要重新登录。
需要注意的是,部分浏览器可以设置成每次启动时“打开上一次浏览的网页”,浏览器为了保证下次启动时用户体验的连续性,即使关闭浏览器也不会删除临时 Cookie。在这种情况下,临时 Cookie 实际上已经成为永久 Cookie。
设置了有效期的 Cookie,浏览器并不能保证它在有效期之内不会被删除,浏览器对每个站点有最大 Cookie 数量的限制,超过这个限制时会删除旧的 Cookie。所以如果存在可以向受害者植入Cookie的漏洞(如CRLF 注入漏洞),攻击者可以植入多个 Cookie,导致受害者的正常 Cookie被“挤掉”
HttpOnly 属性
在大多数情况下,Cookie 只是用来与服务端交互的,并不需要让客户端的 JavaScript 读取它。允许 JavaScript 读取 Cookie 会增加 Cookie 泄露的风险,如果网站存在跨站脚本 (XSS)漏洞,恶意脚本就可以窃取受害用户的 Cookie,盗取身份凭证。
HttpOnly 属性的作用是让 Cookie 只能用于HTTP/HTTPS 传输,客户端JavaScript 无法读取它,从而在一定程度上减少了 XSS 漏洞带来的危害。
但是,某些服务端应用框架的调试或报错信息会展示 HTTP 请求头的内容,PHP 开发人员常用的获取服务端信息的 phpinfo0函数也会展示请求头信息,这样会使 Cookie泄露到 Web 前端页面。如果站点存在 XSS 漏洞,还是可以通过 JavaScript 取带有 HttpOnly属性的 Cookie。
一种同时利用 XSS 漏洞和TRACE 方法的攻击可获取带有 HttpOnly 属性的 Cookie,这种攻击叫 Cross-Site Tracing (XST)。TRACE 方法是用于 Web 服务器调试与诊断的,服务端返回客户端请求中的所有信息。在存在 XSS 漏洞的应用中通过 XMLHtpRequest 向服务端发起TRACE请求,即可获取 Cookie 的内容。这种攻击方法现在很难奏效了,因为新版本的浏览器基本上都不再支持在XMLHttpRequest 中使用TRACE 方法,而且接受TRACE方法的 Web 服务器也不多
在Apache HTTP Server 2.2.x 版本中,当 HTTP 请求头的长度超过所允许的最大值时,服务器会返回一个 400 错误页面,其中包含了所有请求头信息。在存在 XSS 漏洞的应用中,通过XSS 生成一个超长的 Cookie,再通过 XMLHttpRequest 访问服务端,就可以读取带有 HttpOnly属性的 Cookie。
可以看到,通过 JavaScript 代码读取带有 HttpOnly 属性的 Cookie 都要借助服务端输出的内容,所以除了对关键 Cookie 设置前端保护措施,还要关注服务端有没有输出 Cookie 的场景。
会话安全
在 Web 应用中,会话的本质是标识不同的访问者,并记录他们的状态。攻击者如果窃取到个合法的会话标识,或者伪造会话标识,都相当于盗取了一个账号的身份。如果服务端会话管理不当,也可能造成敏感数据泄露。
会话管理
会话的安全在 Web 应用中至关重要,下面介绍关于会话的安全管理。
会话ID的随机性
会话ID(SessionID)最基本的要求是随机性,让攻击者无法猜测出来。所以,不能简单地使用用户的ID、时间戳等数据作为 SessionID,也不能基于用户的公开信息简单计算出一个值。
同时,SessionID 也要足够长,以防止攻击者通过遍历穷举的方法获取 SessionID。
过期和失效
很多比较敏感的应用都有超时自动退出账号的机制,大多数有“记住登录状态”功能的应用也会有超时机制,只是这个超时时间设置得比较长。
在有超时机制的 Web 应用中,不应该仅通过 Cookie 的有效期来实现超时退出,因为访问者可以任意修改 Cookie 的 Expires 属性,或者他记住这个值后一直重复使用,所以超时机制应该是在服务端来实现的。如果应用的需求是客户端在一定时间后退出登录状态,应该由服务端在时间到期后删除已存储的会话数据,或者标记一个SessionID 已失效。
另外,在修改密码、账号挂失等业务场景中,也应该在服务端使该账号相关的会话数据失效,这样攻击者盗取的账号也会被强行退出。
绑定客户端
在应用中、如果将会话与客户端绑定会更安全,因为即使攻击者窃取了 SessionID,也无法在新的设备中登录目标网站。
在Web应用中可以将浏览器 User-Agent 与会话绑定,这种绑定关系比较弱,在移动App中有更好的唯一标识客户端的机制,可以实现会话与设备之间更强的绑定关系。
另一个更安全的做法是,在登录时将会话与访问者的 IP 地址绑定,不少网银 App 有这种机制,所以当手机从蜂窝网络切换到家里 Wi-Fi时,网银App 会要求你重新登录。这种机制可以带来更强的安全保护,但是在一定程度上也影响了用户体验。
安全传输
现代Web 应用基本上都是将 SessionID 写入 Cookie 中,所以设置相应 Cookie 的安全属性非常重要,大多数情况下建议开启 HttpOnly和Secure 属性
一些老的 Web 应用通过URL 参数来传递 SessionID,这是非常不安全的做法,有很多场景可能造成 SessionID泄露,例如:
- 在跳转到站外链接,或者加载其他站点的资源时,SessionID 会通过 Referer 被泄露出去
- 在浏览器历史记录中会保存URL,现代浏览器还支持多设备之间同步历史记录
- 服务端日志通常会记录URL,这些目去可能被分发给做日志分析的团队和部门
- 用户不会意识到URL 中存在然信息,可能将 URL 通过IM、论坛分享给他人
客户端存储会话
前面讲的会话案例全部是会话存储在服务端的场录,客户端只保存一个很短的 SessionID,也有一些应用将会话存储在客户端,最典型的就是将JWT(JSON Web Token) 用下会话管理
JWT本质上是带签名的JSON 数据。有必要的情况下也可以对它加密,在用户登录后,服务端将会话信息生成为一个带签名的JWT 写入客户端,客户次访问时都带上JWT,服务端可以验证签名的有效性,并提取会话相关的信息。使用这种机制可以将会话完全存储在客户端,使服务端无状态,所以非常容易对服务端扩容,后端有 Web 服务器集群时不需要在多台服务器之间做会话同步。
客户端存储会话有一个致命的缺陷,就是已签发的会话 Token 无法吊销,这将导致很多账号安全功能无法实施,如退出账号、修改密码等。因此,只能在签发 Token 时加入 Token 有效期,并尽量使用短的有效期,但是过短的有效期又会影响用户体验。
另外,签名用的密钥的安全管理也很重要,一旦密钥被泄露,攻击者就可以签发任意账号的JWT,使所有账号都受到威胁。
版权归原作者 Festival_ 所有, 如有侵权,请联系我们删除。