65.9K
CodeProject 正在变化。 阅读更多。
Home

在OnChange/ViewState中使用验证的危险。

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.49/5 (20投票s)

2004年5月6日

5分钟阅读

viewsIcon

80054

本文演示了通过使用ASP.NET OnChange事件进行验证而容易引入的严重错误和安全漏洞。

引言

ASP.NET使用隐藏的ViewState字段来确保每个控件的OnChange事件仅在用户实际更改字段时触发。因此,如果一个表单提交了三次,但某个字段只更改了一次,那么OnChange事件只触发一次。这可能导致过时的验证、应用程序错误和安全漏洞。

有许多关于ASP.NET的书籍和文章描述了OnChange的一般工作原理,以及Web窗体在很大程度上可以以与客户端窗体相同的方式实现。然而,我没有看到任何指出危险的文章。本文认为,ASP.NET的这一基本功能应该被完全避免。

危险

假设我们有一个订单表单,其中包含以下字段:

订购数量 验证以检查是否有库存。
信用额度 只是一个显示的标签
当前客户余额 显示标签,如果订单确认则更新。
信用额度 显示标签。
订单价值 由客户端JavaScript计算。验证以确保不超出信用额度。
送货详情 验证以检查与订单的兼容性。

现在,假设用户提交了具有有效数据但无效送货详情的表单。首先,将检查*订购数量*OnChange事件以确保有库存。然后*送货详情*OnChange事件将触发,并发现错误。表单将呈现给用户进行更正。到目前为止一切顺利。

然后用户更正送货详情并重新提交表单。*订购数量*没有改变,因此不会重新验证。如果*送货详情*现在没问题,那么订单将被接受。

但是,如果我们用户的送货详情正在纠正期间,有人提交了一个耗尽我们最后库存的订单,会发生什么?我们的用户订单现在应该因为缺货而失败。但是*订购数量*不会被重新验证,所以订单实际上会被接受。很有可能我们会发现数据库中存储了负库存值。

同样,信用额度测试引入了安全漏洞。假设一个用户有1000美元的信用额度,那么他们可以创建十个会话并提交10个900美元的订单,这些订单都具有错误的送货详情。在检查送货详情之前,每个订单都会通过信用额度测试。然后用户可以更正所有10个订单的送货详情并重新提交它们。它们都会被接受,因为信用额度已经被验证过,即使这十个订单的总价值为9000美元。

更糟糕的情况是,如果程序计算的更新后的客户余额等于表单上显示的当前客户余额加上订单价值。假设在上一场景中,用户最初的未偿余额为50美元。那么这十个订单中的每一个都会计算出更新后的客户余额为50美元+900美元。它会进行10次不正确的计算,余额将停留在950美元,尽管用户实际订购的商品总价值为9050美元。ASP.NET使得通过ViewState很容易获取显示标签的值,从而很容易犯这种类型的错误。

讨论

这些错误的麻烦在于它们非常难以测试。上面的表单在正常测试期间会工作得很好。只有在生产环境中才会出现奇怪的、无法重现的错误。最糟糕的那种。

根本问题在于,大多数验证是针对不断变化的数据库进行的,然而没有使用任何锁定机制,无论是悲观的还是乐观的。通过允许用户在验证处理过程中更改字段,引入了许多潜在的麻烦。

现在,很容易开发出复杂的方案,使每个表单都可以使用OnChange进行编码,并且通常是正确的。例如,可以将数据库时间戳与某些字段一起存储。但在实际应用程序中,正确性不应依赖于复杂的方案。我想要一种工具和方法,可以使引入这种难以重现的错误变得非常困难,即尽可能地防傻。 (这可能说明了作者。)

而且OnChange最初是为了什么目的而引入的?是为了避免在那些提交了错误数据的相对较少的表单上重新验证字段的微小开销吗?在网络上传输视图状态的成本会更大。

客户端窗体需要复杂的事件驱动的控制流,因为我们想要即时、逐字段的验证。Web窗体的一个优点是可以在一个简单的单次传递中完成验证,而不会受到事件的干扰。这简化了逻辑,并避免了错误。费力地重新引入复杂的事件驱动控制流似乎很疯狂。当Web窗体中没有像客户端窗体那样可用的有状态ADO锁定机制时,这一点尤其如此。

当然,有人可能会争辩说我只是将OnChange用于了错误的目的。当然,验证不应该在那里进行。但是OnChange/ViewState/WebControl事件模型似乎是ASP.NET的一个主要特性。我看到过书籍和代码以完全相同的方式使用它。它是Visual Studio双击控件时的默认事件。如果它仅用于更晦涩的用途,那么为什么ViewState默认启用?我相信很多程序员会感到困惑。

建议

永远不要使用OnChange。只需在页面加载期间执行验证。必要时,每次提交表单时重复验证,以避免潜在的错误。保持简单,保持快速,保持正确。

这也表明您应该始终禁用ViewState,因为您将不会使用它。没有潜在的加密安全漏洞需要担心。

反馈

我希望能够就如何最好地使用ASP.NET构建简单、健壮的应用程序展开热烈讨论。我还希望收到对其他受关注较少的ASP.NET陷阱的评论。

令我惊讶的是,我以前从未见过这个问题被讨论过,这启发了我写这篇文章。

© . All rights reserved.