Web应用程序核心防御机制

防御攻击者向应用程序发动有效攻击的重要前提是彻底了解这些机制

May 12, 2017 - 2 minute read -
web security test

注:原文来自《黑客攻防技术宝典》

Web应用程序采用的防御机制由以下几个核心因素构成。

  • 处理用户访问应用程序的数据与功能,防止用户获得未授权的访问
  • 处理用户对应用程序的输入,防止错误输入造成不良行为
  • 防范攻击者,确保应用程序在成为直接攻击目标时能够正常运转,并采取适当的防御与攻击措辞挫败攻击者
  • 管理应用程序本身,帮助管理员监控其行为,配置其功能

1. 处理用户访问

1.1 身份验证

今天,绝大多数Web应用程序都采用传统的身份验证模型,即要求用户提交用户名与密码,再由应用程序对其进行核实,确认其合法性。在安全性至关重要的应用程序(如电子银行使用的应用程序)中,通常使用其他证书与多阶段登陆过程强化这个模型。在安全要求更高的情况下,可能需要基于客户端证书、智能卡或质询-响应令牌(challenge-response token)使用其他身份验证模型。除了核心登录过程外,身份验证机制往往还要采取一系列其他支持功能,如自我注册、账户恢复和密码修改工具。

尽管表面看似简单,但无论是设计方面还是执行方面,身份验证机制都存在大量缺陷。常见的问题可能使得攻击者能够确定其他用户的用户名、推测出他们的密码,或者利用逻辑缺陷完全避开登陆功能。攻击Web应用程序时,渗透测试员应当投入大量精力,攻击应用程序采用的各种与身份验证有关的功能。出人意料的是,这种功能中存在的缺陷往往允许攻击者非法访问敏感数据与功能。

1.2 会话管理

处理用户访问的下一个逻辑任务是管理通过验证用户的会话。成功登录应用程序后,用户访问各种页面与功能,从浏览器提出一系列HTTP请求。与此同时,应用程序还会受到各类用户(包括通过验证的用户与匿名用户)发出的无数请求。为实施有效地访问控制,应用程序需要识别并处理每一名用户提交的各种请求。

为了满足以上要求,几乎所有的Web应用程序都为每一个用户建立一个会话,并向用户发布一个标识会话的令牌。会话本身是一组保存在服务器上的数据结构,用于跟踪用户与应用程序的交互状态。令牌是一个唯一的字符串,应用程序将其映射到会话中。当用户收到一个令牌时,浏览器会在随后的HTTP请求中将它返回给服务器,帮助应用程序将请求与该用户联系起来。虽然许多应用程序使用隐藏表单字段(hidden form field)或URL查询字符串(query string)传送会话令牌(session token),但HTTP cookie才是实现这一目的的常规方法。如果用户在一段时间内没有发出请求,会话将会自动终止。

就受攻击面而言,会话管理机制的有效性基本上取决于其令牌的安全性,绝大多数针对它的攻击都企图攻破其他用户的令牌。如果令牌被攻破,攻击者就可以伪装成被攻破的用户,向已经通过验证的用户一样使用应用程序。令牌生成过程中存在的缺陷是主要的漏洞来源,是攻击者能够推测出发布给其他用户的令牌;随后,攻击者再利用令牌中的缺陷截获其他用户的令牌。

少数应用程序不向用户发布会话令牌,而是通过其他方法在多个请求中重复确认用户身份。如果使用HTTP的内置身份验证机制,那么浏览器会自动在每个请求中重复提交用户证书,帮助应用程序直接通过这些请求识别用户。在其他情况下,应用程序会将状态信息保存在客户端而非服务器上,通常还需要对这些信息进行加密,以防止遭到破坏。

1.3 访问控制

处理用户访问的最后一个逻辑步骤是做出并实施正确的决策,决定允许或拒绝每一个请求。如果前面的机制运作正常,应用程序即可从收到的每一个请求确认用户的身份。在此基础上,应用程序需要决定是否授权用户执行其所请求的操作或访问相关数据。

访问控制机制一般需要实现某种精心设计的逻辑,并分别考虑各种相关应用程序领域与不同类型的功能。 应用程序可支持无数不同的用户角色,每种角色都拥有特定的权限,每名用户只允许访问应用程序中的部分数据。应用程序可能需要根据用户的身份,通过特殊功能实现交易限制与其他检查。

