在 ASP.NET (C#) 中实现安全、独特的“一次性”激活 URL

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/938031/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 03:46:09  来源:igfitidea点击:

Implementing secure, unique "single-use" activation URLs in ASP.NET (C#)

c#asp.nettsqlcryptographysecurity

提问by Tyst

I have a scenario inwhich users of a site I am building need the ability to enter some basic information into a webform without having to logon. The site is being developed with ASP.NET/C# and is using MSSQL 2005 for its relational data.

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

The users will be sent an email from the site, providing them a unique link to enter the specific information they are required. The email will be very similar to the style of email we all get when registering for sites such as forums, containing a randomly generated, unique URL paramter specifically pertaining to a single purpose (such as verifying an email address for a forum).

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

My queries are regarding the secure implementation of this problem. I was considering using a GUID as the unique identifier, but am unsure of its implications in the security world.

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

  1. Is a GUID sufficiently long enough such that values cannot be easily guessed (or brute-forced over time)?
  2. Is .NET's GUID implmentation sufficiently random in the sense that there is an equal chance of generation of all possible values in the "key space"?

  3. If using a GUID is an acceptable approach, should the site then redirect to the information via URL rewriting or by associating the information in a datatable with the GUID as a reference?

  4. Will using a URL rewriting hide the true source of the data?

  5. Should I consider using TSQL's SELECT NEWID() as the GUID generator over the .NET implementation?

  6. Am I completely wrong with my approach to this problem?

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

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

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

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

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

Many thanks,

非常感谢,

Carl

卡尔

采纳答案by Mehrdad Afshari

  1. Yes, 2128is long enough.
  2. No, GUID implementations are designed to generate uniqueGUIDs rather than random ones. You should use a cryptographically securerandom number generator (e.g. RNGCryptoServiceProvider) to generate 16 random bytes and initialize a Guidstructure with that.
  3. Yes, it's an acceptable approach overall. Both will work.
  4. Yes, if you don't give out any other clues
  5. No, goto 2
  6. No, it's pretty OK. You just need to use a cryptographically secure random number generator to generate the GUID.
  1. 是的,2 128足够长了。
  2. 不,GUID 实现旨在生成唯一的GUID,而不是随机的。您应该使用加密安全的随机数生成器(例如RNGCryptoServiceProvider)来生成 16 个随机字节并Guid用它初始化一个结构。
  3. 是的,总体来说这是一种可以接受的方法。两者都会起作用。
  4. 是的,如果你不提供任何其他线索
  5. 不, goto 2
  6. 不,还可以。您只需要使用加密安全的随机数生成器来生成 GUID。

回答by Matthew Flaschen

I would recommend just using a plain random number, e.g. bytes from RNGCryptoServiceProvider.GetBytes. GUIDs are not meant to be completely random. They have fixed bits, and some versions use your MAC address. GetBytes also gives you the option to use more than 128 bits (though that should be plenty).

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

I think it is better not to put the user's data in a url. Though HTTPS can protect it in transit, it may still be in the user's browser history or such. It is better to use POST and associate the random number with a row of your database.

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

回答by peterchen

First, if possible you should limit brute force, by limiting the throughput of the query (e.g. limited tries per IP per second, and limited total tries per second).

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

Depending on the GUID generation algorithm, this might not be a good idea. While recent implementations should be "good enough usually", there can be a lot of predictability to the values. Recent implementations as the one used in .NET apply a hash, otherwise you have clearly identifiable fields for MAC address, and time since boot. However, the possible values are limited, so you don't have true 128 random bits.

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

If security is the top priority, I'd pick a cryptographically strong random number generator, and build a string of random characters. This also makes oyu independent of an easily guessable algorithm (as a guid.ToString() is easily identified as such) for which an attack might be discovered in the near future.

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

If these criteria are met, I see no problem in having the key in the query string.

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

回答by JP Alioto

It might be overkill, but you could hash their email address with SHA1using your guid (NewGuid is fine) as a hash saltand place that in the URL. Then, when they arrive at your page, you could ask them their email address, retrieve the guid and recompute the hash to validate. Even if somebody were to know what email addresses to try, they would never be able to generate a hash collision without knowing the guid you salted with (or it would take them a hell of a long time :). Of course, you would have to save their email and the hash salt guid in the database.

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

回答by AviD

  1. No, GUIDs are not fully random, and most of the bits are either static or easily guessable.
  2. No, they're not random, see 1. There is actually a very small number of bits that are actually random, and not cryptographically strong random at that.
  3. It's not, see 1 and 2.
  4. you can, but dont need to... see my solution at the end.
  5. No, see 1 and 2
  6. Yes.
  1. 不,GUID 不是完全随机的,大多数位要么是静态的,要么很容易猜到。
  2. 不,它们不是随机的,参见 1。实际上有非常少量的比特实际上是随机的,并且在加密方面不是强随机的。
  3. 不是,见1和2。
  4. 你可以,但不需要......在最后看到我的解决方案。
  5. 不,见 1 和 2
  6. 是的。

What you should be using instead of a GUID, is a cryptographically strong random number generator - use System.Security.Cryptography.RNGCryptoServiceProvider, to generate long (say, 32 bytes) string of data, then base64 encodethat.
Also, assuming this is some kind of registration with sensitive data, you'd want to time limit the validity of the link, say 60 minutes, or 24 hours - depends on your site.
You'll need to keep a mapping of these values to the specific users. Then you can automatically present him with the proper form as needed. Dont need to do url rewriting, just use that as the user's identifier (on this page).
Of course, dont forget this URL should be HTTPS...

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

Btw, just a note - its good practice to put some form of text in the email, explaining that users shouldnt click on links in anonymous emails, and typically your site wont send, and they should never enter their password after clicking blablabla....

顺便说一句,只是一个说明 - 在电子邮件中放入某种形式的文本是一种很好的做法,解释说用户不应该点击匿名电子邮件中的链接,通常你的网站不会发送,他们不应该在点击后输入他们的密码 blablabla ...... .

Oh, almost forgot - another issue you should consider is what happens if the user wants several emails sent to him, e.g. hits register several times. Can he do this over and over again, and get many valid URLs? Is only the last one valid? Or maybe the same value gets resent over and over again? Of course, if an anonymous user can put in a request for this email, then DoS may become an issue... not to mention that if he puts in his own email, he can put in any random address too, flooding some poor shmuck's inbox and possibly causing your mail server to get blacklisted...
No one right answer, but needs to be considered in context of your application.

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

回答by Spegah

create a table in the database with a linkID and a Datesent column, on generation of the link send insert DateTime.Now into the table and return linkId, set the linkID as a querystring parameter on the activationLink. On load of the activation page retrive the linkId and use it to evoke a stored procedure that will return the date when passed the correspondin linkId as a parameter, when you get the date back you can add how long you want the link to stay active by using the .AddDays()/.AddMonths this are C# methods for datetime. then compare the date you got back with today's date. if it has passed its length of days or months give an error message or else continue and display the page content. I sugest you keep the contents of the page in a panel and and set it vissible = "false" then only make the panel visible="true" if the date is still within range.

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