【问题标题】:Implementing secure, unique "single-use" activation URLs in ASP.NET (C#)在 ASP.NET (C#) 中实现安全、唯一的“一次性”激活 URL
【发布时间】:2009-06-02 05:37:17
【问题描述】:

我有一个场景,我正在构建的网站的用户需要能够将一些基本信息输入网络表单而无需登录。该站点正在使用 ASP.NET/C# 进行开发,并使用 MSSQL 2005 处理其关系数据。

网站将向用户发送一封电子邮件,为他们提供一个独特的链接以输入他们所需的特定信息。该电子邮件将与我们在注册论坛等网站时收到的电子邮件样式非常相似,其中包含一个随机生成的、唯一的 URL 参数,该参数专门针对单一目的(例如验证论坛的电子邮件地址)。

我的问题是关于这个问题的安全实施。我正在考虑使用 GUID 作为唯一标识符,但不确定它在安全领域中的含义。

  1. GUID 是否足够长,以至于无法轻易猜出值(或随着时间的推移被暴力破解)?
  2. .NET 的 GUID 实现是否足够随机,因为在“密钥空间”中生成所有可能值的机会均等?

  3. 如果使用 GUID 是一种可接受的方法,那么网站是否应该通过 URL 重写或通过将数据表中的信息与 GUID 关联作为参考来重定向到信息?

  4. 使用 URL 重写会隐藏数据的真实来源吗?

  5. 我是否应该考虑使用 TSQL 的 SELECT NEWID() 作为 .NET 实现的 GUID 生成器?

  6. 我解决这个问题的方法完全错了吗?

非常感谢,

卡尔

【问题讨论】:

    标签: c# asp.net tsql cryptography security


    【解决方案1】:
    1. 不,GUID 不是完全随机的,大多数位要么是静态的,要么很容易被猜到。
    2. 不,它们不是随机的,请参见 1。实际上有非常少量的位实际上是随机的,而且不是加密强随机。
    3. 不是,见 1 和 2。
    4. 您可以,但不需要...在最后查看我的解决方案。
    5. 不,见 1 和 2
    6. 是的。

    您应该使用强加密随机数生成器而不是 GUID - 使用 System.Security.Cryptography.RNGCryptoServiceProvider 生成长(比如 32 字节)数据字符串,然后使用 base64 encode 生成。
    此外,假设这是对敏感数据的某种注册,您可能希望限制链接的有效期,例如 60 分钟或 24 小时 - 取决于您的网站。
    您需要将这些值映射到特定用户。然后,您可以根据需要自动向他展示适当的表格。不需要进行 url 重写,只需将其用作用户的标识符(在此页面上)。
    当然,别忘了这个 URL 应该是 HTTPS...

    顺便说一句,请注意 - 在电子邮件中添加某种形式的文本是一种很好的做法,说明用户不应该点击匿名电子邮件中的链接,通常您的网站不会发送,并且他们不应该在点击 blablabla 后输入密码....

    哦,差点忘了 - 您应该考虑的另一个问题是,如果用户希望向他发送几封电子邮件,例如多次点击注册。他能一遍又一遍地这样做,并获得许多有效的 URL 吗?只有最后一个有效吗?或者也许相同的值会一遍又一遍地被重新发送?当然,如果匿名用户可以请求这封电子邮件,那么 DoS 可能会成为问题......更不用说如果他输入自己的电子邮件,他也可以输入任何随机地址,淹没一些可怜的 shmuck's收件箱并可能导致您的邮件服务器被列入黑名单...
    没有一个正确答案,但需要在您的应用程序的上下文中加以考虑。

    【讨论】:

      【解决方案2】:
      1. 是的,2128 足够长了。
      2. 不,GUID 实现旨在生成唯一 GUID,而不是随机的。您应该使用 cryptographically secure 随机数生成器(例如 RNGCryptoServiceProvider)生成 16 个随机字节并用它初始化 Guid 结构。
      3. 是的,总体而言,这是一种可以接受的方法。两者都可以。
      4. 是的,如果你不提供任何其他线索
      5. 不,goto 2
      6. 不,没关系。您只需要使用加密安全的随机数生成器来生成 GUID。

      【讨论】:

      • 抱歉,这确实是错误的,并且具有误导性 - GUID 不适用于此类事情,请参阅下面的答案。
      • GUID,在 unique identifier 的意义上并不真正适合(正如我在 #2 中提到的)。但是没有什么可以阻止您将它们用作 128 位数字。
      • 但问题是它们不是真正的 128 位数字(即使确实需要 128 位来存储它们)。同样,太多的信息要么是静态的,要么很容易被猜到。 GUID 在安全标识符中没有用处。 OP 需要的是安全的东西,并且在“128 位随机性”的意义上足够长,而 GUID 不是,甚至不是很接近。
      • 问题是,您不使用 Guid.NewGuid() 和 NEWID() 正如我所说的明确。您使用 CSRNG 生成 16 个随机字节并从中获取 GUID 表示。
      • @AviD:我从来没有说过你会通过调用Guid.NewGuid 来生成 GUID。我只关心 GUID 格式。您使用随机数生成器生成 16 个任意字节并将其打包为 GUID 格式。是的,它可能不能用作全球唯一标识符,但这没关系。 GUID format 只是一种方便的格式。
      【解决方案3】:

      这可能有点矫枉过正,但您可以使用您的 guid(NewGuid 很好)作为 hash salt 将他们的电子邮件地址与 SHA1 散列,并将其放在 URL 中。然后,当他们到达您的页面时,您可以询问他们的电子邮件地址、检索 guid 并重新计算哈希以进行验证。即使有人知道要尝试哪些电子邮件地址,他们也永远无法在不知道您使用的 guid 的情况下产生哈希冲突(或者这将花费他们很长时间:)。当然,您必须将他们的电子邮件和哈希盐 GUID 保存在数据库中。

      【讨论】:

      • 虽然上面在技术上直接回答了我的问题,但我喜欢你的实现想法。经过一番思考后,我对其进行了一些扩展: URL = SHA1(email + randomPass + salt) serverHash = SHA1(email + randomPass) 表列: serverHash (primary key) salt randompass 也将与电子邮件中的 url 一起发送并在原始 URL 计算中使用,然后丢弃。我相信在这个实现中不会将敏感信息存储在数据库中。这意味着即使暴露了原始数据表,您也无法生成 URL 来暴力破解电子邮件地址
      • 很高兴为您提供帮助。关于您的解决方案的一个注释,您将自己打开一个新的用例,因为您无法重新创建电子邮件。也就是说,用户请求和电子邮件,什么都不做,请求另一个,然后单击第一封电子邮件中的链接(我相信你知道,用户会做这样的事情:)所以,你必须让每个ServerHash 通过将您发送电子邮件的日期保留在数据库中并给用户一个合理的时间来响应。
      • 是的,谢谢。我意识到这一点。生成的电子邮件和 URL 非常针对具体情况,并且生成的目的是让用户通常可以为自己节省一些钱,所以他们可能应该使用它们,但我也会进行一些处理。
      • 我读过的大多数加盐建议都建议在开头而不是结尾添加盐。
      【解决方案4】:

      我建议只使用一个普通的随机数,例如来自 RNGCryptoServiceProvider.GetBytes 的字节。 GUID 并不意味着完全随机。它们具有固定位,并且某些版本使用您的 MAC 地址。 GetBytes 还为您提供了使用超过 128 位的选项(尽管应该足够了)。

      我认为最好不要将用户的数据放在 url 中。虽然 HTTPS 可以在传输过程中保护它,但它可能仍然存在于用户的浏览器历史记录等中。最好使用 POST 并将随机数与数据库的一行相关联。

      【讨论】:

        【解决方案5】:

        首先,如果可能,您应该通过限制查询的吞吐量来限制暴力破解(例如,限制每个 IP 每秒的尝试次数,以及每秒的总尝试次数)。

        根据 GUID 生成算法,这可能不是一个好主意。虽然最近的实现应该“通常足够好”,但这些值可以有很多可预测性。最近在 .NET 中使用的实现应用了哈希,否则您可以清楚地识别 MAC 地址字段和自启动以来的时间。但是,可能的值是有限的,所以你没有真正的 128 个随机位。

        如果安全是重中之重,我会选择cryptographically strong random number generator,并构建一串随机字符。这也使得 oyu 独立于一个容易猜测的算法(因为 guid.ToString() 很容易被识别出来),在不久的将来可能会发现攻击。

        如果满足这些条件,我认为在查询字符串中包含键没有问题。

        【讨论】:

          【解决方案6】:

          在数据库中创建一个带有linkID 和Datesent 列的表,在生成链接时将DateTime.Now 插入表中并返回linkId,将linkID 设置为activationLink 上的查询字符串参数。在加载激活页面时检索linkId并使用它来调用一个存储过程,该存储过程将在将对应的linkId作为参数传递时返回日期,当您获取日期时,您可以添加您希望链接保持活动多长时间使用 .AddDays()/.AddMonths 这是日期时间的 C# 方法。然后将您返回的日期与今天的日期进行比较。如果它已经过了几天或几个月的长度,则给出错误消息,否则继续并显示页面内容。我建议您将页面内容保留在面板中并将其设置为 vissible = "false",然后仅在日期仍在范围内时才使面板可见 =“true”。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-05-23
            • 1970-01-01
            • 2020-10-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多