这是一个关于基于表单的网站认证的权威指南[closed]。
这是一个关于基于表单的网站认证的权威指南[closed]。
版主请注意:
这个问题不适合我们的问题和回答格式,因为目前适用于 Stack Overflow 的主题规则。我们通常对这样的问题使用“历史锁定”,因为内容仍然具有价值。但是,这个问题的答案正在积极维护,历史锁定不允许编辑答案。因此,已经应用了“维基答案”锁定,以允许编辑答案。你应该假设通常由历史锁定处理的主题问题存在(即这个问题不是 Stack Overflow 中一个范例问题的好例子)。
网站基于表单的认证
我们认为 Stack Overflow 不仅应该成为非常专业的技术问题的资源,还应该提供关于如何解决常见问题变体的一般指导方针。 \"网站基于表单的认证\" 应该是这样一种实验的好主题。
它应该包括以下主题:
- 如何登录
- 如何退出登录
- 如何保持登录状态
- 管理 cookie(包括推荐设置)
- SSL/HTTPS 加密
- 如何存储密码
- 使用密保问题
- 忘记用户名/密码功能
- 使用 nonce 防止跨站请求伪造 (CSRF)
- OpenID
- \"记住我\" 复选框
- 浏览器自动填充用户名和密码
- 秘密 URL(公共 URL 受到摘要保护)
- 检查密码强度
- 电子邮件验证
- 以及更多有关基于表单的身份验证的内容...
它不应该包括以下内容:
- 角色和授权
- HTTP 基本认证
请帮助我们:
- 建议子主题
- 提交有关此主题的好文章
- 编辑官方答案
决定性文章
发送凭据
100%安全地发送凭据的唯一实用方法是使用SSL。使用JavaScript哈希密码不安全。客户端密码哈希的常见陷阱:
- 如果客户端和服务器之间的连接未加密,则您所做的一切都容易遭受中间人攻击。攻击者可以替换传入的JavaScript以破坏哈希或将所有凭据发送到他们的服务器,他们可以监听客户端响应并完全冒充用户,等等。带有受信任证书颁发机构的SSL旨在防止MitM攻击。
- 如果服务器未对哈希密码进行额外的冗余工作,则收到的哈希密码不太安全。
还有另一种安全的方法称为SRP,但它受专利保护(尽管它是免费许可的),而且可用的好实现很少。
存储密码
不要以明文形式在数据库中存储密码,即使您不关心自己网站的安全性。假设您的某些用户会重复使用其在线银行帐户的密码。因此,请存储哈希密码,并丢弃原始密码。确保密码不会显示在访问日志或应用程序日志中。 OWASP 建议在新应用程序中首选Argon2。如果没有可用的,则应使用PBKDF2或scrypt。最后,如果上述方法都不可用,请使用bcrypt。
仅使用哈希函数是不安全的。例如,相同的密码意味着相同的哈希 - 这使哈希查找表成为大量密码一次性破解的有效方法。相反,应存储加盐的哈希值。盐是在哈希之前附加到密码的字符串 - 每个用户使用不同的(随机)盐。盐是公共值,因此可以将它们与哈希值一起存储在数据库中。请参见此处以获取更多信息。
这意味着您无法向用户发送其遗忘的密码(因为您只有哈希值)。除非您已经验证了用户(用户必须证明他们能够阅读发送到存储的(已验证)电子邮件地址的电子邮件),否则不要重置用户的密码。
安全问题
安全问题是不安全的 - 避免使用它们。为什么?任何安全问题都比密码更好地完成了它的工作。在本维基中阅读@Jens Roland answer中的第III部分:使用秘密问题。
会话Cookie
用户登录后,服务器会向用户发送会话Cookie。服务器可以从Cookie中检索用户名或ID,但其他人无法生成这样的Cookie(TODO解释机制)。
Cookie会被劫持:它们仅与客户端机器的其他通信一样安全。它们可以从磁盘中读取,在网络流量中被嗅探,被跨站点脚本攻击提取,被恶意DNS钓鱼所钓鱼,以使客户端将其Cookie发送到错误的服务器上。不要发送持久性Cookie。 Cookie应该在客户端会话结束时到期(关闭浏览器或离开您的域名)。
如果您想让用户自动登录,可以设置一个持久化cookie,但它应该与完整会话cookie不同。您可以设置一个附加标志,表示用户已自动登录,并且需要为敏感操作进行真正的登录。这在希望为您提供无缝,个性化购物体验的购物网站中很流行,但仍然保护您的财务详细信息。例如,当您返回亚马逊时,他们会向您显示一个看起来像已经登录的页面,但是当您去下订单(或更改您的送货地址、信用卡等)时,他们会要求您确认密码。
另一方面,银行和信用卡等金融网站仅具有敏感数据,不应允许自动登录或低安全模式。
外部资源列表:
-《Web身份验证的实践与禁忌》,21页的学术文章,提供许多有用的提示。
-有关主题的论坛讨论。
-《您可能不正确地存储密码》的介绍性文章。
-有关密码存储的论坛讨论。
-不要在数据库中存储密码的警告。
-有关几种密码哈希方案弱点的维基百科文章。
-关于彩虹表如何攻击并如何防御以及其他威胁的讨论。包括广泛的讨论。
第一部分:如何登录
我们假定您已经知道如何构建一个登录+密码的HTML表单,将值POST到服务器端的脚本进行身份验证。以下各节将涉及实际可行的身份验证模式,以及如何避免最常见的安全陷阱。
使用HTTPS还是不使用HTTPS?
除非连接已经安全(即通过SSL/TLS使用HTTPS隧道传输),否则您的登录表单值将被以明文形式发送,这使得任何窃听浏览器和Web服务器之间连接的人都能够读取通过的登录。此类窃听常常由政府进行,但一般而言,我们不会处理'所有'线路,只需说一句:只使用HTTPS即可。
本质上,保护登录期间免受窃听/数据包嗅探的唯一实际方法是使用HTTPS或其他基于证书的加密方案(例如,TLS)或经过验证和测试的挑战-响应方案(例如,基于Diffie-Hellman的SRP)。任何其他方法都可以很容易地被窃听攻击者规避。
当然,如果您愿意有些不切实际,还可以采用某种形式的双因素认证方案(例如,Google Authenticator应用程序、物理的'冷战式'代码本或RSA密钥生成器加密狗)。如果正确应用,即使是未加密的连接,这也可能起作用,但很难想象一位开发者愿意实现双因素认证但不使用SSL。
不要自己编写JavaScript加密/哈希
由于在网站上设置SSL证书的认为成本(尽管可以避免)和技术难度比较高,一些开发者会尝试在浏览器中自行构建哈希或加密方案,以避免在不安全的网络下传输明文登录信息。
虽然这是一个崇高的想法,但它基本上是无用的(而且可能是一个安全漏洞),除非它与上述两个中的一个相结合 - 即使用强加密方式来保护通信或者使用一个经过验证的挑战-响应机制(如果你不知道是什么,只需知道它是数字安全中最难以证明、最难以设计、最难以实现的概念之一)。
虽然密码哈希可以有效防止密码泄露,但它容易受到回放攻击、中间人攻击/劫持(如果攻击者在HTML页面到达浏览器之前就能够向其中注入几个字节,他们简单地注释掉JavaScript中的哈希操作就行了)或者暴力攻击(因为你向攻击者提供了用户名、盐和哈希密码)。
人机验证码抗争
CAPTCHA旨在防止一种特定类型的攻击:没有人为操作的自动词典/暴力试错。毫无疑问,这是一个真正的威胁,然而,有一些无需使用人机验证码的处理方法,特别是在服务器端设计合适的登录限制机制-我们稍后将讨论这些方法。
了解到 CAPTCHA 的实现方式不尽相同;它们通常无法被人类解决,大多数实际上对机器人无效,所有实现都对廉价的第三世界劳工无效(根据 OWASP,当前的汗工费率是每 500 次测试 12 美元),而且某些实现在某些国家可能在技术上是非法的(参见 OWASP 认证备忘单)。如果你必须使用 CAPTCHA,则使用 Google 的 reCAPTCHA,因为它在定义上就是 OCR 难以处理的(因为它使用已经进行 OCR 分类错误的书籍扫描),并且非常努力地使用户友好。
就我个人而言,我倾向于觉得 CAPTCHA 很烦人,只在用户登录失败多次并且延迟等待已达最大值时作为最后的手段使用。这种情况很少发生,因此是可以接受的,并且它加强了整个系统的安全性。
存储密码 / 验证登录
经过近年来广受关注的大规模黑客攻击和用户数据泄露后,这可能终于是常识了,但必须明确:不要在数据库中以明文形式存储密码。用户数据库经常被黑客攻击、泄漏或通过 SQL 注入获得,如果您存储原始的明文密码,那么您的登录安全性就立即垮掉了。
那么,如果您无法存储密码,如何检查从登录表单 POST 的登录 + 密码组合是否正确?答案是使用 密钥派生函数 进行哈希运算。当创建新用户或更改密码时,您取其密码并通过 KDF(如 Argon2、bcrypt、scrypt 或 PBKDF2)运行,将明文密码(“correcthorsebatterystaple”)转换为一个长的、看起来随机的字符串,这样在数据库中存储就更安全了。要验证登录,您需要对输入的密码再次运行相同的哈希函数,这次传入盐,并将结果哈希字符串与存储在数据库中的值进行比较。Argon2、bcrypt 和 scrypt 已经在哈希中存储了盐。在 sec.stackexchange 上查看这篇 文章 获取更详细的信息。
使用盐的原因是单独使用哈希不够安全 - 你需要添加所谓的“盐”来保护哈希防止被彩虹表破解。盐有效地防止准确匹配的两个密码被存储为相同的哈希值,防止攻击者在执行密码猜测攻击时一次性扫描整个数据库。
密码存储不应该使用加密哈希,因为用户选择的密码并不足够强大(即通常不包含足够的熵),攻击者可以在相对较短的时间内完成密码猜测攻击,只要他能够访问哈希值。这就是为什么要使用KDF - 它们有效地“拉长密码”,这意味着攻击者每做一次密码猜测就会导致多次重复哈希算法,例如10,000次,这使得攻击者猜测密码的速度要慢10000倍。
会话数据 - “您已作为Spiderman69登录”
一旦服务器已经通过用户数据库验证了登录名和密码并找到匹配项,系统需要一种方式来记住浏览器已被认证。这个事实应该仅存储在会话数据的服务器端。
如果您不熟悉会话数据,这是它的工作原理:在一个过期的cookie中存储一个随机生成的字符串,用它来引用一个数据集合 - 会话数据 - 存储在服务器上。如果你正在使用MVC框架,这无疑已经处理好了。
如果可能的话,在将会话 cookie 发送到浏览器时,请确保 secure 和 HTTP Only 标志已设置。HTTP Only 标志可提供一定的保护,防止通过 XSS 攻击读取 cookie。secure 标志可确保仅通过 HTTPS 将 cookie 发回,从而保护免受网络嗅探攻击。cookie 的值不应可预测。如果提交了引用不存在的会话的 cookie,则应立即替换其值,以防止会话固定攻击。客户端也可以维护会话状态。这可通过使用 JWT(JSON Web Token)等技术实现。
第二部分:如何保持登录状态——臭名昭著的“记住我”复选框
持久登录 cookie(“记住我”功能)是一个危险区域;一方面,如果用户知道如何安全处理它们,它们完全与传统登录一样安全;另一方面,在粗心的用户手中,它们是巨大的安全风险,这些用户可能在公共计算机上使用并忘记注销,并且可能不了解浏览器 cookie 是什么或如何删除它们。
就我个人而言,我喜欢在定期访问的网站上使用持久登录,但我知道如何安全处理它们。如果您确定您的用户也知道如何安全处理它们,您可以毫无顾虑地使用持久登录。如果不是-好吧,那么您可能认同这样的哲学:对于那些对其登录凭据粗心大意的用户,如果它们被黑客攻击,那他们也是自作自受的。我们也不会去用户家里撕下那些充满无语的 Post-It 笔记,上面写着他们电脑显示器边缘上排成一行的密码。
当然,有些系统不能承受任何帐户被盗的风险;对于这些系统,你无法辩解有持久登录。
如果您决定实施持久性登录 cookie,则应按照以下步骤操作:
-
首先,请花些时间阅读Paragon Initiative的文章。你需要正确获取一堆元素,而该文章很好地解释了每一个元素。
-
再次强调最常见的陷阱之一,不要将持久登录 cookie(令牌)存储在数据库中,只存储其哈希值!登录令牌等同于密码,因此,如果攻击者拿到了你的数据库,他们可以使用令牌登录到任何帐户,就像他们使用明文登录密码组合一样。因此,在存储持久登录令牌时,请使用哈希(根据https://security.stackexchange.com/a/63438/5002,弱哈希就足够了)。
第三部分:使用密保问题
不要实施“密保问题”功能。“密保问题”功能是一个安全反模式。请阅读MUST-READ列表的链接4上的论文。在之前的总统竞选活动中,你可以问问萨拉·佩林(Sarah Palin)的看法,因为她的雅虎电子邮件帐户被黑客入侵,答案是...“Wasilla高中”!
即使用户指定了问题,很可能大多数用户会选择以下两种情况之一:
Don't allow unlimited attempts to reset a password. This opens up the possibility of brute force attacks, where an attacker can attempt to reset the password an unlimited number of times until they succeed. Instead, limit the number of attempts and increase the waiting time between attempts to prevent these attacks.
总之,无论采取何种方式,安全问题都存在着本质上的不安全性,不应以任何理由在身份验证方案中使用。
安全问题存在的原因是为了省去用户无法访问其电子邮件以获取激活码的一些支持电话的成本。但这是以安全和莎拉·佩林的声誉为代价。值得吗?可能不是。
第四部分:忘记密码功能
我已经提到过,您永远不应该使用安全问题来处理遗忘/丢失用户密码;同时也不要通过电子邮件向用户发送其实际密码。在这个领域还有至少两个非常常见的陷阱需要避免:
-
不要将忘记的密码重置为自动生成的强密码——这样的密码极其难以记忆,这意味着用户必须更改密码或将其写下来——比如在他们的显示器边缘上用明亮的黄色便签纸上。与其设置一个新密码,不如让用户马上选择一个新的密码——这也是他们想要做的。(一个例外可能是,如果用户普遍使用密码管理器来存储/管理通常无法记住的密码)。
-
不要允许无限次尝试重置密码。这会打开暴力攻击的可能性,攻击者可以尝试无限次重置密码,直到成功。相反,限制尝试次数并增加尝试之间的等待时间,以防止这些攻击。
始终在数据库中对遗失的密码代码/令牌进行哈希。再次强调,此代码是另一个密码等价物的示例,因此必须对其进行哈希,以防攻击者获取了您的数据库。当请求遗失的密码代码时,向用户的电子邮件地址发送明文代码,然后对其进行哈希,将哈希保存在数据库中 - 并且删除原始内容。就像密码或持久登录令牌一样。
最后需要注意的是,务必确保输入“遗失密码代码”的界面至少与登录表单本身一样安全,否则攻击者将简单地使用它获取访问权限。确保生成非常长的“遗失密码代码”(例如,16个区分大小写的字母数字字符)是一个好的开始,但考虑为登录表单本身添加相同的限制方案。
第五部分:检查密码强度
首先,您要阅读这篇简短的文章以进行现实检查:最常见的500个密码
好的,也许该列表不是任何系统上最常见的密码的权威列表,但它很好地说明了当没有强制执行的策略时,人们会选择多么糟糕的密码。另外,当您将其与公开可用的最近被盗密码的分析进行比较时,该列表看起来非常惊人。
因此,在没有最低密码强度要求的情况下,有2%的用户使用前20个最常见密码之一。这意味着:如果攻击者只获得20个尝试,您的网站上每50个帐户中就有1个可以被破解。
防止这种情况发生需要计算密码的熵并应用一个阈值。 美国国家标准与技术研究院(NIST)特别出版物800-63有一套非常好的建议。当与字典和键盘布局分析(例如,“qwertyuiop”是一个糟糕的密码)结合使用时,可以在18比特的熵水平上拒绝99%的密码选择不当密码。简单计算密码强度并向用户显示可视化强度计是好的,但不足够。除非强制执行,否则很多用户很可能会忽略它。
对于高熵密码的用户友好性的清新看法,请参阅Randall Munroe的密码强度xkcd。
利用Troy Hunt的Have I Been Pwned API检查用户密码是否与公共数据泄露中受损密码相匹配。
第六部分:更多内容 - 或:防止快速登录尝试
首先,看一下这些数字:密码恢复速度-您的密码能经受多久
如果你没有时间查看那个链接中的表格,这里是它们的列表:
-
即使使用算盘破解弱密码也需要很短的时间。
-
如果密码不区分大小写,那么破解一个9个字符的字母数字组合密码需要很短的时间。
-
如果一个复杂的大小写字母、数字、符号组合密码长度小于8个字符,那么破解它需要很短的时间(一个台式电脑在数天或数小时内可以搜索所有7个字符的密钥空间)。
-
但是,如果你每秒只有一次机会,即使是6个字符的密码也需要很长时间才能破解!
那么我们可以从这些数字中学到什么呢?很多东西,但我们可以关注最重要的部分:防止大量的连续登录尝试(即暴力破解攻击)并不那么困难。但是,正确地预防它并不像看起来那么容易。
一般来说,你有三个选择来有效地防范暴力破解攻击(和字典攻击,但由于你已经采用了强密码策略,它们不应该成为问题):
-
在N次失败尝试后呈现CAPTCHA(非常恼人且经常无效,但我在这里重复了)
-
在N次失败尝试后锁定账户并要求邮件验证(这是一种等待发生DoS攻击的行为)
-
最后,登录限速:也就是在N次失败尝试后设置尝试之间的时间延迟(是的,DoS攻击仍然可能发生,但至少它们更不可能发生,而且更加复杂)。
最佳实践#1:一种随着失败尝试次数增加而递增的短时间延迟,如下:
- 1次失败尝试=没有延迟
- 2次失败尝试=2秒延迟
- 3次失败尝试=4秒延迟
- 4次失败尝试=8秒延迟
- 5次失败尝试=16秒延迟
- 等等。
对于这个方案的拒绝服务攻击将会变得非常不实用,因为 resulting lockout time (被锁定的时间)略微大于之前的锁定时间总和。
澄清一下:这个延迟不是在向浏览器返回响应之前的延迟。 它更像是一个超时或“间歇期间”,在这段时间内,特定帐户或来自特定IP地址的登录尝试将不会被接受或评估。 也就是说,正确的凭据将不会返回成功的登录,而不正确的凭据也不会触发延迟增加。
最佳实践#2:一种中等长度的时间延迟,当N次失败尝试时开始生效,如下:
- 1-4次失败尝试=没有延迟
- 5次失败尝试=15-30分钟延迟
对于这个方案的拒绝服务攻击将会变得相当不实用,但当然仍有可能被实施。此外,还要注意的是,这种长时间的延迟对于普通用户来说可能非常烦人。健忘的用户不喜欢你。
最佳实践#3:结合两种方法 - 一种固定的短时间延迟,在N次失败尝试后开始生效,如下:
- 1-4次失败尝试=没有延迟
- 5次及以上失败尝试=20秒延迟
或者,一个递增的延迟时间和一个固定的上限,如下:
- 1次失败尝试= 5秒延迟
- 2次失败尝试= 15秒延迟
- 3次或以上的失败尝试= 45秒延迟
这个最终的方案来自于OWASP的最佳实践建议(MUST-READ列表中的链接1),即使它确实有点严格,也应该被视为最佳实践。
然而,作为一个经验法则,我会说:你的密码策略越强,你就越不用让用户烦恼延迟。如果你要求强密码(区分大小写的字母数字+要求数字和符号)9个或以上字符,你可以在激活速率限制之前给用户2-4个无延迟的密码尝试。
对于DoS攻击,这个最终的登录速率限制方案是非常不切实际的。最后,始终允许持久(cookie)登录(和/或由CAPTCHA验证的登录表单)通过,这样合法用户在攻击正在进行时甚至不会受到延迟。这样,非常不切实际的DoS攻击变成了极其不切实际的攻击。
此外,对于管理员帐户进行更积极的速率限制也是有意义的,因为那些是最具吸引力的入口点。
第七部分:分布式暴力攻击
作为旁注,更高级的攻击者将尝试通过“扩散他们的活动”来规避登录速率限制:
-
在僵尸网络上分发尝试以防止IP地址标记
-
而不是选择一个用户并尝试50,000个最常见的密码(由于我们的速率限制,他们无法做到这一点),他们会选择“最常见”的密码,并尝试针对50,000个用户。这样,他们不仅绕过了最大尝试措施,例如CAPTCHA和登录速率限制,而且他们成功的机会也增加了,因为排名第一的最常见密码要比第49.995个更有可能成功。
将每个用户账户的登录请求间隔,比如说每隔30秒,以规避监测
在这里,最好的做法是记录全局的登录失败次数,并以你网站的坏登录频率的平均值为依据设置一个上限,然后对所有用户加以限制。
太抽象了?我重述一下:
假设你的网站在过去的3个月中,每天平均有120个坏登录尝试。使用这个(平均数),你的系统可能将全局限制设定为该数的3倍 - 即在24小时内有360次失败的尝试。然后,如果所有账户的失败尝试总数超过了该数字,系统会激活全局的登录限速 - 这意味着短暂的延迟对于所有用户(然而,除了cookie登录和/或备份CAPTCHA登录)。
我还发了一个问题,更详细地讨论了如何避免困难的陷阱来抵御分布式暴力攻击。
第八部分:双因素认证和认证提供商
凭证可能会被破解,无论是通过漏洞,密码被写下并丢失,带有密钥的笔记本电脑被盗,还是用户输入登录信息进入钓鱼站点。可以使用双因素认证进一步保护登录,该认证使用来自电话呼叫、短信、应用程序或硬件令牌的单次使用代码等离散因素。有几个提供商提供双因素认证服务。
认证可以完全委托给单一的单点登录服务,由另一提供商处理收集凭据的问题,这将问题推给了一个可信赖的第三方。Google和Twitter都提供基于标准的SSO服务,而Facebook提供了类似的专有解决方案。