【问题标题】:Creating an AntiForgeryToken through Dependency Injection通过依赖注入创建 AntiForgeryToken
【发布时间】:2010-12-14 17:04:20
【问题描述】:

我正在努力提高我公司网站的安全性,并希望创建一个令牌以防止可以轻松维护的伪造尝试,这就是我想出的。

public class AntiForgeryToken
{
    private readonly string _referenceToken;

    public AntiForgeryToken()
    {
        _referenceToken = Guid.NewGuid().ToString();
    }
    public string ReferenceToken
    {
        get { return _referenceToken; }
    }
}

在我的 MasterPage 基类中,我有一个 HiddenField,其属性名为:ReferenceToken

protected virtual void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        InjectToken();
    }

    ValidateToken();
}

private void InjectToken()
{
    var token = ObjectFactory.GetInstance<AntiForgeryToken>();
    ReferenceToken = token.ReferenceToken;
}

private void ValidateToken()
{
    var token = ObjectFactory.GetInstance<AntiForgeryToken>();
    if (ReferenceToken.Equals(token.ReferenceToken, SC.InvariantCultureIgnoreCase)) 
            return;
    ...do stuff for failed token
}

我有 StructureMap 句柄将令牌存储在 Session 中,因此它会在每个用户会话中持久保存,所有这些都是 AntiForgery 方案的有效实现吗?

编辑: 我的问题似乎有些混乱,是的,我知道 ASP.NET MVC 有一个内置的 AntiForgeryToken 方案,这个问题明确地是关于如何为 WebForms 重新创建它 以防止使用 CSRF 攻击(跨站点请求伪造)。我理解这绝不会消除对用户权限进行适当授权的需要。

我将提出@Neal 和@solairaja 发布的链接:Prevent Cross-Site Request Forgery (CSRF) using ASP.NET MVC’s AntiForgeryToken() helper。本文解释了更多 CSRF 攻击是什么以及 MVC 如何阻止它,但是他们的解决方案不适用于 Web 表单,这就是我开始实施自己的解决方案的原因。

在看到@Neal 的回复后,我认为这很可能是公认的答案,因为我没有意识到我可以从 MVC 工具中获取实际源代码,这很可能会取代 guid 创建。但如果其他人有一些有价值的信息要补充,我会留下这个问题。

【问题讨论】:

标签: c# asp.net webforms dependency-injection antiforgerytoken


【解决方案1】:

安全第一条规则:不要尝试自行设置安全性

据我了解您的描述,这不是一个有效的防伪方案。攻击者只需使用其浏览器的查看源代码功能来查找令牌即可绕过它。然后他或她可以发布他们喜欢的任何内容,只要他们记得发布该令牌,而您的代码不会知道其中的区别。

抱歉,如果我完全误解了您的描述...

【讨论】:

  • 我很确定这正是为 MVC 编写 Microsoft AntiForgery 令牌的方式,它使用页面上的隐藏字段。如果我使用 MVC,无论如何我都会使用它们的功能,但这不包含在 Web 表单中。
  • 我将不得不在这方面使用 DV,因为您没有添加任何信息或提供已经存在的解决方案来支持您的第一条规则。
【解决方案2】:

首先,我想我应该问...“AntiForgery”到底是什么意思?你担心被伪造是什么?接下来的其余部分只是一些浮现在脑海中的一般信息......

我要改变的一件事是不使用 Guid.NewGuid。关于它是否是随机的,因此不适合安全目的存在争议。也就是说,我认为这将是一次非常艰难的攻击。

查看 RNGCryptoServiceProvider 的 GetBytes 方法,以了解更适合生成随机令牌的方法。除了更好的随机性之外,这样做的另一个优点是您可以将其设置为任何您想要的大小。

您是通过 ssl 执行此操作吗?首先,ssl 是中间人攻击的第一道防线。它可能不足以满足所有需求(其他人可以对此进行辩论),但如果您担心这些事情,那么它就是起点。如果不是,你如何确保你得到的是来自正​​确机器的响应,而不是首先响应的中间人?如果没有 SSL 或同等功能,您的令牌就像您做的任何其他事情一样容易被盗。

