第一部分:如何登录
我们假设您已经知道如何构建一个登录+密码 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 错误分类的图书扫描)并且非常努力地使用户友好。
就我个人而言,我倾向于发现 CAPTCHAS 很烦人,并且仅在用户多次登录失败并且限制延迟被最大化时才使用它们作为最后的手段。这种情况很少会发生,以至于无法接受,它会加强整个系统。
存储密码/验证登录
在我们近年来看到的所有高度公开的黑客攻击和用户数据泄漏之后,这可能最终成为常识,但必须说:不要在数据库中以明文形式存储密码。用户数据库通常会通过 SQL 注入被黑客入侵、泄露或收集,如果您存储的是原始的明文密码,那么为了您的登录安全,这就是即时游戏结束。
因此,如果您无法存储密码,如何检查从登录表单发布的登录名+密码组合是否正确?答案是使用密钥派生函数进行散列。每当创建新用户或更改密码时,您都会获取密码并通过 KDF 运行它,例如 Argon2、bcrypt、scrypt 或 PBKDF2,将明文密码(“correcthorsebatterystaple”)变成一个长的、看起来随机的字符串,存储在数据库中要安全得多。要验证登录,您对输入的密码运行相同的哈希函数,这次传入盐并将生成的哈希字符串与存储在数据库中的值进行比较。Argon2、bcrypt 和 scrypt 已经用哈希值存储了盐。查看 sec.stackexchange 上的这篇文章以获取更多详细信息。
使用盐的原因是散列本身是不够的——你需要添加一个所谓的“盐”来保护散列不受彩虹表的影响。盐可以有效地防止两个完全匹配的密码被存储为相同的哈希值,如果攻击者正在执行密码猜测攻击,可以防止一次扫描整个数据库。
密码散列不应该用于密码存储,因为用户选择的密码不够强大(即通常不包含足够的熵),并且可以通过访问散列的攻击者在相对较短的时间内完成密码猜测攻击。这就是使用 KDF 的原因 - 这些有效地“拉伸密钥”,这意味着攻击者每次猜测密码都会导致哈希算法重复多次,例如 10,000 次,这导致攻击者猜测密码的速度要慢 10,000 倍。
会话数据 - “您以 Spiderman69 身份登录”
一旦服务器根据您的用户数据库验证了登录名和密码并找到匹配项,系统需要一种方法来记住浏览器已经过身份验证。这个事实应该只存储在服务器端的会话数据中。
如果您不熟悉会话数据,以下是它的工作原理:一个随机生成的字符串存储在即将到期的 cookie 中,并用于引用存储在服务器上的数据集合 - 会话数据。如果您使用的是 MVC 框架,那么这无疑已经处理好了。
如果可能,请确保会话 cookie 在发送到浏览器时设置了安全和仅 HTTP 标志。HttpOnly 标志对通过 XSS 攻击读取的 cookie 提供了一些保护。安全标志确保 cookie 仅通过 HTTPS 发送回来,因此可以防止网络嗅探攻击。cookie 的值不应是可预测的。在出现引用不存在会话的 cookie 时,应立即替换其值以防止会话固定。
会话状态也可以在客户端维护。这是通过使用 JWT(JSON Web 令牌)等技术实现的。
第二部分:如何保持登录状态——臭名昭著的“记住我”复选框
持久登录 Cookie(“记住我”功能)是一个危险区域;一方面,当用户了解如何处理它们时,它们完全与传统登录一样安全;另一方面,对于粗心的用户来说,它们是一个巨大的安全风险,他们可能会在公共计算机上使用它们而忘记注销,并且可能不知道浏览器 cookie 是什么或如何删除它们。
就个人而言,我喜欢对我定期访问的网站进行持久登录,但我知道如何安全地处理它们。如果您确信您的用户知道相同的内容,您可以使用无愧于心的持久登录。如果不是 - 好吧,那么您可能会同意这样一种哲学,即粗心的用户登录凭据如果被黑客入侵,就会自找麻烦。我们也不会去用户家中撕下所有那些用他们在显示器边缘排列的密码的便签纸。
当然,有些系统承受不起任何帐户被黑的后果;对于此类系统,您无法证明拥有持久登录是合理的。
如果您决定实施持久登录 cookie,您可以这样做:
首先,花点时间阅读Paragon Initiative关于该主题的文章。您需要正确掌握一堆元素,并且这篇文章很好地解释了每个元素。
重申一下最常见的陷阱之一,不要将持久登录 COOKIE(令牌)存储在您的数据库中,只存储它的哈希值!登录令牌是密码等效的,因此如果攻击者掌握了您的数据库,他们可以使用令牌登录到任何帐户,就像它们是明文登录密码组合一样。因此,在存储持久登录令牌时,使用散列(根据https://security.stackexchange.com/a/63438/5002,弱散列可以很好地实现此目的)。
第三部分:使用秘密问题
不要实施“秘密问题”。“秘密问题”功能是一种安全反模式。从必读列表中的链接号 4 中阅读论文。你可以问莎拉佩林关于那个,在她的雅虎之后!电子邮件帐户在之前的总统竞选期间被黑了,因为她的安全问题的答案是……“瓦西拉高中”!
即使有用户指定的问题,大多数用户也很可能会选择:
一个“标准”的秘密问题,比如母亲的娘家姓或最喜欢的宠物
任何人都可以从他们的博客、LinkedIn 个人资料或类似内容中提取的一个简单的琐事
任何比猜密码更容易回答的问题。对于任何体面的密码,这是您可以想象的每一个问题
总之,安全问题在几乎所有形式和变体中本质上都是不安全的,并且不应该出于任何原因在身份验证方案中使用。
安全问题在野外存在的真正原因是,它们方便地节省了一些无法访问其电子邮件以获取重新激活代码的用户的支持电话的成本。这是以牺牲安全和莎拉佩林的声誉为代价的。值得?可能不是。
第 IV 部分:忘记密码功能
我已经提到了为什么你不应该使用安全问题来处理忘记/丢失的用户密码;不言而喻,您永远不应该通过电子邮件向用户发送他们的实际密码。在这个领域至少还有两个非常常见的陷阱需要避免:
不要将忘记的密码重置为自动生成的强密码 - 众所周知,这样的密码很难记住,这意味着用户必须更改或写下来 - 例如,在显示器边缘的亮黄色便利贴上。与其设置新密码,不如让用户立即选择一个新密码——这就是他们想要做的事情。(如果用户普遍使用密码管理器来存储/管理通常不写下来就无法记住的密码,则可能是一个例外)。
始终对数据库中丢失的密码代码/令牌进行哈希处理。再次,此代码是密码等效项的另一个示例,因此必须对其进行哈希处理,以防攻击者获得您的数据库。当请求丢失密码代码时,将明文代码发送到用户的电子邮件地址,然后对其进行哈希处理,将哈希值保存在您的数据库中 - 并丢弃原始. 就像密码或持久登录令牌一样。
最后一点:始终确保您输入“丢失的密码代码”的界面至少与您的登录表单本身一样安全,否则攻击者会简单地使用它来获取访问权限。确保生成很长的“丢失密码”(例如,16 个区分大小写的字母数字字符)是一个好的开始,但请考虑添加与登录表单本身相同的限制方案。
第五部分:检查密码强度
首先,您需要阅读这篇小文章进行现实检查:500 个最常见的密码
好的,所以也许该列表不是任何地方任何系统上最常见密码的规范列表,但它很好地表明了当没有实施强制策略时人们将如何选择他们的密码。此外,当您将其与对最近被盗密码的公开分析进行比较时,该列表看起来非常接近家常便饭。
所以:没有最低密码强度要求,2% 的用户使用前 20 个最常见的密码之一。含义:如果攻击者仅获得 20 次尝试,那么您网站上 50 个帐户中就有 1 个是可破解的。
阻止这一点需要计算密码的熵,然后应用阈值。美国国家标准与技术研究院 (NIST)特别出版物 800-63有一组非常好的建议。结合字典和键盘布局分析(例如,'qwertyuiop' 是一个错误的密码),可以拒绝 99%的 18 位熵级别的所有错误选择的密码。简单地计算密码强度并向用户显示视觉强度计是好的,但还不够。除非强制执行,否则很多用户很可能会忽略它。
对于高熵密码的用户友好性,强烈推荐Randall Munroe 的密码强度 xkcd 。
利用 Troy Hunt 的Have I Been Pwned API来检查用户密码与公共数据泄露中泄露的密码。
第 VI 部分:更多 - 或:防止快速登录尝试
首先,看一下数字:密码恢复速度 - 您的密码可以保存多长时间
如果您没有时间浏览该链接中的表格,以下是它们的列表:
破解一个弱密码几乎不需要时间,即使你是用算盘破解它
如果不区分大小写,则几乎不需要时间来破解字母数字 9 字符的密码
如果长度小于 8个字符(台式电脑一次最多可以搜索整个键空间 7 个字符),破解复杂的符号、字母和数字、大小写密码几乎不需要任何时间数天甚至数小时)
但是,如果您被限制为每秒尝试一次,即使是 6 个字符的密码也将花费大量时间!
那么我们可以从这些数字中学到什么?好吧,很多,但我们可以专注于最重要的部分:防止大量快速连续登录尝试(即暴力攻击)确实并不难。但预防它并不像看起来那么容易。
一般来说,您有三种选择都可以有效抵抗暴力攻击(和字典攻击,但由于您已经采用了强密码策略,因此它们不应该成为问题):
在 N 次尝试失败后显示验证码(非常烦人,而且通常无效——但我在这里重复自己)
在 N 次尝试失败后锁定帐户并要求电子邮件验证(这是等待发生的DoS攻击)
最后,登录限制:也就是说,在 N 次尝试失败后设置尝试之间的时间延迟(是的,DoS 攻击仍然可能,但至少它们的可能性要小得多,而且实施起来要复杂得多)。
最佳实践#1:随着失败尝试次数的增加而增加的短时间延迟,例如:
- 1 次尝试失败 = 无延迟
- 2 次失败尝试 = 2 秒延迟
- 3 次失败尝试 = 4 秒延迟
- 4 次失败尝试 = 8 秒延迟
- 5 次失败尝试 = 16 秒延迟
- 等等
DoS 攻击这种方案是非常不切实际的,因为由此产生的锁定时间略大于先前锁定时间的总和。
澄清一下:延迟不是将响应返回给浏览器之前的延迟。它更像是一个超时或不应期,在此期间,对特定帐户或从特定 IP 地址的登录尝试根本不会被接受或评估。也就是说,正确的凭据不会在成功登录时返回,不正确的凭据不会触发延迟增加。
最佳实践 #2:中等长度的时间延迟,在 N 次尝试失败后生效,例如:
- 1-4 次失败尝试 = 无延迟
- 5 次失败尝试 = 15-30 分钟延迟
DoS 攻击这种方案是非常不切实际的,但肯定是可行的。此外,可能需要注意的是,如此长的延迟对于合法用户来说可能非常烦人。健忘的用户会不喜欢你。
最佳实践#3:结合这两种方法 - 在 N 次尝试失败后生效的固定、短时间延迟,例如:
- 1-4 次失败尝试 = 无延迟
- 5 次以上失败尝试 = 20 秒延迟
或者,具有固定上限的延迟增加,例如:
- 1 次尝试失败 = 5 秒延迟
- 2 次失败尝试 = 15 秒延迟
- 3 次以上失败尝试 = 45 秒延迟
这个最终方案取自 OWASP 最佳实践建议(必读列表中的链接 1),并且应该被视为最佳实践,即使它被承认在限制方面。
但是,根据经验,我会说:您的密码策略越强,您就越不会因延迟而困扰用户。如果您需要强(区分大小写的字母数字 + 所需的数字和符号)9+ 个字符的密码,您可以在激活限制之前为用户提供 2-4 次非延迟密码尝试。
DoS 攻击这个最终的登录限制方案是非常不切实际的。最后一点,始终允许持久性 (cookie) 登录(和/或 CAPTCHA 验证的登录表单)通过,因此合法用户甚至不会在攻击进行时被延迟。这样,非常不切实际的 DoS 攻击就变成了非常不切实际的攻击。
此外,对管理员帐户进行更积极的限制是有意义的,因为这些是最有吸引力的入口点
第七部分:分布式蛮力攻击
顺便说一句,更高级的攻击者会尝试通过“传播他们的活动”来规避登录限制:
在这里,最佳实践是记录系统范围内的失败登录次数,并使用站点的错误登录频率的运行平均值作为上限,然后对所有用户施加上限。
太抽象了?让我改写一下:
假设您的网站在过去 3 个月内平均每天有 120 次错误登录。使用它(运行平均值),您的系统可能会将全局限制设置为 3 倍——即。24 小时内 360 次失败的尝试。然后,如果在一天内所有帐户的失败尝试总数超过了该数字(或者甚至更好,监控加速率并在计算的阈值上触发),它会激活系统范围的登录限制——这意味着所有用户的短暂延迟(仍然,cookie 登录和/或备份 CAPTCHA 登录除外)。
我还发布了一个问题,其中包含更多详细信息以及关于如何避免棘手的陷阱以抵御分布式暴力攻击的非常好的讨论
第 VIII 部分:双因素身份验证和身份验证提供程序
凭据可能会受到破坏,无论是通过漏洞利用、密码被写下和丢失、带有密钥的笔记本电脑被盗,还是用户登录到钓鱼网站。登录可以通过双重身份验证得到进一步保护,该身份验证使用带外因素,例如从电话、SMS 消息、应用程序或加密狗收到的一次性代码。一些提供商提供双因素身份验证服务。
身份验证可以完全委托给单点登录服务,由另一个提供者处理收集凭据。这会将问题推给受信任的第三方。Google 和 Twitter 都提供基于标准的 SSO 服务,而 Facebook 提供类似的专有解决方案。
关于 Web 身份验证的必读链接
- OWASP 认证指南/ OWASP 认证备忘单
- Web 上客户端身份验证的注意事项(非常易读的 MIT 研究论文)
- 维基百科:HTTP cookie
- 后备认证的个人知识问题:Facebook时代的安全问题(非常可读的伯克利研究论文)