我建议以一种与您此处使用的方式正交的方式来看待问题(因此不是不兼容的,您可以用两种方式检查它,以防您用一种方法捕获另一种方法错过的情况)。
任何验证中重要的两个方面是:
- 您关注的内容。
- 传递给另一层的未更改的内容。
现在,到目前为止,您提到的大多数事情都符合第一类。忽略的Cookie属于第二类,如果您使用Server.Execute或类似的方法将查询和提交信息传递给另一个处理程序,则也属于第二类。
第二类别最具争议性。
一方面,如果给定的处理程序(.aspx页面,IHttpHandler等)忽略了可能在将来某个时候由另一个处理程序使用的Cookie,则主要由该其他处理程序来验证它。
另一方面,总是有一个假设其他层有安全漏洞且不应相信它们是正确的方法很好,即使您自己编写了它们(尤其是您自己编写的!)
一个中间地带的立场是,如果有可能存在5种不同的状态,一些持久数据可以合法地存在于其中3种状态,即使对于特定的代码而言,这并不会对其造成风险,它也可能验证它是否处于这三种状态之一。
完成这一步后,我们将集中精力处理第一类。
查询字符串、表单数据、回发、标头和 cookie 都属于来自用户的相同类别的东西(无论他们是否知道)。实际上,它们有时是从不同角度看待同一件事情的不同方式。
其中有一个子集,我们将以任何方式实际处理它。
对于每个这样的项目,都有一系列合法值。
在此基础上,这些项目作为整体还有一系列合法的值组合。
因此,验证变成了以下几点:
- 确定我们要处理哪些输入。
- 确保该输入的每个组件本身都是有效的。
- 确保组合是有效的(例如,不发送信用卡号可能是有效的,但不发送信用卡号但将付款类型设置为“信用卡”可能是无效的)。
现在,当我们谈到这个问题时,通常最好不要尝试捕捉特定的攻击。例如,在传递给 SQL 的值中避免使用
'
并不是一个好办法。相反,我们有三种可能性:
在值中使用 '
是无效的,因为它不属于那里(例如,一个只能是 "true" 或 "false" 的值,或者来自一个值列表的集合,其中没有一个包含 '
)。在这里,我们捕获了它不在合法值集合中的事实,并忽略攻击的具体性质(从而也保护我们免受其他我们甚至不知道的攻击!)。
作为人类输入是有效的,但不是我们将要使用的。一个例子是一个大数字(在某些文化中,'
用于分隔千位数)。在这里,我们将 "123,456,789" 和 "123'456'789" 都规范化为 123456789,并且不关心之前的样子,只要我们可以有意义地这样做(输入不是 "fish" 或超出手头情况的合法值范围的数字)。
这是有效的输入。如果您的应用程序在名称字段中阻止撇号以尝试阻止 SQL 注入,则存在真实姓名带有撇号的情况,因此它是有缺陷的。在这种情况下,我们认为 "d'Eath" 和 "O'Grady" 是有效的输入,并通过适当转义(最好使用数据访问 API 来完成此操作)来处理 '
在 SQL 中的重要性。
ASP.NET中第三点的一个经典例子是使用<
和>
阻止“可疑”输入的代码,这会导致许多ASP.NET页面出现错误。当然,与其不适当地接受它而出现错误,阻止它而出现错误要好得多,但默认设置适用于没有考虑验证并试图防止他们自己受到严重伤害的人。由于您正在考虑验证,因此应考虑是否适当关闭自动验证,然后根据您的特定用途以适当的方式处理<
和>
。
还要注意,我没有提到javascript。我不验证javascript(除非我实际上收到了它),我忽略它。我假装它不存在,然后我就不会错过可能被篡改的验证案例。在这个层面上也假装你的不存在。最终,客户端验证是为了节省善良的人犯错误的时间,而不是为了阻挠坏人。
出于类似的原因,最好不要通过浏览器进行测试。使用 Fiddler 构建请求以命中您想要检查的验证点。这样所有客户端验证都将被绕过,您将像攻击者一样查看服务器。
最后,请记住,具有100%完美验证的页面不一定安全。例如,如果您的验证是完美的,但您的身份验证很差,则某人可以向其发送“有效”代码,该代码将与更经典的 SQL 注入或 XSS 代码一样恶劣,甚至更加恶劣。这涉及到其他问题,除了在此讨论的验证之外,还有其他问题需要考虑。
if(Request.IsLocal)
。 - ShadowG