我们认为 Stack Overflow 不仅应该是非常具体的技术问题的资源,而且还应该是关于如何解决常见问题变化的一般指导原则。 “基于表单的网站身份验证” 应该是这种实验的一个很好的主题。
我们假设您已经知道如何构建登录 + 密码 HTML 表单,该表单将值 POST 到服务器端的脚本以进行身份验证。下面的部分将讨论声音实用 auth 的模式,以及如何避免最常见的安全陷阱。
HTTPS 还是 HTTPS?
除非连接已经安全(即通过 HTTPS 使用 SSL / TLS 进行隧道传输),否则您的登录表单值将以明文形式发送,这允许任何窃听浏览器和 Web 服务器之间的线路的人都可以在通过时读取登录信息通过。这种类型的窃听是由政府常规完成的,但总的来说,除了这样说之外,我们不会解决 “拥有” 的电话:如果您要保护任何重要信息,请使用 HTTPS。
实质上,在登录期间防止窃听 / 数据包嗅探的唯一实用方法是使用 HTTPS 或其他基于证书的加密方案(例如, TLS )或经过验证和测试的质询 - 响应方案(例如, Diffie-Hellman)基于 SRP)。窃听攻击者可以轻易绕过任何其他方法 。
当然,如果你愿意有点不切实际,你也可以采用某种形式的双因素认证方案(例如 Google Authenticator 应用程序,物理 '冷战风格' 代码簿或 RSA 密钥生成器加密狗)。如果应用正确,即使使用不安全的连接,这也可以工作,但是很难想象开发人员愿意实现双因素身份验证而不是 SSL。
(不)滚动自己的 JavaScript 加密 / 散列
鉴于在您的网站上设置 SSL 证书的非零成本和感知技术难度,一些开发人员倾向于推出他们自己的浏览器内哈希或加密方案,以避免通过不安全的线路传递明文登录。
虽然这是一个崇高的想法,但它基本上是无用的(并且可能是一个安全漏洞 ),除非它与上述之一相结合 - 也就是说,要么使用强加密来保护线路,要么使用久经考验的挑战 - 响应机制(如果你不知道那是什么,只要知道它是最难以证明的,最难设计的,也是最难实现的数字安全概念)。
虽然散列密码可以有效防止密码泄露 ,但它很容易受到重放攻击,中间人攻击 / 劫持(如果攻击者可以在你的不安全的 HTML 页面到达你的之前注入几个字节)浏览器,他们可以简单地注释掉 JavaScript 中的哈希),或者暴力攻击(因为你正在向攻击者发送用户名,盐和哈希密码)。
CAPTCHAS 反人类
CAPTCHAs旨在阻止一种特定类型的攻击:自动字典 / 暴力破解试验和错误,没有人类操作员。毫无疑问,这是一个真正的威胁,但有一些方法可以无缝地处理它,不需要 CAPTCHA,特别是正确设计的服务器端登录限制方案 - 我们稍后会讨论它们。
知道 CAPTCHA 实现不是一样的; 它们通常不是人类可以解决的,其中大多数实际上对机器人无效,所有这些都对廉价的第三世界劳动力无效(根据OWASP ,目前的血汗工厂率为每 500 次测试 12 美元),并且一些实施可能是在某些国家 / 地区技术上非法(请参阅OWASP 身份验证备忘单 )。如果您必须使用 CAPTCHA,请使用 Google 的reCAPTCHA ,因为它定义为 OCR-hard(因为它使用已经过 OCR 错误分类的书籍扫描)并且非常努力地使用户友好。
就个人而言,我倾向于发现 CAPTCHAS 很烦人,并且当用户多次登录失败并且限制延迟时间最大时,仅将其用作最后的手段。这种情况很少发生,可以接受,并且整个系统都得到了加强。
存储密码 / 验证登录
在我们近年来看到的所有高度公开的黑客攻击和用户数据泄漏之后,这可能最终是常识,但必须说明:不要在数据库中以明文形式存储密码。用户数据库通常会通过 SQL 注入被黑客入侵,泄露或收集,如果您要存储原始明文密码,那么即时游戏结束也是为了您的登录安全性。
因此,如果您无法存储密码,如何检查登录表单中登录的登录名 + 密码组合是否正确?答案是使用密钥派生函数进行散列。无论何时创建新用户或更改密码,您都会获取密码并通过 KDF 运行,例如 Argon2,bcrypt,scrypt 或 PBKDF2,将明文密码(“correcthorsebatterystaple”)转换为长而随机的字符串,这在您的数据库中存储更安全。要验证登录,请对输入的密码运行相同的哈希函数,这次传入 salt 并将生成的哈希字符串与存储在数据库中的值进行比较。 Argon2,bcrypt 和 scrypt 已经将哈希值与哈希一起存储。有关更多详细信息,请参阅 sec.stackexchange 上的这篇文章 。
使用 salt 的原因是因为散列本身是不够的 - 你需要添加一个所谓的'salt' 来保护散列不受彩虹表的影响 。 salt 有效地防止两个完全匹配的密码被存储为相同的哈希值,从而防止在攻击者正在执行密码猜测攻击时在一次运行中扫描整个数据库。
加密哈希不应该用于密码存储,因为用户选择的密码不够强(即通常不包含足够的熵),并且密码猜测攻击可以在相对较短的时间内由访问哈希的攻击者完成。这就是使用 KDF 的原因 - 这些有效地“拉伸关键”意味着攻击者所做的每个密码都会涉及多次迭代哈希算法,例如 10,000 次,这使得攻击者的密码猜测速度慢了 10,000 倍。
会话数据 - “您以 Spiderman69 身份登录”
一旦服务器针对您的用户数据库验证了登录名和密码并找到匹配项,系统就需要一种方法来记住浏览器已经过身份验证。这个事实应该只在服务器端存储在会话数据中。
如果您不熟悉会话数据,可以使用以下方法:单个随机生成的字符串存储在过期的 cookie 中,用于引用存储在服务器上的数据集合 - 会话数据。如果您使用的是 MVC 框架,那么这无疑已经得到了处理。
如果可能,请确保会话 cookie 在发送到浏览器时设置了安全且仅 HTTP 标记。 httponly 标志提供一些保护,防止 XSS 攻击读取 cookie。安全标志确保 cookie 仅通过 HTTPS 发回,因此可防止网络嗅探攻击。 cookie 的值不应该是可预测的。如果呈现引用不存在的会话的 cookie,则应立即替换其值以防止会话固定 。
持久登录 Cookie(“记住我” 功能)是一个危险区域; 一方面,当用户了解如何处理它们时,它们与传统登录完全一样安全; 而另一方面,它们在粗心用户手中是一个巨大的安全风险,他们可能在公共计算机上使用它们而忘记注销,并且可能不知道浏览器 cookie 是什么或如何删除它们。
就个人而言,我喜欢定期访问的网站的持久登录,但我知道如何安全地处理它们。如果您肯定您的用户知道相同的内容,您可以使用持久登录并保持良心。如果没有 - 那么,你可以赞同这样的理念:如果用户被登录,那么他们的登录凭证会不小心。这并不像我们去我们用户的房子,并且用他们在显示器边缘排列的密码撕下所有那些引人注目的便利贴。
当然,有些系统无法承受任何帐户入侵; 对于这样的系统,你无法证明持久登录的合理性。
如果您决定实施持久登录 cookie,请按以下步骤操作:
首先,花点时间阅读Paragon Initiative关于这个主题的文章 。你需要正确地获得一堆元素,而且这篇文章很好地解释了每一个元素。
并且只是为了重申一个最常见的陷阱, 不要在你的数据库中存储持久的登录 COOKIE(令牌) ,这只是它的一大堆!登录令牌是密码等效,因此如果攻击者抓住您的数据库,他们可以使用令牌登录任何帐户,就像它们是明文登录密码组合一样。因此,在存储持久登录令牌时,使用散列(根据https://security.stackexchange.com/a/63438/5002 ,弱散列将为此目的做好)。
不要实施 “秘密问题” 。 “秘密问题” 功能是一种安全反模式。阅读 MUST-READ 列表中链接号为 4 的论文。在雅虎之后你可以向萨拉佩林询问这个问题。电子邮件帐户在之前的总统竞选期间被黑了,因为她的安全问题的答案是......“Wasilla High School”!
即使有用户指定的问题,大多数用户很可能会选择:
一个 '标准' 的秘密问题,如母亲的娘家姓或宠物
一个简单的琐事,任何人都可以从他们的博客,LinkedIn 个人资料或类似的东西
任何比猜测密码更容易回答的问题。对于任何体面的密码,你可以想象的每一个问题
总之,安全问题本质上在所有形式和变体中都是不安全的,不应出于任何原因在认证方案中使用。
安全问题甚至存在于野外的真正原因是它们可以方便地节省来自无法访问其电子邮件以获得重新激活代码的用户的一些支持呼叫的成本。这是以牺牲安全性和 Sarah Palin 的声誉为代价的。值得?可能不是。
我已经提到过为什么你不应该使用安全问题来处理忘记 / 丢失的用户密码; 不言而喻,您绝不应该向用户发送实际密码。在这个领域至少还有两个常见的陷阱需要避免:
不要将忘记的密码重置为自动生成的强密码 - 这种密码很难记住,这意味着用户必须更改密码或将其写下来 - 例如,在显示器边缘的亮黄色 Post-It 上。不要设置新密码,只需让用户立即选择一个新密码 - 这也是他们想要做的事情。 (例外情况可能是用户普遍使用密码管理器来存储 / 管理通常无法记住的密码而不将其写下来)。
始终在数据库中散列丢失的密码代码 / 令牌。 再次 ,此代码是密码等效的另一个示例,因此必须进行哈希处理,以防攻击者抓住您的数据库。当请求丢失密码代码时,将明文代码发送到用户的电子邮件地址,然后哈希,将哈希值保存在数据库中 - 然后丢弃原始密码。就像密码或持久登录令牌一样。
最后一点:始终确保用于输入 “丢失的密码代码” 的界面至少与登录表单本身一样安全,或者攻击者只是使用它来获取访问权限。确保生成很长的 “丢失密码代码”(例如,16 个区分大小写的字母数字字符)是一个良好的开端,但请考虑添加与登录表单本身相同的限制方案。
首先,您需要阅读这篇小文章进行实际检查: 500 个最常用的密码
好的,所以这个列表可能不是任何 地方 任何系统上最常见密码的规范列表,但它很好地表明,当没有强制执行的策略时,人们会选择密码。此外,当您将该列表与最近被盗密码的公开分析进行比较时,该列表看起来非常贴近家庭。
因此:没有最低密码强度要求,2%的用户使用前 20 个最常用的密码之一。含义:如果攻击者只获得 20 次尝试,则您网站上 50 个帐户中的 1 个将被破解。
阻止这一点需要计算密码的熵,然后应用阈值。美国国家标准与技术研究院(NIST) 特刊 800-63提出了一系列非常好的建议。当与字典和键盘布局分析相结合时(例如,'qwertyuiop' 是一个错误的密码),可以在 18 位熵的水平上拒绝 99%的所有选择不当的密码 。简单地计算密码强度并向用户显示视觉强度计是好的,但不够。除非强制执行,否则很多用户很可能会忽略它。
为了更好地了解高熵密码的用户友好性,强烈建议使用 Randall Munroe 的密码强度 xkcd 。
首先,看一下数字: 密码恢复速度 - 密码会持续多长时间
如果您没有时间浏览该链接中的表,请参阅以下列表:
即使您使用算盘破解密码,也几乎没时间破解弱密码
如果它不区分大小写 ,则几乎没有时间破解字母数字 9 个字符的密码
如果长度小于 8 个字符 ,则几乎没有时间破解复杂的符号和字母和数字大小写密码(台式 PC 可以搜索整个密钥空间,最多 7 个字符)几天甚至几小时的事情)
但是, 如果每秒限制一次尝试 , 那么即使是 6 个字符的密码,也需要花费大量的时间来破解!
那么我们可以从这些数字中学到什么呢?好吧,很多,但我们可以专注于最重要的部分:防止大量快速连续登录尝试(即暴力攻击)的事实并不那么困难。但是正确地防止它并不像看起来那么容易。
一般来说,您有三种选择可以有效抵御暴力攻击(和字典攻击,但由于您已经采用强密码策略,它们应该不是问题) :
在 N 次尝试失败后出现了一个CAPTCHA (令人讨厌并经常无效 - 但我在这里重复自己)
在 N 次尝试失败后锁定帐户并要求电子邮件验证(这是等待发生的DoS攻击)
最后, 登录限制 :也就是说,在 N 次失败尝试之后设置尝试之间的时间延迟(是的,仍然可以进行 DoS 攻击,但至少它们的可能性要小得多,而且更难以完成)。
最佳实践#1:短暂的时间延迟随着尝试失败的次数而增加,例如:
DoS 攻击这个方案是非常不切实际的,因为结果锁定时间略大于先前锁定时间的总和。
澄清一下:在将响应返回给浏览器之前,延迟不是延迟。它更像是超时或不应期,在此期间,根本不会接受或评估特定帐户或特定 IP 地址的登录尝试。也就是说,正确的凭据不会在成功登录时返回,并且不正确的凭据不会触发延迟增加。
最佳实践#2: N 次尝试失败后生效的中等时间延迟,如:
DoS 攻击这个计划是不切实际的,但肯定是可行的。此外,可能需要注意的是,如此长的延迟对于合法用户来说可能非常烦人。忘记用户会不喜欢你。
最佳实践#3:结合两种方法 - 在 N 次尝试失败后生效的固定短时间延迟,例如:
或者,具有固定上限的增加延迟,例如:
这个最终方案取自 OWASP 最佳实践建议(来自 MUST-READ 列表的链接 1),并且应该被认为是最佳实践,即使它是公认的限制性方面。
但是,作为经验法则,我会说:您的密码策略越强,您就越不会因延迟而误导用户。如果您需要强大的(区分大小写的字母数字 + 所需的数字和符号)9 + 字符密码,您可以在激活限制之前为用户提供 2-4 次非延迟密码尝试。
DoS 攻击这个最终登录限制方案将是非常不切实际的。作为最后的触摸,始终允许持久(cookie)登录(和 / 或验证 CAPTCHA 的登录表单)通过,因此合法用户甚至不会在攻击进行中被延迟。这样,非常不切实际的 DoS 攻击变成了一种非常不切实际的攻击。
此外,对管理员帐户进行更积极的限制是有意义的,因为这些是最具吸引力的切入点
同样,更高级的攻击者会试图通过 “传播他们的活动” 来规避登录限制:
在僵尸网络上分发尝试以防止 IP 地址标记
他们不会选择一个用户并尝试使用 50.000 最常用的密码(由于我们的限制而无法使用),他们会选择最常用的密码,然后针对 50.000 用户进行尝试。这样,他们不仅可以实现 CAPTCHA 和登录限制等最大限度尝试措施,而且成功的机会也会增加,因为最常见的 1 号密码比 49.995 更容易发生。
将每个用户帐户的登录请求间隔为 30 秒,以便潜入雷达
在这里,最佳做法是记录系统范围内的失败登录次数 ,并使用站点的错误登录频率的运行平均值作为您对所有用户施加的上限的基础。
太抽象了?让我重新说一下:
假设您的网站在过去 3 个月内平均每天有 120 次不良登录。使用它(运行平均值),您的系统可能将全局限制设置为 3 倍 - 即。在 24 小时内 360 尝试失败。然后,如果所有帐户中的失败尝试总数在一天内超过该数量(甚至更好,监控加速率并触发计算的阈值),则会激活系统范围的登录限制 - 这意味着所有用户的短暂延迟(仍然,cookie 登录和 / 或备份 CAPTCHA 登录除外)。
我还发布了一个更详细的问题和一个非常好的讨论,讨论如何避免棘手的 pitfals抵御分布式暴力攻击
凭据可能会受到损害,无论是利用漏洞,密码被记录下来还是丢失,密钥被盗的笔记本电脑,还是用户登录钓鱼网站。可以使用双因素身份验证进一步保护登录,该身份验证使用带外因素,例如通过电话,SMS 消息,应用程序或加密狗接收的一次性代码。一些提供商提供双因素身份验证服务。
身份验证可以完全委托给单点登录服务,其中另一个提供程序处理收集凭据。这会将问题推送给受信任的第三方。谷歌和 Twitter 都提供基于标准的 SSO 服务,而 Facebook 则提供类似的专有解决方案。
100%安全地发送凭证的唯一实用方法是使用SSL 。使用 JavaScript 来散列密码是不安全的。客户端密码散列的常见缺陷:
还有另一种称为SRP的安全方法,但它已获得专利(尽管它是免费许可的 )并且几乎没有可用的良好实现。
不要将密码作为纯文本存储在数据库中。即使您不关心自己网站的安全性也不会。假设您的某些用户将重复使用其在线银行帐户的密码。因此,存储哈希密码,并丢弃原始密码。并确保密码不会显示在访问日志或应用程序日志中。 OWASP 建议使用 Argon2作为新应用的首选。如果不可用,则应使用 PBKDF2 或 scrypt。最后,如果以上都不可用,请使用 bcrypt。
哈希本身也是不安全的。例如,相同的密码意味着相同的哈希 - 这使得哈希查找表成为一次破解大量密码的有效方法。相反,存储盐渍哈希。 salt 是在散列之前附加到密码的字符串 - 每个用户使用不同的(随机)salt。 salt 是一个公共值,因此您可以将它们与哈希存储在数据库中。有关详细信息,请参阅此处 。
这意味着您无法向用户发送他们忘记的密码(因为您只有哈希)。除非您已对用户进行身份验证,否则请勿重置用户的密码(用户必须证明他们能够阅读发送到存储(和验证的)电子邮件地址的电子邮件。)
安全问题是不安全的 - 避免使用它们。为什么?安全问题,密码做得更好。阅读第三部分:在@Jens Roland 中 使用秘密问题在这个维基中回答这里。
用户登录后,服务器会向用户发送会话 cookie。服务器可以从 cookie 中检索用户名或 id,但是没有其他人可以生成这样的 cookie(TODO 解释机制)。
Cookie 可能被劫持 :它们只能与客户机器的其他部分和其他通信一样安全。它们可以从磁盘中读取,在网络流量中嗅探,由跨站点脚本攻击解除,从中毒的 DNS 中删除,以便客户端将其 cookie 发送到错误的服务器。不要发送持久性 cookie。 Cookie 应在客户端会话结束时过期(浏览器关闭或离开您的域)。
如果要自动登录用户,可以设置持久性 cookie,但它应与完整会话 cookie 不同。您可以设置用户已自动登录的附加标志,并且需要为真正的敏感操作登录。这对于希望为您提供无缝,个性化购物体验但仍保护您的财务详细信息的购物网站而言非常受欢迎。例如,当您返回访问亚马逊时,他们会向您显示一个看起来像您已登录的页面,但当您下订单(或更改您的送货地址,信用卡等)时,他们会要求您确认你的密码。
另一方面,诸如银行和信用卡之类的金融网站仅具有敏感数据,并且不应允许自动登录或低安全性模式。
首先,一个强烈的警告,这个答案不是最适合这个问题的答案。绝对不应该是最好的答案!
我将继续提及 Mozilla 提出的BrowserID (或者更确切地说,可能是已验证的电子邮件协议 ),其目的是寻找未来更好的身份验证方法的升级途径。
我会这样总结一下:
@
domain” 形式简洁明了,并受到各种协议和 URI 方案的支持。当然,这种标识符最普遍地被认为是电子邮件地址。 这不是严格的 “基于表单的网站身份验证”。但是,这是从当前基于表单的身份验证规范过渡到更安全的东西:浏览器支持的身份验证。