返回介绍

4.8 Cookie 输出相关的安全隐患

发布于 2024-10-10 22:16:32 字数 13318 浏览 0 评论 0 收藏 0

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 节。

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文