1.web应用程序所采用的防卫机制的几个核心构成:
1、处理用户对应用程序的数据和功能的访问,以防止用户未经授权访问。
2、处理用户的输入,以防止恶意的输入导致未预期的行为。
3、处理攻击,以确保应用程序在被直接攻击时作出恰当的行为,如采取适当的防御和进攻性措施,以挫败攻击。
4、通过使管理员能够监控应用程序的活动和配置应用程序的功能来管理应用程序本身。
1.1处理用户访问
大多数web应用程序都使用下面的三重相关的安全机制来处理访问:
1、验证
2、会话管理
3、访问控制
1.1.1验证
验证机制在一个应用程序的用户访问处理中是一个最基本的部分。验证就是确
定该用户的有效性。大多数的web应用程序都采用常规的验证模型,即用户提
交一个用户名和密码,应用程序检查它的有效性。在安全性很重要的应用程序
中,如在线银行,这个基本的验证模型常增加额外的证书和多级登录过程。在
安全性要求更高的时候,其它的一些验证模型可以被用,如客户端证书,智能
卡或挑战/应答(challenge-response) tokens。
challenge-response tokens的过程:
用户要求登录时,系统产生一个随机数字串发送给用户。用户将这个串输入到
token设备中,token设备将这个串与用户的秘密口令按特定的算法进行运算并
产生一个回应串发送给系统,系统用同样的算法做验算即可验证用户身份。
除了核心的登录过程外,验证机制也常采用其它的一些辅助功能,如自注册
(self-registration,比如注册后通过邮件中的链接激活帐户),帐户恢复以及
密码更改功能。尽管验证机制表面上简单,但是验证机制在设计和实现方面却
存在着广泛的缺陷。一般的问题可能使一个攻击者能够识别其他用户的用户名
,猜测他们的密码或者通过利用验证的逻辑缺陷绕过这个登录函数。当你攻击
一个web应用程序时,你应该花大量的注意力在该web应用程序所包含的各种
验证相关的功能方面。验证方面的缺陷将使你能够未经授权地访问敏感数据和
功能。
1.1.2会话管理
处理用户访问的下一个工作是管理授权用户的会话。在成功登录到应用程序后,用户将从他们的浏览发送一系列的HTTP请求来访问一些页面和功能。同时,该应用程序将接收无数来自不同用户的其它的请求,有授权的用户,也有匿名的用户。为了实施有效的访问控制,应用程序需要一个方法来识别和处理这一系列来自每个不同用户的请求。
实际上大多数的web应用程序都通过为每个用户创建一个会话和发送给用户一个令牌(token)来识别会话。会话本身是位于服务上的一套数据结构,它被用来跟踪与应用程序交互的用户的状态。令牌是一个具有唯一性的字符串,应用程序将它映射到会话。当一个用户已经收到一个令牌时,浏览器在随后的每次HTTP请求中会自动将这个令牌提交给服务器,以使得应用程序能够将该请求与用户相关联起来。尽管许多应用程序使用隐藏的表单域或URL查询字符串来实现这一目的,但是HTTP cookies是传送会话令牌的标准方法。如果一个用户在指定的时间内没有产生一个请求,那么会话将被认为结束。对于之后的访问,web应用程序会要求你重新登录。
就攻击而言,会话管理机制高度地依赖于它的令牌的安全性。针对会话管理机制的多数攻击都是试图损害发送给别的用户的令牌。有可能的话,一个攻击者可以伪装成受害的授权用户来使用web应用程序。这一漏洞主要来自于两方面,其一是令牌生成的方法的缺陷,使得攻击者能够猜测到发送给别的用户的令牌,其二是令牌的后续处理的方法的缺陷,使得攻击者能够捕获别的用户的令牌。
有少部分的应用程序通过另外的识别方式省掉了会话令牌的需要,比如,如果一个HTTP的内建的授权机制被使用的话,那么浏览器对于每次的请求都自动重新提交用户的证书,使得应用程序能够直接从证书识别用户。也存在其它别的方法,应用程序存储状态信息在客户端而非服务器上,这些状态信息通常采用加密的形式以防止被篡改。
1.1.3访问控制
处理用户访问的最后一步是正确决定对于每个独立的请求是允许还是拒绝。如果前面的机制都工作正常,那么应用程序就知道每个被接受到的请求所来自的用户的id。它据此决定用户对所请求要执行的动作或要访问的数据是否得到了授权。
访问控制机制通常需要根据对应用程序的不同部分或不同类型的功能的考虑,实现一些小而好的逻辑。一个应用程序可能支持许多不同的用户角色,每个都牵涉到特定权限的不同组合。个别的用户可能会被允许访问该应用程序所容纳的整个数据的一个子集。特定的函数能够实现事务限制和其它的检查,所有这些都需要基于用户的id来得到正确的实施。
由于访问控制本身的复杂性,这使得它成为使得一个攻击者能够获得未授权访问数据和功能这一安全漏洞的常见根源。开发者经常对用户会如何与应用程序交互作出有缺陷的假设,以及经常由于省略了对某些应用程序功能的访问控制检查而造成疏忽。由于对于每项功能都需要重复相同的检查,所以探查这些漏洞通常是十分费力的。然而由于访问控制缺陷的普遍性,当你攻击一个web应用程序时这种努力是值得的。
1.2 处理用户的输入
很多针对web应用程序的攻击都涉及到提交未预期的输入,它导致了该应用程序设计者没有料到的行为。因此,对于应用程序安全性防护的一个关键的要求是它必须以一个安全的方式处理用户的输入。基于输入的漏洞可能出现在一个应用程序的功能的任何地方,并与每上通常使用的技术类型相关。对于这种攻击,输入验证是常用的必要防护。不存在通用的单一的防护机制。
1.2.1各种类型的输入
一个典型的web应用程序在不同范围形式内处理用户提供的数据。某种类型的输入验证可能对于所有这些输入形式是不可行的。
在许多情况下,一个应用程序对于特定的输入项能够实施非常严格的验证检查。比如提交给登录函数的用户名可以要求最大长度和只能包含字母。
在另外一些情况下,应用程序必须容纳更大范围的可能性的输入。比如提交给一个个人信息页面的地址字段,它可以包含字母、数字、空格、连接符,撇号以及其它字符。对于这种类型,仍然需要加以可行限制,如不能超过合适的长度,以及不能包含HTML标记。
在某些情形下,一个应用程序可能需要接受来自用户的任意的输入。比如,一个blog应用程序的用户,他创建的博客的主题的web应用程序攻击,那么他提交的内容则可以包含明显的所要讨论的攻击字符串。该应用程序需要以一个安全的方法把这些输入存储在一个数据库中并写到磁盘上,以及回显给用户。所以该应用程序就不能因为输入看起来有潜在的恶意简单拒绝。
除了来自用户的浏览器的各种输入外,典型的应用程序也接受从服务器到客户端,然后回传给服务器的数据。这些项目包括诸如cookies和隐藏的表单字段这些,它们不会被这个应用程序的普通用户所看到,但是对于攻击者来说是可见和可修改的。在这些情况中,应用程序可以对所接收的数据进行特定的验证。例如要求参数必须有一个指定数值集中的值,如表明用户所偏爱的语言的cookie,或以一个指定的格式,如一个客户的ID号。进一步说,当应用程序发现返回自用户的由服务器生成的数据已经被修改了的话,这通常就表明该用户正在探测应用程序的漏洞。在此类情况下,应用程序应该拒绝请求并记录下这个探测事件。
1.2.2 处理输入的方式
处理用户的输入有很多方式.不同的方式适合不同的情形和不同的输入类型,有些时候一个组合的方式是可取的.
1.2.2.1 黑名单
这种方式通常使用一个黑名单,它包含已知的被用在攻击方面的一套字面上的字符串或模式.验证机制阻挡任何匹配黑名单的数据.
一般来说,这种方式是被认为对于检查用户的输入效果最差的一种方式.主要有两个原因,首先是,web应用程序中的一个典型的漏洞可以使用很多种不同的输入来被利用,输入可以是被加密的或以各种不同的方法表示.其二,漏洞利用的技术是在不断地改进的.有关利用已存在的漏洞类型的新的方法不可能被当前黑名单阻挡.
1.2.2.2 白名单
这种方式采用一个白名单,它包含一套字面上的字符串或模式,或一套标准,它们用来匹配符合要求的输入.这种检查机制允许匹配白名单的数据,阻止之外的任何数据.这种方式虽然最有效,但不是通用的,比如撇号和连字符可以被用于对数据库的攻击,但是有时应用程序却应该允许它的输入.
1.2.2.3 过滤
这种方式下,潜在的恶意字符被删除,留下安全的字符,或者在进一步处理被执行之前,它们被适当地加密或去掉.
基于数据过滤的方式通常是十分有效的,并且在许多情形中,可作为处理恶意输入的通用解决方案.比如,针对跨站脚本攻击的通常的防护是在字符被嵌入到应用程序的页面之前进行HTML加密.然而如果几种潜在的恶意数据在一个输入项中话,有效的过滤是困难的.在这种情况下,边界检查方法则是更适用的.
1.2.2.4 安全地处理数据
非常多的web应用程序漏洞的出现是因为用户提供的数据是以不安全的方法被处理的.在一些情况下,存在安全的编程方法能够避免通常的问题.例如,SQL注入攻击能够通过正确的参数查询被阻止.在另外的情况中,应用程序功能设计的方法存在内在的不安全性,比如把用户的输入传递给操作系统的命令解释器.
安全处理数据的方式不能适用于web应用程序需要执行的每种工作,但是它对于处理潜在的恶意输入是通常有效的方式.
1.2.2.5 语义检查
语义检查用于防止各种变形数据的输入,这些数据的内容被精心制作来干扰应用程序的处理。然而对于有一些漏洞,攻击者的输入在表面看来和普通用户的输入是一样的,说到底检查就失去了作用。例如一个攻击者可能会通过改变隐藏的表意字段中的帐号来试图获得对他人银行帐户的访问。要阻止这种未授权的访问,应用程序需要验证帐号是否属于该用户。
1.2.2.5 边界检查
对于web应用程序,核心安全问题的出现是因为接受自用户的数据是不可信任的.尽管在客户端实现的输入验证检查能够能够提高性能和用户体验,但是这对于实际到达服务器的数据却没有任何担保.用户的数据在被服务端应用程序第一时间接受到的一刻就是边界,此时应用程序需要采取步骤来防卫恶意的输入.
鉴于核心问题的本质,对于互联网边界间的输入检查这一问题的思考是很有意义的.哪些是"恶意的"或不可信任的,以及服务端应用程序哪些是"好的"或可信任的.我们给出一个简单的想法:输入验证的角色是清除到达的数据中的潜在的恶意数据,然后把干净的数据传递给可信任的应用程序.据此,这些数据可以被信任和处理而不做进一步的检查或考虑可能的攻击.
当我们开始检查一些实际的漏洞的时候,会很明显地发现上面的简单的想法是不充分的.原因如下:
(1).鉴于web应用程序实现的功能的广泛性,以及所应用的技术的不同,一个典型的web应用程序需要防卫大量的不同的基于输入的攻击.每种输入攻击都可能采用了一套不同的数据.针对外部边界仅设计单一的一个机制来防卫所有这些攻击是非常困难的.
(2).许多应用程序的函数包含相互牵连的一系列不同的处理,单个用户所提交的一块数据可能导致不同组件之间的许多操作,上一个的输出可能作为下一个的输入.当数据被传送时,它可能有所变化,与最初的输入可能有所不同,这样一个熟练的黑客可能能够操纵这个应用程序以在处理的关键阶段导致恶意输入的产生,也就是攻击接受该数据的组件.这对于在外部边界预见用户输入的每块数据的处理的所有结果来实现一个验证机制是十分困难的.
(3).防卫不同种类的输入攻击可能需要对用户的输入执行不同的验证检查,这些验证检查是不兼容的。例如阻止跨站脚本攻击可以要求HTML加密“>”为">";而阻止命令注入攻击(command inject)可能需要阻止包含&和;字符的输入。试图在应用程序的外部边界同时阻止所有种类的攻击有时是不可能的。
一个使用边界检查概念的更有效的模型是,服务器端的每个组件或功能单元把它的输入当作是来自一个潜在的恶意源。数据检查除了在客户端和服务端之间的边界外,也在这些认为可信的边界被执行。这个模型对前面列出的问题列表提供了一个解决方案。每个组件针对自身可能的漏洞的特定的输入攻击能够自我防卫。当数据在不同的组件间传递时,验证检查就可以对前面传来数据进行检查,由于不同有验证检查是在不同的处理阶段被执行的,所以他们之间不会产生冲突。下图演示了一个防卫恶意输入最有效的方式,用户登录致使对用户提交的输入进行了几步处理,并且每步上都执行了适当的检查。
(1).应用程序接受用户的登录的详细数据.表单处理检查输入的每一项,包括允许的字符、长度是否在指定的范围内、以及不能包含任何已知的攻击特征码.
(2).应用程序执行一个SQL查询来验证用户的证书.为了阻止SQL注入攻击,用户输入的任何可能攻击数据库的字符在构造查询之前都被去掉.
(3).如果登录成功,应用程序将把来自用户的数据传递给一个SOAP服务器以检索他的帐户的更多的信息.为了阻止SOAP注入攻击,用户数据中的任何XML元字符都被适当地加密处理.
(4).应用程序把用户的账户信息回传给用户的浏览器以显示.为了防止跨站脚本攻击,应用程序HTML加密嵌在返回的页面中的用户提供的任何数据.
总之,所有牵涉的组件间都应作边界检查.情况的变化会导致所涉及的组件发生变化.例如如果登录失败后,应用程序会发送一个警告邮件给该用户的话,那么任何合并到该邮件中的用户数据可能需要针对SMTP注入攻击作检查.
1.2.2.6 多步检查和恢复
如果对用户的输入没有进行仔细的多级检查的话,攻击者构建的输入就可能会得逞。当一个应用程序试图通过删除或加密特定的字符或表达式来过滤用户的输入的话,这种情况就可能会出现。例如,一个应用程序可能试图从用户提交的数据中通过去掉表达式<script>来防止某些跨站脚本攻击,那么攻击者可能通过输入<scr<script>ipt>来绕过这个过滤。这是因为这个过滤没有被递归地使用,当<scr<script>ipt>中间的<script>被去掉后,剩下的还是<script>。
类似的情况就是,攻击者可以利用多步检查的顺序来绕过这个过滤。例如如果一个应用程序第一是递归地去掉<script>表达式,然后是去掉"符号的话,那么<scr"ipt>就可以成功绕过这个过滤。
另一个问题是在用户输入的数据被解密时会发生的。从用户浏览器过来的数据会是以不同方法加密了的数据,那么就需要对这些数据进行恢复,也就是转换或解密为通常的字符。如果解密是在输入过滤之后,那么攻击者就可以通过加密来绕过这个检查机制。例如,如果一个应用程序通过删除用户输入数据中"号来防止某些SQL注入攻击的话,由于过滤先于恢复,那么攻击者可以使用"号的URL加密形式%27来绕过这个检查机制。同理,如果检查机制也会去掉%27话,只要没有递归处理,那么%%2727就能够得逞。
有时候避免多步检查和恢复中存在的问题是比较困难的。不存在单一的方案能够解决这些问题。有些情况下,递归地处理一个有问题的字符可能会
导致死循环,通常,这只能在所执行的检查类型上来根据情况处理,情况允许的话,更好的方法就是简单地拒绝某些类型的恶意输入。