要考虑添加的另一件事是让您的代币仅适用于一次旅行,并且您在下次旅行时生成一个新的代币返回给客户。尝试重用它失败了。

我会尝试用您自己设计的其他东西替换 SSL,如果这是您的想法。但是,如果您担心重放,则生成一次令牌是阻止它的一种方法。如果您担心用户两次提交相同的表单数据,这是一回事。如果您对此感到担忧,我也会考虑您的整体应用程序设计。合理的业务逻辑设计(例如不信任客户端向您发送敏感信息(例如购物车中商品的价格))可以击败许多重播和类似场景。

还请查看有关 ASP.NET 和 IIS 安全性的各种 Microsoft 指南(即 Google ASP.NET 或 IIS 安全站点:microsoft.com)作为起点。很多聪明人已经为我们解决了很多问题。

【讨论】:

  • 是的,此令牌将位于 SSL 之上,以进一步防止重放/中间人攻击。我不认为单次使用令牌会在实际安全性和阻止白痴点击内容之前甚至在最后一次回发完成之前提供更多的稳健性,直到您提出这一点。
  • 我开始考虑使用一次性令牌的唯一一件事是跟踪有效令牌的有效方法,因为用户可以打开多个窗口,将所有令牌的列表保留在会话,然后在提交时通过从列表中删除令牌来使令牌过期,因此任何后续提交相同表单的尝试都会失败,直到页面完成它的往返并在视图状态中存储了一个新令牌?
【解决方案3】:

所有这些都是有效的吗 AntiForgery 的实现 方案?

据我所知,您似乎正在将 GUID 插入页面,然后在页面返回时查找相同的 GUID。

我不知道你的要求是什么,所以我不能说这个方案是否真的“有效”。不过,我可以指出几点:

  1. 如果 GUID 只存在于页面上,那么什么会阻止用户在一台机器上阅读页面并从另一台机器提交表单?如果您还没有将其与 cookie 或会话(也使用 cookie)相关联,那将是很好的选择。
  2. 如果 GUID 作为静态隐藏 &lt;input&gt; 字段写入页面,则机器人可以读取和提交表单。您可以通过要求页面上的脚本在提交之前处理令牌来解决此问题。
  3. 您在使用 ViewState 吗?如果是这样,您可以考虑将ViewStateUserKey 设置为每个客户唯一的可重复值;它执行的功能与您在此处描述的功能相似。

【讨论】:

  • “我有 StructureMap 句柄将令牌存储在 Session 中,因此它在每个用户会话中都保持不变”
【解决方案4】:

克里斯,

您的方法或多或少模仿了 MVC 中的防伪方法,除了它们使用从 RNGCryptoServiceProvider 生成的 base64 编码字节数组并将令牌存储在页面(隐藏表单字段)和 cookie 中。我建议将更多逻辑移到令牌实现中(例如,将大部分验证逻辑封装在令牌内)。

MVC 实现的代码可在http://aspnet.codeplex.com/sourcecontrol/changeset/view/23011?projectName=aspnet#391757 免费访问,如果可能的话,您可能应该查看该代码以及http://blog.codeville.net/2008/09/01/prevent-cross-site-request-forgery-csrf-using-aspnet-mvcs-antiforgerytoken-helper/ 以获得分析和想法。

【讨论】:

  • 我不知道我能得到它的来源!是的,我对令牌的注入/验证有更好的封装,为了简洁起见,我刚刚删除了所有这些,特别是当它对我的实际问题没有任何帮助时。我还将添加 cookie 部分以遵循他们的实现。
  • 您当前的实现基本的双重用途,即会话 cookie 作为防伪 cookie 的占位符,因此并非完全必要(但确实减少了会话负载)。
【解决方案5】:

正如 NICK 所说,对我来说,您提供的代码看起来就像您正在将 GUID 插入页面,然后在页面返回时查找相同的 GUID。当您使用存储到会话中的结构映射时,这也是个好主意。

但有一些内置方法可用于此 AntiForgery 概念。

请先参考以下链接了解

http://blog.maartenballiauw.be/post/2008/09/01/ASPNET-MVC-preview-5s-AntiForgeryToken-helper-method-and-attribute.aspx

现在,

