不使用 Cookie 的 ASP.NET 会话管理:风险与利益
摘要:Dino 探究无 Cookie 会话的优缺点,并且讨论为什么应该避免在会话状态中存储有价值的信息。
![*](http://img.microsoft.com/library/gallery/templates/MNP2.Common/images/3squares.gif)
本页内容
我们承认这一点 — 我们对会话状态这一概念是如此习以为常,以至于我们忘记了会话状态是在 1997 年用 Active Server Pages (ASP) 引入的一个手段。会话状态使开发人员能够在用户与应用程序交互这段时间内持久保存有关该用户的一块信息。特定于用户的信息通常会保留 20 分钟长的时段,而每当用户返回该站点时,该时段都将重新开始计时。
当用户首次连接到站点时,将以内存块的形式创建一个全新的会话状态以存放数据,同时,还会创建一个 ID 以便将其与当前用户唯一地联系起来。当下一次发出请求时,该用户将被要求提交该会话 ID,以便检索并正确地还原会话状态。会话 ID 是 ASP 和 ASP.NET 完全自主生成的字母数字字符串。用户如何管理它并确保用每个后续请求来包装它呢?
HTTP 协议的性质是无状态的,并且没有任何人试图更改这一事实。差不多二十年以前,当 Netscape Corporation 开发它的第一个浏览器时,它“发明”了一种通过 HTTP 工作的持久性机制。它将其称为 HTTP Cookie。有趣的是,计算机科学行话中的术语“Cookie”仅仅表示一块由应用程序持有的不透明数据,它会影响用户但永远不会由用户直接管理。
因此,Cookie 存储会话的 ID,而浏览器则在 Web 服务器和本地用户的计算机之间来回移动它们的内容。当启用了 Cookie 的浏览器收到响应数据包时,它将寻找附加的 Cookie,并将它们的内容存储到本地 Windows 目录中特定文件夹的某个文本文件中。Cookie 还包含有关该源站点的信息。接下来,当浏览器向该站点发送请求时,它会在 Cookie 文件夹中查找源自该域的 Cookie。如果找到,则该 Cookie 自动附加到传出的数据包中。该 Cookie 将命中服务器应用程序,并在此被检测、提取和处理。
最终,Cookie 使 Web 站点更加易于导航,因为它们在用户体验之上提供了必然跨越多个请求的连续性错觉。
Cookies 是不是一个问题?
多年以来,Cookie 只被视为一种技术功能,并且在很大程度上被忽略了。几年以前,针对 Web 安全的世界范围的浪潮将人们的注意力集中于 Cookie 身上。Cookie 被断定包含危险的程序,它们甚至能够超出计算机的物理边界来窃取有价值的信息。
不言而喻,Cookie 不是程序,因而无法自行收集任何信息 — 更不用说有关用户的任何个人信息。更加清楚的是,Cookie 是 Web 站点可以放置在用户的计算机中以便以后检索和重用的一段文本。所存储的信息是由无害的名称-值对组成的。
要点在于,Cookie 不是标准 HTTP 规范的一部分,因此它们意味着浏览器和 Web 站点之间的一种协作。并非所有浏览器都支持 Cookie,而且更为重要的是,并非所有用户都在他们自己的浏览器副本中启用 Cookie 支持。
在历史上,有一些 Web 站点功能是如此紧密地与 Cookie 相联系,以至于很难区分究竟是哪个功能最先出现。一方面,用 Cookie 对会话状态管理和用户身份验证进行编码要容易得多。另一方面,如果您观察一下站点与用于访问页的浏览器有关的统计信息,那么您可能会惊讶地发现,相当一部分用户在连接时禁用了 Cookie。这一点会对开发人员有所启示。
总而言之,Cookie 本身并不是问题,但它们的使用无疑给予一些服务器代码在客户端计算机中存储一段数据的能力。这预示着一些潜在的安全风险和一种不够理想的总体状况。(在某些情况以及某些国家/地区中,应用程序要求 Cookie 工作甚至是非法的。)
进入无 Cookie 会话
在 ASP.NET 中,无需使用 Cookie,就可以有选择地建立必要的会话-用户联系。非常有趣的是,除了以下配置设置以外,您无需在 ASP.NET 应用程序中更改任何内容即可启用无 Cookie 会话。
<sessionState cookieless="true" />
ASP.NET 会话状态的默认设置是在 machine.config 文件中定义的,并且可以在应用程序根文件夹中的 web.config 文件中重写。通过确保上述行出现在根 web.config 文件中,您可以启用无 Cookie 会话。就是这样 — 简单而有效!
<sessionState>节点还可以用于配置会话状态管理的其他方面,包括存储介质和连接字符串。但是,就 Cookie 而言,只需您将 cookieless 属性设置为 true(默认设置为 false)。
请注意,会话设置是应用程序范围的设置。换句话说,您站点中的页要么都将使用要么都将不使用 Cookie 来存储会话 ID。
当不使用 Cookie 时,ASP.NET 在哪里存储会话 ID 呢?在这种情况下,会话 ID 插入到 URL 内的特定位置中。下图显示一个使用无 Cookie 会话的真实站点的快照。
![](https://i-blog.csdnimg.cn/blog_migrate/2b2c9ed96b81e13dfe2b7509b1ed6fd9.png)
图 1. 使用无 Cookie 会话的 MapPoint
假设您请求了一个类似于 http://yourserver/folder/default.aspx 的页。正如您可以从 MapPoint 快照中看到的那样,资源名称前面的相邻斜杠进行了扩展,以便包含在内部填充了会话 ID 的括号,如下所示。
http://yourserver/folder/(session ID here)/default.aspx
会话 ID 嵌入到 URL 中,并且无需在其他任何地方持久保存它。唔,并不完全是这样。请考虑以下方案。
您访问了一个页,并且被分配了一个会话 ID。接下来,您清除了同一浏览器示例的地址栏,转到另一个应用程序并且开始工作。然后,您重新键入了上一个应用程序的 URL,并且(猜猜看)在您进入的过程中检索会话值。
如果您使用无 Cookie 会话,那么当您第二次访问该应用程序时,您将被分配一个不同的会话 ID,并且丢失以前的所有状态。这是无 Cookie 会话的一个典型的副作用。为了了解其原因,让我们进一步探讨无 Cookie 会话的实现。
实现
无 Cookie 会话的实现得益于下列两个运行时模块的努力:一个名为 SessionStateModule 的标准会话 HTTP 模块,以及一个名为 aspnet_filter.dll 的可执行文件。后者是一小段 Win32 代码,它充当 ISAPI 筛选器。HTTP 模块和 ISAPI 筛选器实现了相同的思想,不同之处在于 HTTP 模块由托管代码组成,并且需要 ASP.NET 和 CLR 触发才能工作。像 aspnet_filter.dll 这样的传统 ISAPI 筛选器是由 Internet 信息服务 (IIS) 调用的。二者都截获在请求处理过程中激发的 IIS 事件。
当新浏览器会话的第一个请求进入时,会话状态模块读取 web.config 文件中有关 Cookie 支持的设置。如果 节的 cookieless 属性设置为 true,则该模块生成一个新的会话 ID,通过将该会话 ID 填充到资源名称前面的相邻位置来分割 URL,并且使用 HTTP 302 命令将浏览器重定向到新的 URL。
当每个请求到达 IIS 入口时(远远早于它被移交给 ASP.NET),aspnet_filter.dll 获得了一个查看它的机会。如果该 URL 将会话 ID 嵌入到括号中,则会提取该会话 ID 并将其复制到一个名为 AspFilterSessionId 的请求标头中。然后,重写该 URL 以使其看起来像原来请求的资源,并且将其释放。这一次,ASP.NET 会话状态模块从请求标头中检索会话 ID,并且通过会话-状态绑定继续工作。
只要该 URL 包含可用来获取会话 ID 的信息,无 Cookie 机制就可以很好地工作。正如您稍后将看到的那样,这会造成一些使用限制。
让我们研究一下无 Cookie 会话的优缺点。
优点
在 ASP.NET 中,会话管理和表单身份验证是唯一的两个在后台使用 Cookie 的系统功能。通过无 Cookie 会话,您现在可以部署无论用户的有关 Cookie 的首选项如何都能正常工作的有状态应用程序。然而,就 ASP.NET 1.x 而言,仍然需要使用 Cookie 来实现表单身份验证。好消息是,在 ASP.NET 2.0 中,表单身份验证可以选择以无 Cookie 方式工作。
另一个经常提出的反对 Cookie 的理由是安全性。这是一个值得予以更多关注的要点。
Cookies 是无活动能力的文本文件,因此,这些文件可能被攻击者替换或损坏 — 只要他们获得了对计算机的访问。真正的威胁并不在于 Cookie 可以在客户端计算机上安装什么,而是在于它们可以向目标站点上载什么。Cookie 不是程序,并且永远不会像程序那样运行;然而,您计算机上安装的其他软件可以使用对 Cookie 的浏览器内置支持来远程从事破坏活动。
此外,Cookie 要受到被盗窃的风险。一旦失窃,包含有价值的和私人的信息的 Cookie 就可能将其内容泄露给恶意攻击者,并且为其他类型的 Web 攻击提供便利。总之,通过使用 Cookie,您将自己暴露在本可以消除的风险之中。这是真的吗?
缺点
让我们从另一个角度来考察安全性。您是否曾经听说过会话劫持?如果没有,则请阅读一下 TechNet Magazine 文章 Theft On The Web: Prevent Session Hijacking。简单说来,当攻击者获得对特定用户的会话状态的访问时,将发生会话劫持。其实质是,攻击者窃取有效的会话 ID,并且使用它侵入系统和窥探数据。获取有效会话 ID 的一种常见方式是窃取有效的会话 Cookie。鉴于此,如果您认为无 Cookie 会话保护了您应用程序的安全,那您就完全错了。实际上,对于无 Cookie 会话,会话 ID 直接显示在地址栏中!请尝试下列操作:
1. | 连接到使用无 Cookie 会话的 Web 站点(例如,MapPoint)并获得一个映射。此时,该地址存储在会话状态中。 |
2. | 抓取 URL(直至页名称)。不要包括查询字符串,但请确保该 URL 包括会话 ID。 |
3. | 将该 URL 保存到文件中,并将该文件复制/发送到另一台计算机。 |
4. | 在第二台计算机上打开该文件,并将该 URL 粘贴到新浏览器实例中。 |
5. | 只要会话超时仍然有效,就会显示同一个映射。 |
通过无 Cookie 会话,可以比以往任何时候都更加容易地窃取会话 ID。
从道德的观点来看,窃取会话是应该受到谴责的操作,我相信大家会一致认同这一点。但它是否也是有害的?这取决于会话状态中实际存储的内容。窃取会话 ID 本身并不会执行超出代码控制范围的操作。但是,它可能向未经授权的用户泄露私有数据,并且使一些坏家伙能够执行未经授权的操作。有关如何在 ASP.NET 应用程序中阻止会话劫持的提示,请阅读 Wicked Code: Foiling Session Hijacking Attempts。(而且,它并不依赖于无 Cookie 会话!)
使用无 Cookie 会话还会引起与链接有关的问题。例如,您不能在 ASP.NET 页中具有绝对的、完全限定的链接。如果您这样做,那么源自该超链接的每个请求都将被视为新会话的一部分。无 Cookie 会话要求您总是使用相对 URL,就像在 ASP.NET 回发中一样。仅当您可以将会话 ID 嵌入到 URL 中时,您才可以使用完全限定的 URL。但是,既然会话 ID 是在运行时生成的,那么您如何才能做到这一点呢?
下面的代码中断了该会话:
<a runat="server" href="/test/page.aspx">Click</a>
要使用绝对 URL,可以借助于一个小技巧,即,使用 HttpResponse 类上的 ApplyAppPathModifier 方法:
<a runat="server" href=<% =Response.ApplyAppPathModifier("/test/page.aspx")%> >Click</a>
ApplyAppPathModifier 方法采用一个表示 URL 的字符串作为参数,并且返回一个嵌入了会话信息的绝对 URL。例如,当您需要从 HTTP 页重定向到 HTTPS 页时,该技巧尤其有用。最后,请特别注意,每当您在同一个浏览器内部键入指向某个站点的路径时,您都将丢失无 Cookie 会话的状态。还要请您注意的是,对于移动应用程序,如果设备无法处理专门格式化的 URL,则无 Cookie 会话可能会出现问题。
小结
ASP.NET 中存在无 Cookie 会话的主要原因是用户(无论出于什么原因)可能在他们的浏览器中禁用了 Cookie。如果您的应用程序需要会话状态,那么无论您是否喜欢,您都必须面对这种情况。无 Cookie 会话将会话 ID 嵌入到 URL 中,并且得到了双重结果。一方面,它们为 Web 站点提供了一种正确标识发出请求的用户的方式。然而,另一方面,它们使会话 ID 清楚地显现在潜在的攻击者面前,从而使攻击者可以轻松地窃取它并以您的身份进行操作。
要实现无 Cookie 会话,您无需修改自己的编程模型 — 只需在 web.config 文件中进行简单更改,就可以完成相关工作 — 但是,还要强烈建议您重构您的应用程序,以免在会话状态中存储有价值的信息。同时,将会话的生存期缩短至默认的 20 分钟以内有助于保护您的用户和站点的安全。