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注入攻击作检查.