由于典型访问控制的要求相当复杂,因此这种机制中一般存在大量的安全漏洞,使得攻击者能够未授权访问应用程序的数据与功能。开发者经常会对用户与应用程序的交互方式做出错误假设,并常常会有所疏忽,在某些应用程序功能中省略访问控制检查。探查这些漏洞是一件费力的工作,因为需要对每一项功能重复进行相同的检查。然后,因为访问机制中存在大量漏洞,所以在测试Web应用程序时付出这样的努力总是值得的。

2. 处理用户输入

所有用户输入都不可信。大量针对Web应用程序的不同攻击都与提交错误输入有关,攻击者专门设计这类输入,以引发应用程序设计者无法预料的行为。因此,能够安全处理用户输入是对应用程序安全防御的一个关键要求。

应用程序的每一项功能以及几乎每一种常用的技术都可能出现输入方面的漏洞。通常来说,输入确认(input validation)是防御这些攻击的必要手段。然而,任何一种保护机制都不是万能的,防止恶意输入也并非听起来那样简单。

2.1 输入的多样性

典型的Web应用程序以各种不同形式处理用户提交的数据。一些类型的输入确认可能并不能使用或能够确认所有这些形式的输入。在许多情况下,应用程序可能会对一些特殊的输入实行非常严格的确认检查。例如,提交给登录功能的用户名的最大长度为8个字符,且只能包含字母。在其他情况下,应用程序必须接受更广泛的输入。有些时候,应用程序可能需要接受用户提交的任意输入。

除了用户通过浏览器界面提交的各种输入外,一个典型的应用程序还会收到大量数据,它们在服务器上生成,并被传送给客户端,以便客户端能够在随后的请求中将其返回给服务器。这些数据包括cookie和隐藏表单字段,普通应用程序用户虽然无法浏览这些数据项,但攻击者能够查看并修改它们。

2.2 输入处理的方法

1)拒绝已知的不良输入

这种方法一般使用一个黑名单,其中包含一组在攻击者使用的已知的字面量字符串或模式。确认机制阻止任何与黑名单匹配的数据,并接受其他数据。

一般来说,因为两方面的主要原因,这种方法是确认用户输入效率最低的方法。首先,攻击者可通过一系列输入对典型Web应用程序中存在的输入漏洞加以利用,这些输入可通过各种方式进行编码,或者表现为不同的形式。除非在最简单的情况下,否则,黑名单可能会忽略某些可用于攻击应用程序的输入模式。其次,攻击技术处在不断发展的过程之中。当前的黑名单无法防止利用现有漏洞的新型方法。最后,各种基于黑名单的过滤,特别是那些由Web应用程序防火墙执行的过滤,都易受空字节攻击。由于在托管和非托管情况下处理字符串的方式各不相同,在被阻止的表达式之前的任何位置插入空字节可能导致某些过滤器停止处理输入。

2)接受已知的正常输入

这种方法使用一个白名单,其中包含仅与良性输入匹配的一组字面量字符串、模式或一组标准。确认机制接受任何与白名单匹配的数据,并阻止其他数据。

在切实可行的情况下,这种方法是处理潜在恶意输入的最有效方法。然而,在许多情况下,应用程序必须接受不满足任何已知“正常”标准的数据,并对其进行处理。例如,在一些人的姓名中包含撇号和连字符的情况。因此,虽然这种方法极其有效,但基于白名单的方法并非解决处理用户输入问题的万能办法。

3)净化

这种方法认可有时需要接受无法保证其安全的数据。应用程序并不拒绝这种输入,相反,它以各种方式对其进行净化,防止它造成任何不利的影响。数据中可能存在的恶意字符被彻底删除掉,只留下已知安全的字符,或者进一步处理前对它们进行适当编码或“转义”。

基于数据净化的方法一般非常有效。在许多情况下,可将其作为处理恶意输入问题的通用解决办法。例如,在将危险字符植入应用程序页面前对其进行HTML编码,是防御跨站点脚本攻击的常用方法。然而,如果需要在一个输入项中容纳几种可能的恶意数据,可能就很难对其进行有效地净化。这时,最好曹勇边界区人方法处理用户输入。

4)安全数据处理

以不安全的方式处理用户提交的数据,是许多Web应用程序漏洞形成的根本原因。通常,不需要确认输入本身,只需确保处理过程绝对安全,即可避免这些漏洞。有些时候,可使用安全的编程方法避免常见问题。例如,在数据库访问过程中正确使用参数化查询,就可以避免SQL注入攻击。在其他情况下,完全可以避免应用程序功能设计不安全的做法,如向操作系统命令解释程序提交用户用户输入。