查看以下链接了解详细说明和接近方法。

http://msdn.microsoft.com/en-us/library/system.web.mvc.htmlhelper_methods.aspx

http://blog.codeville.net/2008/09/01/prevent-cross-site-request-forgery-csrf-using-%20aspnet-mvcs-antiforgerytoken-helper/

谢谢!!

【讨论】:

    【解决方案6】:

    我将验证经过身份验证的用户实际上是否具有进行请求修改所需的权限,而不是使用这样的防伪令牌。

    例如网页是“创建用户页面”,我会检查经过身份验证的用户是否有权创建新用户。该页面是否为“编辑用户 X 页面”,我会检查经过身份验证的用户是否有权修改用户 X。可能是因为他自己是用户 X,或者是管理用户。

    也就是说,使用 GUID 并不是很安全。 Guid 是基于为唯一性而非随机性而编写的算法生成的。 AFAIK 有三种有效的算法,基于名称的、基于时间的和随机的。如果系统使用的 Guid 算法(可能会被未来的 .NET 版本更改)是基于时间的,那么猜测有效的 Guid 并不是很困难。

    【讨论】:

    • DV 因为我的问题与授权/用户访问无关
    • Pete,请阅读blog.codeville.net/2008/09/01/… 以了解防伪令牌的原因/方式。 CSRF 将提交用户会话 cookie,因此只要攻击发生在活动会话范围内,它就会看起来像经过身份验证的用户。防伪令牌被设计为一次性使用令牌,因此漏洞利用窗口显着减少。
    • @Neal - 感谢该链接,这是一个不知道该特定漏洞。
    【解决方案7】:

    在我看来,它好像在为每个请求生成一个金丝雀。现在如果用户打开多个标签会发生什么? :)

    您的方法(以及 ASP.NET MVC 实现)的问题在于它依赖于开发人员来实现它。像这样的安全应该是选择退出,而不是选择加入。当我写了AntiCSRF module 时,我最终改用了 ASP.NET 页面生命周期,这意味着不会更改底层代码,除非开发人员想要选择退出 CSRF 检查的页面。您会注意到,它使用一个令牌,该令牌在该用户的浏览器会话的整个生命周期内都持续存在 - 实际上不需要在每个请求中更改令牌。

    现在我写这个模块主要是为了说明我即将出版的书的一些概念(在此处插入广告grin),你当然可以使用ViewStateUserKey,但这也是可选的- 加入,而不是选择退出。

    【讨论】:

    • 我想你也错过了我所说的令牌将由 StructureMap 存储在会话中的地方。并且这里发布的代码是为了简洁起见,正如我向@Neal 解释的那样,我对注入/验证有更好的封装,它实际上并没有在每页上实现(所以它符合 DRY),这与我的无关问题。
    • 我的意思是您仍然依赖从您的基页类继承的开发人员。如果您通过 HTTP 模块连接到 page_prerender 事件,那么您不必担心开发人员从正确的类继承。将检查的令牌存储在哪里并不重要(尽管 StructureMap 在网络场中很容易工作吗?)
    • 我看了你的模块,它看起来很不错,我一定是在谷歌上搜索错误的关键字,我以前找不到这个!
    • 我怀疑我的应用程序很快就需要托管在网络农场中,但是如果是这种情况,我会将我的实际会话状态替换为分布式缓存,然后可能需要编写自定义SM 的缓存提供程序。实际上,我将它放在我的母版页的基类中,所有母版页都从该基类派生,因为它具有站点全局的其他功能。
    • 嘿,我写的模块是偏执的。如果您是唯一的开发人员,并且您总是要使用母版页,那么就足够公平了。但是一旦其他人开始编辑您的代码...:D
    【解决方案8】:

    如果您使用 View State,ASP.NET Web 窗体会在默认情况下防止 CSRF 攻击。除非您在应用程序中有效地禁用了视图状态,否则您正在添加代码来解决您没有的问题。

    View State 防止 CSRF 攻击
    .Net CSRF Guard - OWASP

    【讨论】:

      猜你喜欢
      • 2020-01-02
      • 2013-12-24
      • 2019-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-10
      • 2016-12-28
      相关资源
      最近更新 更多