4.8 Cookie 输出相关的安全隐患
Web 应用中广泛使用 Cookie 来进行会话管理,而如果 Cookie 的使用方法不当就会滋生安全隐患。与 Cookie 相关的安全隐患大致可分为以下两类。
- Cookie 的用途不当
- Cookie 的输出方法不当
本节将首先介绍 Cookie 的正确用途,即 Cookie 应当被用于保存会话 ID,而不应该将应用的数据保存在 Cookie 中。具体原因在后面会进行说明。
接着我们会详细讲述输出 Cookie 时容易产生的安全隐患,有如下两种。
- HTTP 消息头注入漏洞
- Cookie 的安全属性设置不完善
以上两种都是与被动攻击相关的安全隐患。HTTP 消息头注入漏洞在 4.7.2 节中已经做过介绍。而 Cookie 的安全属性设置不完善这一点将在 4.8.2 节中讲述。
4.8.1 Cookie 的用途不当
Web 应用中需要存储包含多个网页的信息时,一般会使用 PHP 或 Servlet 容器等提供的会话管理机制。通常情况下,会话管理机制仅将会话 ID 保存至 Cookie,而将数据本身保存在 Web 服务器的内存或文件、数据库中。如果在 Cookie 中保存了不该保存的数据,就有可能产生安全隐患。
- 不该保存在 Cookie 中的数据
下面我们来看一下因在 Cookie 中保存了不恰当的内容而引发安全隐患的情况。我们知道,外界无法更改会话变量,而应用的用户则能够更改自己的 Cookie 值。因此,如果将不希望被用户擅自更改的数据保存在 Cookie 中,就有可能会导致安全隐患。
像用户名和权限信息等,就是不可以被用户擅自更改的数据的代表性例子。一旦将这些信息保存在 Cookie 中,就有可能出现用户越权操作或越权浏览等现象。详情请参考 5.3 节。
- 参考:最好不要在 Cookie 中保存数据的原因
尽管将数据保存在 Cookie 中并非一定会造成安全隐患,但一般还是不推荐这种做法。为了解释其原因,我们先来看一下表 4-17 中所归纳的将数据保存至 Cookie 和使用会话变量这两种方法的比较。
表 4-17 Cookie 和会话变量的比较
Cookie 会话变量 易用性 通过 API 进行取值和赋值 与普通变量的用法基本一致 存储数组或对象 需要在应用中转换为字符串 大多都和变量一样可以直接赋值 容量限制 有严格的限制 使用上没有限制 用户直接查看存储的信息 容易 不可能 漏洞等导致 Cookie 泄漏后的信息泄漏情况 Cookie 被泄漏后信息也会被泄漏 可以通过控制使信息不易泄漏 数据被用户更改 容易 不可能 数据被第三方更改 如果有 XSS 或 HTTP 消息头注入等漏洞就可能被更改 即使有可导致 Cookie 被更改的漏洞,会话变量也无法被更改 控制信息的有效期限 容易 仅限当前会话 不同服务器之间共享信息 域名相同时可能 基本不可能 如上表所示,使用会话变量无法实现而使用 Cookie 可以实现的项目,只有控制信息有效期限和不同服务器之间共享信息这两点。除此以外,会话变量既安全又便利,因此,一般来说最好使用会话变量。
会话变量之所以可以通过控制使信息不易泄漏,是因为在 Web 应用中,在显示机密信息时可以要求用户再次输入密码(再认证)。另外,会话过期(Session Timeout)后,保存在会话中的信息也就会无法显示。而将信息保存在 Cookie 中的情况下则很难进行这样的控制。
另一方面,如果需要保存一些横跨会话和服务器的信息,则可以使用 Cookie。其中一个典型的案例就是登录页面的“保持登录状态”功能。图 4-71 为 Google 的登录页面,密码框下有“保持登录状态”的单选框,选中它后就会通过 Cookie 保持登录状态。
图 4-71 Google 的登录页面
关于如何实现“保持登录状态”的功能,请参考 5.1.4 节。另外,此情况下 Cookie 中同样也应当只保存随机数,称为令牌。而不要将用户名和密码等“数据”保存在 Cookie 中。认证状态等信息则由服务器来管理。
专栏:Padding Oracle 攻击与 MS10-070
在一些 Web 应用开发框架中,会话信息不仅会被保存在服务器端,而且还会在客户端以 hidden 参数或加密 Cookie 的形式保存。其中一个典型的例子就是 ASP.NET,它的页面状态(ViewState)被保存在 hidden 参数中,而认证状态(Form Authentication Ticket)则被保存在了 Cookie 中。而且这些值都会使用 RFC2040 算法进行加密。
然而,在 2010 年 9 月 17 日的 Ekoparty 安全会议上,T.Duong 与 J.Rizzo 两人表示通过名为 Padding Oracle 48 的攻击方法就能够破解这些加密信息。微软立刻意识到了事态的严重性,成立紧急对应小组在 10 天时间内开发出了对应的补丁程序,并打破更新补丁每月发布一次的惯例,破例对外紧急提供。这就是 MS10-070 安全更新补丁(2010 年 9 月 29 日发布)。
而从这个事件中我们也能够得到两个教训。第一,即使进行了加密,保存在客户端的信息也有被解密的风险。第二,平台中提供的会话管理机制被曝出安全隐患后,需要在最短时间内将问题解决。有关平台中的安全隐患的对应措施,请参考 7.1 节。
- 参考
- Ekoparty 安全会议上发表的幻灯片(英文)
http://netifera.com/reserch/poet/PaddingOraclesEverywhereEkoparty2010.pdf
- 安全性公告 MS10-070“ASP.NET 的安全漏洞可能引发信息泄漏(2418042)”
http://technet.microsoft.com/en-us/security/bulletin/MS10-070
48 Padding Oracle 是一个解密手段的名称,与著名的数据库 Oracle 没有关系。
4.8.2 Cookie 的安全属性设置不完善
概要
正如第 3 章中介绍的那样,Cookie 中含有名为 Secure 的属性(以下记为安全属性),指定了安全属性的 Cookie 仅在 HTTPS 传输的情况下才会被浏览器发送至服务器。而如果 Cookie 没有指定安全属性,那么即使应用中使用了 HTTPS 传输,Cookie 也仍然有可能会以明文的方式传输,这样就会有被监听的风险。
Cookie 中通常保存了会话 ID 等事关安全性的重要信息,因此一旦被窃听就会直接导致伪装攻击。
为了解决 Cookie 的安全属性设置不完善这一问题,最直接的对策就是设置 Cookie 的安全属性。然而,有些网站同时使用 HTTP 与 HTTPS 两种传输方式,如果在存有会话 ID 的 Cookie 中设置了安全属性,应用就可能会运行不正常。这种情况下可以采取以下解决方法,即除了使用会话 ID,再生成一个令牌作为设有安全属性的 Cookie,并在每个 HTTPS 页面中确认该令牌值。详情请参考本节的“对策”。
Cookie 的安全属性设置不完善总览
攻击手段与影响
下面我们就来看一下针对 Cookie 的安全属性设置不完善这一问题的攻击模式与其造成的影响。本书事先为读者在网络上准备了使用 HTTPS 并且生成不带安全属性的 Cookie(PXPSESID)的网页( https://www.hash-c.co.jp/wasbook/set_non_secure_cookie.php )。源代码如下。
代码清单 set_non_secure_cookie.php
<?php
ini_set('session.cookie_secure', '0'); // 关闭安全属性
ini_set('session.cookie_path', '/wasbook/'); // 指定路径
ini_set('session.name', 'PXPSESID'); // 更改会话 ID 名
session_start(); // 会话开始
$sid = session_id(); // 取得会话 ID
?>
<html>
<body>
会话已经开始 <br>
PXPSESID =
<?php echo htmlspecialchars($sid, ENT_NOQUOTES, 'UTF-8'); ?>
</body>
</html>
此页面被托管在笔者所在企业的主页上,为了防止被恶意使用,这里采取了限定 Cookie 的路径和更改默认会话 ID 名称等方法。
接下来,我们就来体验一下如何使用这个页面窃听不带安全属性的 Cookie。
1. 启动 Fiddler
2. 用户浏览上述页面后,浏览器中就被设置了 Cookie(PXPSESID)
3. 访问恶意网页
4. 在 Fiddler 中能看到恶意网站发送的请求中附带了 Cookie 信息
下面我们来讲解具体的流程。在 http://example.jp/48/ 的菜单(下记为“/48/ 菜单”)中点击“1.HTTPS 中设置 Cookie(无安全属性)”链接,进入设置 Cookie 的页面。如图 4-72 所示。
图 4-72 设置 Cookie 的页面
此时,浏览器中就被设置了不带安全属性的 Cookie。
接着返回到 /48/ 菜单,点击“2.48-900: 浏览恶意网站”链接。也可以直接输入 URL http://trap.example.com/48/48-900.html 进入。此页面上有一个看不见的图像(高度和宽度都设置为 0),它的引用地址为 http://www.hash-c.co.jp:443/wasbook/ 。下面是 HTML 代码。
<body>
恶意网页
<img src="https://www.wenjiangs.com/wp-content/uploads/2024/10/http://www.hash-c.co.jp:443/wasbook/" width="0" height="0">
</body>
URL 中的端口号 443 是 HTTPS 的默认端口,但由于指定的协议为 http:,因此该请求在被发送时并没有进行加密。另外,虽然此 URL 指定的目标中不存在图像,但由于目的是让浏览器发送 Cookie,所以就算没有图像,攻击也照样能成功。
浏览恶意网页后,Fiddler 会弹出警告(下图),这里不用管它直接点击 OK 按钮 49 。
49 使用 Wireshark 确认数据包后,Host 消息头中的端口号(:443)设置没有问题,因此该警告或许是 Fiddler 的 Bug。
图 4-73 Fiddler 弹出警告后点击 OK 按钮
通过下图就能够查看发向目标网站的 HTTP 消息。
图 4-74 从恶意网站发出的请求中附带了 Cookie
在恶意网站使用 443 端口发送 HTTP 请求(明文)之后,原本应该使用 HTTPS 进行传输的 Cookie 值在未经加密的情况下开始在网络上传输。其情形如下图所示。
图 4-75 针对 Cookie 的安全属性不完善实施攻击
而一旦攻击者成功窃取未经加密的 Cookie 值,就能用它来实施会话劫持。
- 关于抓包方法的注意点
此处讲述的监听方法中网络传输经过了代理,这与不经过代理时的条件略有不同。
没有经过代理时,浏览器与 Web 服务器直接通信,因此要让浏览器发送请求,就需要指定 Web 服务器上开放的端口。就像上面的试验中指定了端口号 443。
而通信经过代理时,浏览器的所有请求都会先发送到代理服务器。因此,使用身为代理的 Fiddler 观测时,可以发现指定 443 以外的端口号也能够发送请求。具体情形如下图所示。
图 4-76 有代理和无代理时的 HTTP 请求观测
不经过代理的 HTTP 请求无法通过 Fiddler 等代理工具进行观测,观测时可以使用 Wireshark 等嗅探器(抓包软件)。使用嗅探器进行数据包解析的技术方法请参考 Chris Sanders 著的《Wireshark 数据包分析实战》[1]。
安全隐患的产生原因
Cookie 的安全属性设置不完善的直接原因显而易见,就是没有给 Cookie 设置安全属性,以笔者多年来诊断安全隐患的经验来看,不给 Cookie 设置安全属性的主要原因有如下两类。
- 开发者对安全属性毫不知情
- 设置安全属性后应用无法运行
经过本书的学习后,相信第一类原因就能够得到解决。因此,下面我们来主要讲述设置安全属性后应用无法运行的情况。
- 什么样的应用程序不能在 Cookie 中设置安全属性
有些 Web 应用同时使用 HTTP 和 HTTPS,典型例子为电子商务网站。多数电子商务网站中,用户浏览商品页面时使用的是 HTTP 传输,而当用户选择完商品进入支付阶段时使用的则是 HTTPS。图 4-77 即展示了同时使用 HTTP 和 HTTPS 的电子商务网站的页面跳转情况。
图 4-77 同时使用 HTTP 和 HTTPS 的网站的页面跳转
Web 应用中同时使用 HTTP 和 HTTPS 时,为保存会话 ID 的 Cookie 设置安全属性是非常困难的。因为设置了安全属性后,HTTP 传输的页面就无法接收到 Cookie 中的会话 ID,因此也就无法利用会话管理机制。由于使用 HTTP 的网页为了实现购物车等功能也需要利用会话管理机制,因此当前很多使用 HTTPS 的网站都没有设置 Cookie 的安全属性。
这种情况下,使用令牌是一种行之有效的对策。详情会在稍后讲述。
对策
为了解决 Cookie 的安全属性设置不完善这一问题,最直接的对策就是要设置 Cookie 的安全属性。
- 给保存会话 ID 的 Cookie 设置安全属性的方法
在 PHP 中给保存会话 ID 的 Cookie 设置安全属性,只需在 php.ini 中设置如下。
session.cookie_secure = On
Apache Tomcat 中使用 HTTPS 传输请求时,会自动给保存会话 ID 的 Cookie 设置安全属性。
而使用 ASP.NET 时,则需要如下编辑 web.config 文件。
<configuration> <system.web> <httpcookies requireSSL="true" /> </system.web> </configuration
- 使用令牌的对策
无法给保存会话 ID 的 Cookie 设置安全属性时,可以采用通过令牌来防止会话劫持的方法。此方法与 4.6.4 节中讲述对策时介绍的方法相同。将保存令牌值的 Cookie 设置安全属性后,HTTP 页面与 HTTPS 页面将会共享会话变量,而即使会话 ID 被窃听,HTTPS 页面也能够防止会话劫持。
为了给令牌的 Cookie 设置安全属性,这里我们将 /463/46-015.php 按照以下代码进行修改。该脚本中不仅加上了安全属性,同时也设置了 HttpOnly 属性。
代码清单 /48/48-001.php
<?php // /dev/urandom 通过 /dev/urandom 实现伪随机数生成器 function getToken() { $s = file_get_contents('/dev/urandom', false, NULL, 0, 24); return base64_encode($s); } // 假设到这里已经成功通过认证 session_start(); session_regenerate_id(true); // 重新生成会话 ID $token = getToken(); // 生成令牌 // 生成带有安全属性的令牌 Cookie setcookie('token', $token, 0, '', '', true, true); $_SESSION['token'] = $token;
然后再在 HTTPS 的页面上通过以下脚本检验令牌值。内容与 /463/46-016.php 相同。
代码清单 /48/48-002.php
<?php session_start(); // 确认用户名【省略】 // 确认令牌 $token = $_COOKIE['token']; if (! $token || $token != $_SESSION['token']) { die('认证错误。令牌值错误。。'); } ?> <body> 检验令牌,确认通过认证。 </body>
为了确认上面的脚本,接下来我们使用以下 URL 来浏览页面。
https://example.jp/48/48-001.php
或者在 /48/ 菜单中点击“3.48-001: 生成令牌(SSL)”链接。这时页面显示如下。
图 4-78 试验环境中即使出现证书错误也依然可以选择继续访问
由于虚拟机中无法附带正规证书,因此便导入了自签名证书,这也是出现上图错误信息的原因。而考虑到是虚拟机环境,因此请选择“继续浏览此网站”。关于自签名证书的风险请参考 7.2.3 节。
图 4-79 校验令牌方式的页面跳转
下面我们就来在 HTTP(非 SSL)中尝试此页面跳转。在 /48/ 菜单中点击“4.48-001: 生成令牌(非 SSL)”链接。结果为,48-001.php 中显示“认证成功”后,48-002.php 中显示了如下错误消息。
图 4-80 非 SSL 状态下无法收到令牌
这是因为,48-001.php 中生成的令牌值被保存到了设置了安全属性的 Cookie 中,因此非 SSL 传输时 48-002.php 就没有收到令牌。也就是说,可以确认安全属性运作正常。
- 使用令牌能确保安全性的原因
即使没有设置安全属性的会话 ID 被窃听,但只要令牌值设置了安全属性并被加密,HTTPS 页面就不会遭到会话劫持。原因如下。
- 服务器输出令牌的时机只有一次,即认证成功的时候
- 令牌在 HTTPS 的页面被生成(服务器→浏览器)
- 令牌被加密后由浏览器发送出去(浏览器→服务器)
- 浏览 HTTPS 的页面必须要有令牌
换言之,令牌值在服务器和浏览器之间传输时都进行了可靠的加密,而浏览 HTTPS 页面时需要的令牌值不可能被第三方得知,因此便确保了安全性。
除安全属性外其他属性值需要注意的地方
除了安全属性之外,Cookie 中还有其他会影响安全性的属性。第 3 章中已经介绍了 Cookie 的属性,因此这里将主要介绍保存会话 ID 的 Cookie 的属性。
- Domain 属性
Domain 属性的默认状态(即不指定的状态)是最安全的。只有在多台服务器中共享 Cookie 时才需要指定 Domain 属性,而一般来说在多台服务器间共享会话 ID 是没有意义的。
虽然 PHP 中能够指定会话 ID 的 Domain 属性,但是在没有特殊理由的情况下,最好不要指定 Domain 属性。
- Path 属性
PHP 的会话 ID 默认生成 path=/ 的属性。一般情况下这种设置不会有问题,而如果要针对每个路径生成不同的会话 ID,则可以指定 Path 属性。
需要注意的是,即使指定了 Path 属性也不会提高安全性。因为 JavaScript 的同源策略是以域名为单位的,而不是以路径为单位。这在 3.2 节中已经做过讲解。
- Expires 属性
会话 ID 的 Cookie 通常不指定 Expires 属性,即浏览器被关闭的同时 Cookie 也会被删除。设置 Expires 属性后,关闭浏览器后也照样能维持认证状态。详细的使用方法将在 5.1.4 节中讲述。
- HttpOnly 属性
设置了 HttpOnly 属性的 Cookie 无法通过 JavaScript 访问。但由于 JavaScript 访问会话 ID 并没有什么意义,因此建议每次都给 Cookie 加上 HttpOnly 属性。正如 4.3 节中所介绍的那样,HttpOnly 属性有助于减轻跨站脚本攻击造成的损害,但这并不是根本性的防范策略。
PHP 中给会话 ID 的 Cookie 设置 HttpOnly 属性,只需如下编辑 php.ini。
session.cookie_httponly = On
总结
本节讲述了 Cookie 输出的相关问题。其中有两点非常重要的是,原则上仅将 Cookie 用于保存会话 ID,以及使用 HTTP 传输的应用中给 Cookie 设置安全属性。
参考文献
[1] Chris Sanders(著). 诸葛建伟等(译)(2013).《Wireshark 数据包分析实战》. 人民邮电出版社
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论