这种方法并不适用于Web应用程序需要执行的每项任务,但如果适用,它是一种有效处理潜在恶意输入的通用方法。

5)语法检查

目前为止,描述的防御措施全都用于防止应用程序接受各种错误的输入,攻击者专门设计这些输入的内容以干扰应用程序的处理过程。然而,在一些漏洞中,攻击者提交的输入与普通的非恶意用户提交的输入完全相同。之所以称其为恶意输入,是因为攻击者提交的动机不同。例如,攻击者可能会修改通过隐藏表单字段提交的账号,企图访问其他用户的银行账户。这时,再多的语法确认也无法区别用户与攻击者的数据。为防止未授权访问,应用程序必须确认所提交的账号属于之前提交该账号的用户。

2.3 边界确认

在信任边界确认数据的做法并不少见。用户提交的数据不可行是造成Web应用程序核心安全问题的主要原因。虽然在客户端执行的输入确认检查可以提高性能,改善用户体验,但他们并不能为实际到达服务器的数据提供任何保证。服务器端应用程序第一次收到用户数据的地方是一个重要的信任边界,应用程序需要在此采取措施防御恶意输入。

鉴于核心问题的本质,可以基于因特网(“不良”且不可信)与服务器端应用程序(“正常”且可信)之间的边界来考虑输入问题。从这个角度看,输入确认的任务就是净化到达的恶意数据,然后将“洁净的”数据提交给可信的应用程序。此后,数据即属于可信数据,不需要任何进一步的检查或担心可能的攻击,即可进行处理。

很明显,当我们开始分析一些实际的漏洞时,执行这种简单地输入确认是不够的,原因如下:

  • 基于应用程序所执行功能的广泛性已经其所采用技术的多样性,一个典型的应用程序需要防御大量各种各样的基于输入的攻击,且每种攻击可能采用一种截然不同的专门设计的数据。因此,很难再外部边界建立一个单独的机制,防御所有这些攻击。
  • 许多应用程序功能都涉及组合一系列不同类型的处理过程。用户提交的一项输入可能会在不同的组件中引发很多操作,其中前一个操作的输入结果被用于后一个操作的输入。数据发生转换后,可能会变得与原始的输入完全不同。而经验丰富的攻击者能够操纵应用程序,在关键处理阶段生成恶意输入,攻击接受这些数据的组件。为此,很难再外部边界执行确认机制,预测每一个用户输入的全部可能处理结果。
  • 防御不同类型的基于输入的攻击可能需要对相互矛盾的用户输入执行各种确认检查。例如,防止跨站点脚本攻击可能需要将>字符进行HTML编码,而防止命令输入攻击则需要组织包含&与;字符的输入。有时候,想要在应用程序的外部边界同时组织所有类型的攻击几乎是不可能的事情。

边界确认(boundary validation) 是一种更加有效地模型。此时,服务器端应用程序的每一个单独的组件或功能单元将其输入当做来自潜在恶意来源的输入对待。除客户端与服务器之间的外部边界外,应用程序在上述每一个信任边界上执行数据确认。这种模型为前面提出的问题提供了一个解决方案。每个组件都可以防御它受到的特殊类型的专门设计的输入。当数据通过不同的组件时,即可对前面转换过程中生成的任意数据值执行检查确认。而且,由于在不同的处理阶段执行不同的确认检查,它们之间不可能发生冲突。

下图是一种在多阶段处理步骤中使用边界确认的应用程序功能实例,此时边界确认是防御恶意输入的最有效方法。在用户登录过程中,需要对用户提交的输入进行几个步骤的处理,并在每个步骤执行适当的确认检查。 boundary-validation-demo

(1) 应用程序收到用户登录信息。表单处理程序确认每个输入仅包含合法字符,符合特殊的长度限制,并且不包含任何已知的攻击签名。

(2) 应用程序执行一个SQL查询检验用户证书。为防止SQL注入攻击,在执行查询前,应用程序应对用户输入中包含的可用于攻击数据库的所有字符进行转义。

(3) 如果用户成功登录,应用程序再将用户资料中的某些数据传送给SOAP服务器,进一步获得用户账户的有关信息。为防止SOAP注入攻击,需要对用户资料中的任何XML元字符进行适当编码。

(4) 应用程序在用户的浏览器显示用户的账户信息。为防止跨站点脚本攻击,应用程序对植入返回页面的任何用户提交的数据执行HTML编码。

如果这一功能发生变化,需要向其他应用程序组件提交数据,那么可能需要在相关信任边界执行类似防御。

