本模块内容
Web 应用程序为结构设计人员、设计人员和开发人员提出了一系列复杂的安全问题。最安全、最有能力抵御攻击的 Web 应用程序是那些应用安全思想构建的应用程序。
在设计初始阶段,应该使用可靠的体系结构和设计方法,同时要结合考虑程序部署以及企业的安全策略。如果不能做到这一点,将导致在现有基础结构上部署应用程序时,要不可避免地危及安全性。
本模块提供了一系列安全的体系结构和设计指南,并按照常见的应用程序漏洞类别进行了组织。这些指南是 Web 应用程序安全的重要方面,并且是经常发生错误的领域。
目标
使用本模块可以实现:
• | 确定安全 Web 应用程序的重要体系结构和设计问题。 |
• | 设计时考虑重要部署问题。 |
• | 制定能增强 Web 应用程序输入验证的策略。 |
• | 设计安全的身份验证和会话管理机制。 |
• | 选择适当的授权模型。 |
• | 实现有效的帐户管理方法,并保护用户会话。 |
• | 对隐私、认可、防止篡改和身份验证信息进行加密。 |
• | 防止参数操作。 |
• | 设计审核和记录策略。 |
适用范围
虽然本模块内容包含在 ASP.NET 安全手册中,但其适用于对开发安全 Web 应用程序感兴趣的所有人员。
如何使用本模块
本模块主要提供了设计应用程序时应该遵循的一些指南和原则。
为了充分理解本模块内容,请:
• | 了解应用程序将会受到的威胁,以确保通过程序设计解决这些问题。阅读模块 2 威胁和对策,以了解需要考虑的威胁。模块 2 列出了可能会危害应用程序的因素。在程序设计阶段应该考虑到这些威胁。 |
• | 在应用程序易受攻击的重要环节应用系统的方法。将重点放在程序部署、输入验证、身份验证和授权、加密及数据敏感度、配置、会话、异常管理以及适当的审核和记录策略上,以确保应用程序具有责任性。 |
Web 应用程序的体系结构和设计问题
Web 应用程序向设计人员和开发人员提出了许多挑战。HTTP 是无国界的,这意味着跟踪每位用户的会话状态将成为应用程序的责任。作为先导者,应用程序必须能够通过某种形式的身份验证来识别用户。由于所有后续授权决策都要基于用户的标识,因此,身份验证过程必须是安全的,同样必须很好地保护用于跟踪已验证用户的会话处理机制。设计安全的身份验证和会话管理机制仅仅是 Web 应用程序设计人员和开发人员所面临的众多问题中的两个方面。由于输入和输出数据要在公共网络上进行传输,因此还会存在其他挑战。防止参数操作和敏感数据泄漏是另外一些重要问题。
图 4.1 列出了安全设计方法中必须解决的其他重要问题。
图 4.1
Web 应用程序设计问题
本模块中的设计指南是根据应用程序漏洞类别进行组织的。实际经验表明,如果这些领域的设计存在薄弱环节,将会导致安全漏洞。表 4.1 列出了漏洞的类别,每个类别都突出显示了由于设计不当可能会导致的潜在问题。
表 4.1:由于设计不当导致的 Web 应用程序漏洞及潜在问题
漏洞类别 | 由于设计不当而引起的潜在问题 |
输入验证 |
嵌入到查询字符串、表单字段、cookie 和 HTTP 头中的恶意字符串的攻击。这些攻击包括命令执行、跨站点脚本(XSS)、SQL 注入和缓冲区溢出攻击。 |
身份验证 |
标识欺骗、密码破解、特权提升和未经授权的访问。 |
授权 |
访问保密数据或受限数据、篡改数据以及执行未经授权的操作。 |
配置管理 |
对管理界面进行未经授权的访问、具有更新配置数据的能力以及对用户帐户和帐户配置文件进行未经授权的访问。 |
敏感数据 |
泄露保密信息以及篡改数据。 |
会话管理 |
捕捉会话标识符,从而导致会话劫持及标识欺骗。 |
加密 |
访问保密数据或帐户凭据,或二者均能访问。 |
参数操作 |
路径遍历攻击、命令执行以及绕过访问控制机制,从而导致信息泄漏、特权提升和拒绝服务。 |
异常管理 |
拒绝服务和敏感的系统级详细信息的泄漏。 |
审核和记录 |
不能发现入侵迹象、不能验证用户操作,以及在诊断问题时出现困难。 |
部署考虑
在应用程序设计阶段,应考虑公司安全策略和程序,以及部署应用程序的基础结构。通常,目标环境是固定不变的,应用程序的设计必须要反映这些限制条件。有时需要折衷考虑设计方案,例如,由于存在协议和端口限制,或是特定部署拓扑结构的要求。要在设计初期确定存在哪些限制条件,以避免日后在开发过程中出现意外;另外,应邀请网络和基础结构工作组的成员参与此过程。
图 4.2 显示了需在程序设计阶段考虑的几个程序部署问题。
图 4.2
部署考虑
安全策略和程序
安全策略确定允许应用程序及其用户执行哪些操作。更重要的是,安全策略定义了一些限制,以确定不允许应用程序及其用户执行哪些操作。设计应用程序时,应在公司安全策略定义的框架内进行标识和工作,以确保您没有违反防止部署应用程序的策略。
网络基础结构组件
确保您了解目标环境提供的网络结构,并了解网络的基本安全要求,如筛选规则、端口限制、支持的协议等等。
确定防火墙和防火墙策略可能会如何影响应用程序的设计和部署。在面向 Internet 的应用程序和内部网络之间可能存在防火墙将其隔开。也许还存在用于保护数据库的其他防火墙。这些防火墙影响了可用的通信端口,因此会影响 Web 服务器到远程应用程序和数据库服务器的身份验证选项。例如,Windows 身份验证需要附加端口。
在设计阶段,需要考虑允许哪些协议、端口和服务从外围网络中的 Web 服务器访问内部资源。还应确定应用程序设计所需的协议和端口,并分析打开新端口或使用新协议会带来哪些潜在威胁。
交流并记录所有有关网络和应用层安全的设想,以及哪些组件将处理哪些问题。这样,当开发人员和网络管理人员都认为对方会解决安全问题时,可以防止安全控制失败。注意网络为应用程序提供的安全防范措施。设想如果更改网络设置,可能会带来哪些安全隐患。如果实现特定的网络结构更改,将会出现多少安全漏洞?
部署拓扑结构
应用程序的部署拓扑结构和是否具有远程应用层是设计阶段必须考虑的关键问题。如果具有远程应用层,需要考虑怎样保护服务器之间的网络以减少网络窃听威胁,以及怎样保护敏感数据的保密性和完整性。
此外,还要考虑标识符流,并确定在应用程序连接到远程服务器时将用于网络身份验证的帐户。一种常见方法是使用最小特权进程帐户,并在远程服务器上创建一个具有相同密码的帐户副本(镜像)。另一种方法是使用域进程帐户,此类帐户管理方便,但会带来更大的安全问题,因为很难限制该帐户在网络上的使用。未建立信任关系的介入防火墙和单独域使应用本地帐户成为唯一的选择。
Intranet、Extranet 和 Internet
Intranet、Extranet 和 Internet 应用程序方案在设计上都具有挑战性。应该考虑的问题包括:怎样将调用方标识通过多重应用层传送到后端资源?在哪里进行身份验证?能否信任前端的身份验证,然后使用可信连接访问后端资源?在 Extranet 方案中,还必须考虑是否信任合作伙伴帐户?
有关这些问题以及其他方案特定问题的详细信息,请参阅“Building Secure ASP.NET Web Applications:Authentication, Authorization, and Secure Communication”中的“Intranet Security”、“Extranet Security”和“Internet Security”部分,Authentication, Authorization, and Secure Communication”,其网址为:http://msdn.microsoft.com/library/en-us/dnnetsec/html/secnetlpMSDN.asp(英文)。
输入验证
输入验证是一个很复杂的问题,并且是应用程序开发人员需要解决的首要问题。然而,正确的输入验证是防御目前应用程序攻击的最有效方法之一。正确的输入验证是防止 XSS、SQL 注入、缓冲区溢出和其他输入攻击的有效对策。
输入验证非常复杂,因为对于应用程序之间甚至应用程序内部的输入,其有效构成没有一个统一的答案。同样,对于恶意的输入也没有一个统一的定义。更困难的是,应用程序对如何处理此输入将会影响应用的风险。例如,您是否存储用于其他应用程序的数据,或者应用程序是否接受来自其他应用程序所创建的数据源的输入?
以下做法可以增强 Web 应用程序的输入验证:
• | 假定所有输入都是恶意的。 |
• | 集中方法。 |
• | 不要依赖客户端验证。 |
• | 注意标准化问题。 |
• | 限制、拒绝和净化输入。 |
假定所有输入都是恶意的
开始输入验证时,首先假定所有输入都是恶意的,除非有证据表明它们并无恶意。无论输入是来自服务、共享文件、用户还是数据库,只要其来源不在可信任的范围之内,就应对输入进行验证。例如,如果调用返回字符串的外部 Web 服务,如何知道该服务不会执行恶意命令呢?另外,如果几个应用程序写入同一个共享数据库,您在读取数据时,如何知道该数据是否安全呢?
集中方法
将输入验证策略作为应用程序设计的核心元素。考虑集中式验证方法,例如,通过使用共享库中的公共验证和筛选代码。这可确保验证规则应用的一致性。此外,还能减少开发的工作量,且有助于以后的维护工作。
许多情况下,不同的字段要求不同的验证方法,例如,要求使用专门开发的常规表达式。但是,通常可以得到验证常用字段(如电子邮件地址、标题、名称、包括邮政编码在内的通讯地址,等等)的常规方法。图 4.3 显示了此验证方法。
图 4.3
输入验证的集中式方法
不要依赖于客户端验证
应使用服务器端代码执行其自身的验证。如果攻击者绕过客户端或关闭客户端脚本例程(例如,通过禁用 JavaScript 脚本),后果如何?使用客户端验证可以减少客户端到服务器端的往返次数,但不要依赖这种方法进行安全验证。这是一个深入彻底的防御示例。
注意标准化问题
数据的标准形式是最标准、最简单的形式。标准化是指将数据转化为标准形式的过程。文件路径和 URL 尤其倾向于标准化问题,许多广为人知的漏洞利用都直接源自标准化缺陷。例如,请考虑以下字符串,它以标准形式表示了文件及其路径。
c:\temp\somefile.dat
以下字符串也可以代表同一个文件。
somefile.dat c:\temp\subdir\..\somefile.dat c:\ temp\ somefile.dat ..\somefile.dat c%3A%5Ctemp%5Csubdir%5C%2E%2E%5Csomefile.dat
最后一个示例使用十六进制指定字符:
• | %3A 代表冒号。 |
• | %5C 代表反斜杠符号。 |
• | %2E 代表句号。 |
通常,应设法避免让应用程序接受用户输入的文件名,以防止标准化问题。可以考虑其他设计方法。例如,让应用程序为用户确定文件名。
如果确实需要用户输入文件名,在作出安全决策(如授予或拒绝对特定文件的访问权限)之前,应确保这些文件名具有严格定义的形式。
有关如何处理文件名以及如何以安全方式执行文件 I/O 的详细信息,请参阅模块 7 中的“文件 I/O”部分:构建安全的程序集,以及模块 8:代码访问安全的实践。
限制、拒绝和净化输入
输入验证的首选方法是从开始就限制允许输入的内容。按照已知的有效类型、模式和范围验证数据要比通过查找已知有害字符的数据验证方法容易。设计应用程序时,应了解应用程序需要输入什么内容。与潜在的恶意输入相比,有效数据的范围通常是更为有限的集合。然而,为了使防御更为彻底,您可能还希望拒绝已知的有害输入,然后净化输入。图 4.4 显示了我们推荐的策略。
图 4.4
输入验证策略:限制、拒绝和净化输入
要创建有效的输入验证策略,需了解以下方法及其折衷方案:
• | 限制输入。 |
• | 按照类型、长度、格式和范围验证数据。 |
• | 拒绝已知的有害输入。 |
• | 净化输入。 |
限制输入
限制输入与允许输入有益数据类似。这是首选的输入验证方法。其思想是定义一个筛选器,根据类型、长度、格式和范围来筛选输入的数据。定义应用程序字段可以接受的数据输入,并强制应用该定义。拒绝一切有害数据。
限制输入可能包括在服务器上设置字符集,以便在本地建立输入的规范格式。
验证数据的类型、长度、格式和范围
在适当的地方对输入数据使用强类型检查,例如,在用于操作和处理输入数据的类中,以及在数据访问例程中。例如,可以使用参数化的存储过程来访问数据,以便利用输入字段的强类型检查所带来的好处。
应该检查字符串字段的长度,在许多情况下,还应检查字符串的格式是否正确。例如,邮政编码和身份证号码等都具有明确定义的格式,可以使用常规表达式进行验证。严格的检查不仅是很好的编程习惯,还能让攻击者更难利用您的代码。攻击者可能会通过类型检查,但长度检查会加大攻击者实施其所喜欢的攻击方式的难度。
拒绝已知的有害输入
虽然不能完全依赖于这种方法,但还是应该拒绝“有害”数据。此方法通常不会像使用上述的“允许”方法那样有效,但二者结合使用可以收到最佳效果。要拒绝有害数据,需假定应用程序知道恶意输入的所有变体。请记住,字符有多种表达方式。这是“允许”方法成为首选方法的另一个原因。
虽然在应用程序已经部署、不能再做重大更改时,“拒绝”方法非常有用,但它不如“允许”方法那样可靠,因为有害数据(如可用于识别常见攻击的样式)不是保持不变的。有效数据的形式是保持不变的,但有害数据的范围却是时常变化的。
净化输入
净化是为了使有潜在危害的数据变得安全。如果所允许的输入范围不能保证输入数据的安全性,那么净化输入是非常有用的。净化包括从删除用户输入字符串后面的空格到去除值(以便按照文字格式处理该数据)等一切行为。
在 Web 应用程序中,另一个常见的净化输入示例是使用 URL 编码或 HTML 编码来包装数据,并将其作为文本而不是可执行脚本来处理。HtmlEncode 方法去除 HTML 字符,而 UrlEncode 方法对 URL 进行编码,使其成为有效的 URI 请求。
在实践中
以下是使用上述方法处理常见输入字段的几个示例:
• | 姓氏字段。这是一个很好的应用限制输入的示例。在这种情况下,可以接受的字符串范围为 ASCII A–Z 和 a–z,以及连字符和波浪线(在 SQL 中,波浪线没有意义),以便处理类似 O'Dell 之类的姓氏。还应限制输入内容的最大长度。 | ||||
• | 数量字段。这是应用输入限制的另一个例子。在此示例中,可以使用简单的类型和范围限制。例如,输入数据应该是介于 0 和 1000 之间的正整数。 | ||||
• |
自定义文本字段。示例包括留言版上的备注字段。在这种情况下,您可能允许输入字母和空格,以及省略符号、逗号和连字符等常用字符。允许输入的字符集只包括符号、括号和大括号。
有些应用程序可能允许用户使用一组有限的脚本字符修饰文本,如粗体“<b>”、斜体“<b>”,甚至包含指向他们所喜爱的 URL 的连接。处理 URL 时,验证时应先对所输入的值进行编码,以便将其作为 URL 处理。 有关验证自定义文本字段的详细信息,请参阅模块 10 中的“输入验证”部分:构建安全的 ASP.NET 网页和控件。 |
||||
• |
不验证用户输入的现有
Web 应用程序。在理想方案中,应用程序将检查每个字段和入口点的输入内容是否可以接受。然而,如果现有
Web
应用程序不验证用户输入,则需要一种权宜方法来降低风险,直到改进应用程序的输入验证策略。以下两种方法都不能确保输入数据的安全处理,因为这要依赖于输入的来源,以及应用程序使用输入数据的方式,目前,它们作为快速的补救措施,能在短期内提高应用程序的安全性:
|
有关输入编码、使用常规表达式以及 ASP.NET 验证控件的详细信息和示例,请参阅模块 10 中的“输入验证”:构建安全的 ASP.NET 页面和控件。
身份验证
身份验证是确定调用方身份的过程。有三方面问题需要考虑:
• | 确定应用程序中何处需要身份验证。通常在跨越信任边界时,都需要进行身份验证。信任边界通常包括程序集、过程和主机。 |
• | 确认调用方的身份。用户通常使用用户名和密码证明自己的身份。 |
• | 在后续请求中识别用户。这需要某种形式的验证令牌。 |
许多 Web 应用程序使用密码机制对用户进行身份验证,用户可以在 HTML 表单中输入用户名和密码。这里需要考虑的问题包括:
• | 用户名和密码是否通过不安全的通道以纯文本形式发送?如果答案是肯定的,攻击者可以通过网络监视软件进行窃听,以捕获凭据。对付此类攻击的对策是使用安全套接字层 (SSL) 确保通信通道的安全。 |
• | 如何存储凭据?如果在文件或数据库中以纯文本形式存储用户名和密码,则会惹来麻烦。设想如果应用程序目录配置不当,使攻击者浏览到该文件并下载了其内容,或者添加了新的特权登录帐户,后果如何?如果怀有敌意的管理员得到了您的用户名和密码数据库,后果如何? |
• | 如何验证凭据?如果唯一目的是验证用户是否知道密码值,则没有必要存储用户密码。可以使用哈希值的形式存储验证程序,当用户登录时,根据用户所输入的值重新计算哈希值。为减少对凭据存储的词典攻击威胁,应使用强密码,并将随机生成的 salt 值与密码哈希结合使用。 |
• | 初次登录后,怎样识别已通过身份验证的用户?这需要某种形式的验证票证,如身份验证 cookie。怎样保证 cookie 的安全?如果通过不安全的通道来发送 cookie,攻击者可以捕获该 cookie 值,并使用它来访问应用程序。身份验证 cookie 被窃取意味着登录被窃取。 |
以下做法可以增强 Web 应用程序的身份验证:
• | 区分公共区域和受限区域。 |
• | 对最终用户帐户使用帐户锁定策略。 |
• | 支持密码有效期。 |
• | 能够禁用帐户。 |
• | 不要在用户存储中存储密码。 |
• | 要求使用强密码。 |
• | 不要在网络上以纯文本形式发送密码。 |
• | 保护身份验证 cookie。 |
区分公共区域和受限区域
站点的公共区域允许任何用户进行匿名访问。受限区域只能接受特定用户的访问,而且用户必须通过站点的身份验证。考虑一个典型的零售网站。您可以匿名浏览产品分类。当您向购物车中添加物品时,应用程序将使用会话标识符验证您的身份。最后,当您下订单时,即可执行安全的交易。这需要您进行登录,以便通过 SSL 验证交易。
将站点分割为公共访问区域和受限访问区域,可以在该站点的不同区域使用不同的身份验证和授权规则,从而限制对 SSL 的使用。使用 SSL 会导致性能下降,为了避免不必要的系统开销,在设计站点时,应该在要求验证访问的区域限制使用 SSL。
对最终用户帐户使用帐户锁定策略
当最终用户帐户几次登录尝试失败后,可以禁用该帐户或将事件写入日志。如果使用 Windows 验证(如 NTLM 或 Kerberos 协议),操作系统可以自动配置并应用这些策略。如果使用表单验证,则这些策略是应用程序应该完成的任务,必须在设计阶段将这些策略合并到应用程序中。
请注意,帐户锁定策略不能用于抵制服务攻击。例如,应该使用自定义帐户名替代已知的默认服务帐户(如 IUSR_MACHINENAME),以防止获得 Internet 信息服务 (IIS) Web 服务器名称的攻击者锁定这一重要帐户。
支持密码有效期
密码不应固定不变,而应作为常规密码维护的一部分,通过设置密码有效期对密码进行更改。在应用程序设计阶段,应该考虑提供这种类型的功能。
能够禁用帐户
如果在系统受到威胁时使凭证失效或禁用帐户,则可以避免遭受进一步的攻击。
不要在用户存储中存储密码
如果必须验证密码,则没有必要实际存储密码。相反,可以存储一个单向哈希值,然后使用用户所提供的密码重新计算哈希值。为减少对用户存储的词典攻击威胁,可以使用强密码,并将随机 salt 值与该密码结合使用。
要求使用强密码
不要使攻击者能轻松破解密码。有很多可用的密码编制指南,但通常的做null