2.4 多步确认与规范化

在确认检查过程中,当需要在几个步骤中处理用户提交的输入时,就会出现一个输入处理机制经常遇到的问题。如果不谨慎处理这个过程,那么攻击者就能够建立专门设计的输入,使恶意数据成功避开确认机制。但应用程序试图通过删除或编码某些字符或表达式净化用户输入时,就会出现这种问题。例如,为防御某些XSS攻击,应用程序可能会从任何用户提交的数据中删除表达式:

<script></script>

但攻击者可通过应用以下输入避开过滤器:

<src<script>ipt>

由于过滤无法递归运行,删除被阻止的表达式后,表达式周围的数据又合并在一起,重新建立恶意表达式。

同样,如果对用户输入执行几个确认步骤,攻击者就可以利用这些步骤的顺序来避开过滤。例如,如果应用程序首先递归删除..\,然后递归删除../,就可以使用下面输入避开确认检查:

....\/

数据规范化(data canonicalization)会造成另一个问题。当用户浏览器送出输入时,它可对这些输入进行各种形式的编码。之所以使用这些编码方案,是为了能够通过HTTP安全传输不常见的字符与二进制数据。规范化是指将数据转换或解码成一个常见字符集的过程。如果在实施输入过滤之后才执行规范化,那么攻击者就可以使用URL编码的输入避开确认机制。

例如,应用程序可能会从用户输入中删除省略号,以防止某些SQL注入攻击。但是,如果应用程序随后对净化后的数据进行规范化,那么攻击者就可以使用URL编码的输入避开确认:

    %2527

收到该输入后,应用程序服务器会执行正常的URL解码,因此该输入变成:

    %27

其中并不包含省略号,因此,应用程序的过滤器允许该输入。但是,如果应用程序执行进一步的URL编码,该输入将变成省略号,从而避开过滤。

如果应用程序删除而不是阻止省略号,然后执行进一步的规范化,则可以使用以下输入避开过滤:

    %%2727

值得注意的是,在这些情况下,应用程序服务器端不一定会执行多步确认和规范化。例如,在下面的输入中,几个字符已经被HTML编码:

    <iframe src=j&#x61;vasc&#x72ipt&#x3a;alert&#x28;1&#x29; >

如果服务器端应用程序使用输入过滤来阻止某些JavaScript表达式和字符,该已编码的输入就可以成功避开过滤。但是,如果该输入随后被复制到应用程序的响应中,某些浏览器将对src参数值执行HTML解码,嵌入的JavaScript将得以执行。

除了供Web应用程序使用的标准编码方案外,其他情况下,如果应用程序采用的组件将数据从一个字符集转换为另一个字符集,这也会导致规范化的问题。例如,某些技术会基于印刷字形的相似性,对字符执行“最佳”映射。这时,字符《和》分别被转换为<和>。攻击者经常利用这种方法传送受阻止的字符或关键字,从而避开应用程序的输入过滤。

类似这种攻击可有效挫败应用程序针对常见的基于输入的漏洞而采取的许多防御机制。

有时候,可能很难避免多不确认与规范化造成的问题,也不存在解决这类问题的唯一方案。一种解决办法是递归执行净化操作,直到无法进一步修改输入。然而,如果需要在净化过程中对一个存在疑问的字符进行转义,那么这种情况可能会造成无限循环。通常,这个问题只有根据具体情况、基于所执行的确认类型加以解决。如果可能,最好避免净化某些不良输入的做法,完全拒绝这种类型的输入。

3. 处理攻击者

任何设计安全应用程序的开发人员必须基于这样一个假设:应用程序将成为蓄意破坏且经验丰富的攻击者的直接目标。能够以受控的方式处理并应对这些攻击,是应用程序安全机制的一项主要功能。这些机制通常结合使用一系列防御与攻击措施,以尽可能地阻止攻击者,并就所发生的事件,通知应用程序所有者以及提供相应的证据。

3.1 处理错误

应用程序的一个关键防御机制是合理地处理无法预料的错误,要么纠正这些错误,要么向用户发送适当的错误消息。在生产环境下,应用程序不应再其响应中返回任何系统生成的消息或其他调试信息。过于详细的错误消息非常有利于恶意用户向应用程序发动进一步攻击。

有效地错误处理措施通常与应用程序的日志机制整合在一起,后者尽可能地记录与无法预测的错误有关的调试信息。通常,无法预料的错误往往能够指明应用程序的防御机制中存在的缺陷。如果应用程序的所有者获得必要的信息,就能从源头解决这些问题。

3.2 维护审计日志

审计日志(audit log)在调查针对应用程序的入侵尝试时会发挥很大作用。发生入侵时,有效地审计日志功能应能够帮助应用程序所有者了解实际发生的情况,如哪些漏洞(如果有)被加以利用,攻击者是否可以对数据进行非法访问或执行任何未授权的操作,并尽可能地提供侵入者的身份信息。

在任何注重安全的应用程序中,日志应记录所有重要事件。一般这些事件应至少包括以下几项:

  • 所有与身份验证功能有关的事件,如成功或失败的登录、密码修改。
  • 关键交易,如信用卡支付与转账。
  • 被访问控制机制阻止的企图访问。
  • 任何包含已经攻击字符串,公然表明恶意意图的请求。

3.3 向管理员发出警告

许多时候,警报机制必须在两个相互矛盾的目标之间取得平衡,既准确报告每次的真实攻击又不会生成过多警报,造成它们被管理员忽略。精心设计的报警机制能够组合各种因素,确定应用程序正在遭受的某种攻击;并在可能的情况下将所有相关事件集中到一个警报中。警报监控的反常事件一般包括以下几类:

  • 应用反常,如收到由单独一个IP地址或用户发出的大量请求,表明应用程序正收到自定义的攻击。
  • 交易反常,如单独一个银行账户所转入或转出的资金数量出现异常。
  • 包含已知攻击字符串的请求。
  • 请求中普通用户无法查看的数据被修改。

现有的应用程序防火墙和入侵检测产品能够相当完善地提供其中一些功能。这些产品一般组合应用一组基于签名与异常规则来确定对应用程序的恶意利用,并能够主动阻止恶意请求,向管理员发出报警。

在任何安全性至关重要的应用程序中,进行实时报警的最有效方法是将其与应用程序的输入确认机制和其他控制方法紧密结合起来。例如,如果认为cookie中包含一组特殊值中的某个值,那么任何违反这种情况的现象即表明该值已被修改,而且应用程序的普通用户无法执行此类修改。同样,如果一名用户修改隐藏表单字段中的账号,以确定另一名用户的账号,这种做法也明确表现出恶意意图。应用程序的主要防御机制应阻止这些攻击,而且,这些保护机制可轻易与应用程序的警报机制进行整合,提供完全自定义的恶意行为警示。

3.4 应对攻击

除向管理员发出警报外,许多安全性至关重要的应用程序还含有内置机制,以防御潜在恶意用户。

由于应用程序各不相同,现实世界中的许多攻击要求攻击者系统地探查应用程序中存在的漏洞,提交无数包含专门设计的输入请求,以确定其中是否存在各种常见的漏洞。高效的输入确认机制能够把许多这种类型的请求确定为潜在的恶意请求,并阻止这些输入,防止它们给应用程序造成任何不利影响。然而,我们还应意识到,攻击者仍然能够以某种方式避开这些过滤;而且,应用程序确实包含某些实际的漏洞,等待攻击者去发现和利用。从某种意义上说,进行系统性探查的攻击者可能会发现这些缺陷。

有鉴于此,一些应用程序采取自动反应措施阻止攻击者进行这种形式的探查,例如对攻击者提交的请求的响应变得越来越慢,或者终止攻击者的会话,要求其重新登录或在继续攻击前执行其他步骤。

4. 管理应用程序

任何有用的应用程序都需要进行管理与维护,这种功能通常是应用程序安全机制的一个重要组成部分,可以帮助管理员管理用户账户与角色、应用监控与审计功能、执行诊断任务并配置应用程序各种功能。在这种情况下,管理机制就成为应用程序的主要受攻击面。它吸引攻击者的地方主要在于它能够提升权限。

  • 身份验证机制中存在的薄弱环节使攻击者能够获得管理员权限,迅速攻破整个应用程序。
  • 许多应用程序并不对它的一些管理功能执行有效地访问控制。利用这个漏洞,攻击者可以建立一个拥有强大特权的新用户账户。
  • 管理功能通常能够显示普通用户提交的数据。管理界面中存在的任何跨站点脚本缺陷可能危及用户会话的安全。
  • 因为管理用户被视为可信用户,或者由于渗透测试员只能访问低权限的账户,所以管理功能往往没有经过严格的安全测试。而且,它通常需要执行相当危险的操作,包含访问磁盘上的文件或操作系统命令。如果一名攻击者能够攻破管理功能,就能利用它控制整个服务器。