基于表单的网站身份验证的权威指南
网站的基于表单的身份验证它应包括以下主题:如何登录如何注销如何保持登录状态管理cookie(包括推荐设置)SSL / HTTPS加密如何存储密码使用机密问题忘记用户名/密码功能使用防止跨站点请求伪造(CSRF)OpenID的集合"记住我"复选框浏览器用户名和密码的自动完成秘密URL(受摘要保护的公共URL)检查密码强度电子邮件验证以及有关基于表单的身份验证的更多信息...它不应该包括以下内容:角色和授权HTTP基本身份验证请通过以下方式为我们提供帮助:建议子主题提交有关此主题的优质文章编辑官方答案权威性文章发送凭据如果客户端和服务器之间的连接未加密,则我们所做的一切都会受到攻击中间人攻击。攻击者可以替换传入的JavaScript来破坏哈希或者将所有凭据发送到其服务器,他们可以侦听客户端的响应并完美地模拟用户,等等。等等。具有受信任证书颁发机构的SSL旨在防止MitM攻击。如果我们不对服务器执行其他多余的工作,则服务器收到的哈希密码不太安全。存储密码安全性问题会话cookie外部资源列表网上客户端身份验证的注意事项(PDF)21页的学术文章,其中包含许多重要技巧。询问YC:关于该主题的用户身份验证最佳实践论坛讨论我们可能不正确地存储密码关于存储密码的介绍性文章讨论:编码恐慌:我们可能不正确地存储密码关于论坛的有关恐怖的讨论。切勿将密码存储在数据库中!关于在数据库中存储密码的另一条警告。密码破解Wikipedia文章介绍了几种密码哈希方案的弱点。足够使用Rainbow表:关于安全密码方案我们需要了解的内容关于Rainbow表以及如何防御它们以及其他线程的讨论。包括广泛的讨论。第一部分:如何登录
我们认为,StackOverflow不仅应作为解决非常具体的技术问题的资源,而且还应作为解决常见问题的通用准则。 "基于表单的网站身份验证"应该是此类实验的一个不错的主题。
第II部分:如何保持登录状态-臭名昭著的"记住我"复选框第III部分:使用机密问题第IV部分:忘记了密码功能第V部分:检查密码强度第VI部分:更多-或者:防止快速登录失败1尝试=无延迟2次失败尝试= 2秒延迟3次失败尝试= 4秒延迟4次失败尝试= 8秒延迟5次失败尝试= 16秒延迟等
解决方案:
1-4次失败尝试=无延迟5次失败尝试= 15-30分钟延迟1-4次失败尝试=无延迟5次失败尝试+ 20秒延迟1次失败尝试= 5秒延迟2次失败尝试= 15秒延迟3+次失败尝试= 45秒延迟
唯一安全地发送凭据的实用方法是使用SSL。使用JavaScript哈希密码是不安全的。客户端密码哈希的常见陷阱:
第七部分:分布式蛮力攻击第八部分:两方面身份验证和身份验证提供者关于Web身份验证的必读链接OWASP身份验证/ OWASP指南速查表Web上客户端身份验证的注意事项(非常可读的MIT研究论文)维基百科:HTTP cookie用于回退身份验证的个人知识问题:Facebook时代的安全性问题(非常可读的伯克利研究论文)
还有另一种称为SRP的安全方法,但该方法已获得专利(尽管它是免费许可的),并且几乎没有好的实现方法。
{分块}
永远不要将密码以明文形式存储在数据库中。即使我们不在乎自己网站的安全性,也不会这样做。假设某些用户将重复使用其在线银行帐户的密码。因此,存储散列密码,然后丢弃原始密码。并确保密码未显示在访问日志或者应用程序日志中。 OWASP建议将Argon2用作新应用程序的首选。如果不可用,则应改用PBKDF2或者scrypt。最后,如果以上都不可用,请使用bcrypt。
散列本身也不安全。例如,相同的密码意味着相同的哈希值-这使哈希查找表成为一次破解大量密码的有效方法。而是存储加盐的哈希。盐是散列前添加在密码上的字符串,每位用户使用不同的(随机)盐。 salt是一个公共值,因此我们可以将它们与哈希一起存储在数据库中。有关更多信息,请参见此处。
这意味着我们不能向用户发送他们忘记的密码(因为我们只有哈希)。除非我们对用户进行了身份验证,否则请不要重置用户密码(用户必须证明他们能够阅读发送到已存储(并经过验证)的电子邮件地址的电子邮件。)
{分块}
安全问题是不安全的,请避免使用它们。为什么?任何安全问题都可以解决,密码更好。阅读@Jens Roland中的第III部分:使用秘密问题,在此Wiki中回答。
{分块}
用户登录后,服务器将向用户发送会话cookie。服务器可以从cookie中检索用户名或者id,但是没有其他人可以生成这样的cookie(TODO解释机制)。
Cookie可以被劫持:它们仅与客户端计算机其余部分和其他通信一样安全。可以从磁盘读取它们,嗅探网络流量,通过跨站点脚本攻击解除攻击,从有毒的DNS钓鱼,因此客户端将cookie发送到错误的服务器。不要发送永久性cookie。 Cookies应该在客户端会话结束时关闭(浏览器关闭或者离开域)。
如果我们想自动登录用户,则可以设置一个持久性cookie,但是它应该与完整会话的cookie不同。我们可以设置用户已经自动登录并且需要真正登录才能进行敏感操作的其他标志。这在想要为我们提供无缝的,个性化的购物体验但仍保护财务详细信息的购物网站中很受欢迎。例如,当我们再次访问亚马逊时,他们会向我们显示一个看起来像我们已登录的页面,但是当我们下订单(或者更改送货地址,信用卡等)时,他们会要求我们确认你的密码。
另一方面,诸如银行和信用卡之类的金融网站仅具有敏感数据,不应允许自动登录或者低安全性模式。
{分块}
我们假设我们已经知道如何构建一个登录名+密码HTML表单,该表单将值发布到服务器端的脚本上以进行身份验证。以下各节将介绍合理的实用身份验证模式,以及如何避免最常见的安全隐患。
要HTTPS还是不要HTTPS?
除非连接已经很安全(即使用SSL / TLS通过HTTPS进行隧道传输),否则登录表单值将以明文形式发送,这使任何窃听浏览器和Web服务器之间链接的人都可以在通过时读取登录信息通过。此类窃听通常由政府执行,但总的来说,我们不会说"拥有"的窃听器:除非我们要保护任何重要内容,否则请使用HTTPS。
本质上,防止登录期间窃听/数据包嗅探的唯一实用方法是使用HTTPS或者其他基于证书的加密方案(例如TLS)或者经过验证的测试挑战响应方案(例如Diffie-Hellman)的SRP)。窃听攻击者可以轻松地绕过任何其他方法。
当然,如果我们愿意采取一些不切实际的做法,则还可以采用某种形式的两因素身份验证方案(例如Google Authenticator应用,物理"冷战风格"密码本或者RSA密钥生成器加密狗)。如果正确应用,即使在不安全的连接下也可以使用,但是很难想象开发人员愿意实现两因素身份验证,而不愿意实现SSL。
(请勿)自行编写JavaScript加密/哈希
考虑到非零成本以及在网站上设置SSL证书的技术难度,一些开发人员倾向于使用自己的浏览器内哈希或者加密方案,以避免在不安全的网上传递明文登录信息。
尽管这是一种高尚的思想,但除非与上述方法之一结合使用,即通过强加密保护线路或者使用经过实践检验的质询-响应机制,否则它实际上是没有用的(并且可能是安全缺陷)。 (如果我们不知道这是什么,只需知道它是数字安全中最难证明,最难设计和最难实现的概念之一)。
散列密码确实可以有效地防止密码泄露,但它很容易受到重放攻击,中间人攻击/劫持(如果攻击者可以在未受保护的HTML页面中注入一些字节,然后再到达网站)浏览器,他们可以简单地注释掉JavaScript中的哈希)或者蛮力攻击(因为我们同时向攻击者提供了用户名,盐和哈希密码)。
CAPTCHAS反人类
验证码旨在阻止一类特定的攻击:无需人工操作的自动词典/蛮力试错法。毫无疑问,这是一个真正的威胁,但是有一些无缝处理的方法,它们不需要验证码(经过专门设计的服务器端登录限制方案,我们将在后面讨论)。
知道CAPTCHA的实现不是完全一样的。它们通常不是人类可解决的,大多数实际上对机器人无效,对廉价的第三世界劳动力而言它们都无效(根据OWASP,目前的血汗工厂收费标准是每500个测试12美元),某些实现可能是在某些国家/地区在技术上是非法的(请参阅OWASP身份验证备忘单)。如果我们必须使用CAPTCHA,请使用Google的reCAPTCHA,因为从定义上讲它是OCR硬的(因为它使用了已经被OCR错误分类的书本扫描),并且会尽力使用户友好。
就个人而言,我倾向于发现CAPTCHAS很烦人,并且仅在用户多次登录失败并且限制延迟最大化时才将其用作最后的手段。这种情况很少发生,难以接受,并且会增强整个系统。
存储密码/验证登录名
在近年来发生的所有广为人知的黑客攻击和用户数据泄漏之后,这可能最终成为常识,但必须指出:不要在数据库中以明文形式存储密码。用户数据库通常会通过SQL注入被黑客入侵,泄漏或者收集,如果我们存储的是原始的纯文本密码,那么这将为登录安全提供即时的## 解决方案。
因此,如果我们无法存储密码,如何检查从登录表单发布的登录名和密码组合是否正确?答案是使用密钥推导函数进行哈希处理。每当创建新用户或者更改密码时,我们都将获取密码并通过KDF(例如Argon2,bcrypt,scrypt或者PBKDF2)运行它,将明文密码(" correcthorsebatterystaple")变成一个长的,随机的字符串,这样可以更安全地存储在数据库中。为了验证登录名,我们对输入的密码运行相同的哈希函数,这次传入了salt并将结果哈希字符串与数据库中存储的值进行比较。 Argon2,bcrypt和scrypt已经将盐与哈希一起存储了。请查看sec.stackexchange上的这篇文章,以获取更多详细信息。
使用盐的原因是散列本身还不够-我们需要添加一个所谓的"盐"来保护散列免受彩虹表的侵害。 Salt可以有效地防止将两个完全匹配的密码存储为相同的哈希值,从而防止在攻击者执行密码猜测攻击时一次扫描整个数据库。
密码哈希不能用于密码存储,因为用户选择的密码不够强(即通常不包含足够的熵),并且攻击者可以在较短时间内完成对密码的猜测攻击。这就是为什么要使用KDF来有效地"拉伸密钥"的原因,这意味着攻击者进行的每个密码猜测都涉及多次重复哈希算法,例如10,000次,从而使攻击者的密码猜测速度慢10,000倍。
会话数据"我们以Spiderman69登录"
服务器针对用户数据库验证了登录名和密码并找到匹配项后,系统需要一种方式来记住浏览器已通过身份验证。这个事实只应该存储在会话数据的服务器端。
If you are unfamiliar with session data, here's how it works: A single randomly-generated string is stored in an expiring cookie and used to reference a collection of data - the session data - which is stored on the server. If you are using an MVC framework, this is undoubtedly handled already.
如果有可能,请确保会话cookie在发送到浏览器时已设置了安全和HTTP Only标志。 httponly标志为XSS攻击读取cookie提供了一些保护。安全标志可确保Cookie仅通过HTTPS发送回,因此可以防止网络嗅探攻击。 cookie的值不应是可预测的。如果提供了引用不存在的会话的cookie,则应立即替换其值以防止会话固定。
{分块}
永久登录Cookies("记住我"功能)是一个危险区域。一方面,当用户了解如何处理它们时,它们与传统登录完全一样安全;另一方面,粗心的用户则面临巨大的安全风险,他们可能会在公用计算机上使用它们,却忘记了注销,并且可能不知道浏览器cookie是什么或者如何删除它们。
就个人而言,我喜欢经常访问的网站的永久登录,但是我知道如何安全地处理它们。如果我们确定用户知道相同的信息,则可以凭良心使用持久性登录。如果不是很好,那么我们可能会赞同这样一种哲学,即粗心的登录凭据的用户会在被黑客入侵时将其带到自己身上。这也不像我们去用户家并撕下所有由facepalm引起的Post-It便条一样,它们的密码也都已排列在显示器的边缘。
当然,某些系统承受不了任何帐户被黑客入侵的风险。对于此类系统,我们无法证明拥有永久登录名是合理的。
如果我们决定实施永久登录cookie,请按以下步骤进行:
首先,花一些时间阅读Paragon Initiative关于该主题的文章。我们需要正确处理一堆元素,并且本文在解释每个元素方面做得很好。
只是要重申最常见的陷阱之一,请勿将持久登录的Cookie(令牌)存储在数据库中,而只是将其作为哈希!登录令牌是等效的密码,因此,如果攻击者将手放在数据库上,他们可以使用令牌登录任何帐户,就像它们是明文登录密码组合一样。因此,在存储持久性登录令牌时,请使用哈希(根据https://security.stackexchange.com/a/63438/5002进行此操作,哈希较弱就可以达到此目的)。
{分块}
不要实施"秘密问题"。 "秘密问题"功能是一种安全反模式。从必读列表中的链接编号4中阅读论文。我们可以在Yahoo!之后询问Sarah Palin的问题。该电子邮件帐户在上次总统竞选期间遭到黑客入侵,原因是对她的安全问题的回答是" Wasilla高中"!
即使有用户指定的问题,大多数用户也很可能会选择以下任一项:
"标准"秘密问题,例如母亲的娘家姓或者最喜欢的宠物
一个简单的琐事,任何人都可以从他们的博客,LinkedIn个人资料或者类似内容中获取
任何比回答他们的密码都容易回答的问题。对于任何体面的密码,这是我们可以想象的每个问题
总而言之,安全性问题实际上在所有形式和变体上本质上都是不安全的,并且出于任何原因都不应在身份验证方案中使用。
甚至普遍存在安全性问题的真正原因是,它们可以方便地节省无法访问其电子邮件以获取重新激活码的用户的一些支持电话费用。这是以牺牲安全性和萨拉·佩林的声誉为代价的。值得?可能不是。
{分块}
我已经提到了为什么我们永远不应该使用安全性问题来处理忘记/丢失的用户密码。不用说,我们永远不要通过电子邮件将其实际密码发送给用户。在此字段中,至少要避免两个非常常见的陷阱:
请勿将忘记的密码重设为自动生成的强密码,这样的密码众所周知很难记住,这意味着用户必须更改密码或者将其写下来,例如在显示器边缘的亮黄色Post-It上。无需设置新密码,只需让用户立即选择一个新密码即可。 (这可能是一个例外,如果用户普遍使用密码管理器来存储/管理通常不记下来就无法记住的密码)。
始终对数据库中丢失的密码代码/令牌进行哈希处理。同样,该代码是等效密码的另一个示例,因此,必须将其进行哈希处理,以防攻击者进入数据库。当要求输入丢失的密码代码时,请将纯文本代码发送到用户的电子邮件地址,然后对其进行哈希处理,然后将哈希表保存在数据库中,然后丢弃原始密码。就像密码或者永久登录令牌一样。
最后一点:请始终确保输入"丢失的密码"的界面至少与登录表单本身一样安全,否则攻击者只会使用它来获取访问权限。确保我们生成很长的"丢失的密码"(例如,区分大小写的16个字母数字字符)是一个不错的开始,但是请考虑添加与登录表单本身相同的限制方案。
{分块}
首先,我们需要阅读这篇小文章进行现实检查:500个最常用的密码
好的,所以该列表可能不是任何系统上任何系统上最常见密码的规范列表,但是这很好地表明了在没有实施强制策略的情况下人们选择密码的能力有多么差。另外,将该列表与最近对失窃密码进行的公开分析比较时,该列表看上去离家很近。
因此:在没有最低密码强度要求的情况下,2%的用户使用前20个最常用的密码之一。意思是:如果攻击者仅获得20次尝试,则我们网站上50个帐户中的1个将是可破解的。
要阻止这种情况,需要计算密码的熵,然后应用阈值。美国国家标准技术研究院(NIST)特殊出版物800-63有一组非常好的建议。当与字典和键盘布局分析结合使用时(例如," qwertyuiop"是错误的密码),可以以18位的熵级别拒绝所有错误选择的密码的99%。简单地计算密码强度并向用户显示视觉强度计是好的,但还不够。除非强制实施,否则很多用户很可能会忽略它。
为了使用户更容易使用高熵密码,强烈建议使用Randall Munroe的Password Strength xkcd。
{分块}
首先,请看一下数字:密码恢复速度密码可以使用多长时间?
如果我们没有时间浏览该链接中的表,请参见以下列表:
几乎不需要时间就可以破解一个弱密码,即使我们使用算盘破解它也是如此。
如果它不区分大小写,则几乎无需花费时间即可破解字母数字的9个字符的密码
如果密码的长度少于8个字符,则几乎不需要时间就可以破解复杂的,符号和字母和数字,大小写(台式PC可以在一个键盘中最多搜索7个字符的整个密钥空间)。几天甚至几小时)
但是,如果我们每秒只能尝试一次,则破解6个字符的密码将花费大量时间!
那么我们可以从这些数字中学到什么呢?好了,但是很多,但是我们可以专注于最重要的部分:事实上,防止大量快速连发登录尝试(即强力攻击)确实并不那么困难。但是,正确地阻止它似乎并不容易。
一般而言,我们有三种选择都可以有效抵抗暴力攻击(和字典攻击),但是由于我们已经在使用强密码策略,因此这不应该成为问题。
在N次失败尝试后提出验证码(烦人,经常无济于事-但我在这里重复我自己)
N次尝试失败后锁定帐户并要求电子邮件验证(这是等待发生的DoS攻击)
最后,登录限制:也就是说,在N次失败尝试之后设置两次尝试之间的时间延迟(是的,仍然可能发生DoS攻击,但起跳的可能性要小得多,复杂得多)。
最佳实践1:尝试失败的次数越短,延迟时间越长,例如:
{分块}
DoS攻击此方案非常不切实际,因为最终的锁定时间略大于先前锁定时间的总和。
To clarify: The delay is not a delay before returning the response to the browser. It is more like a timeout or refractory period during which login attempts to a specific account or from a specific IP address will not be accepted or evaluated at all. That is, correct credentials will not return in a successful login, and incorrect credentials will not trigger a delay increase.
最佳实践2:中等长度的时间延迟会在N次失败尝试后生效,例如:
{分块}
DoS攻击此方案将是不切实际的,但肯定是可行的。同样,可能需要注意的是,如此长的延迟对于合法用户而言可能非常烦人。健忘的用户会不喜欢我们。
最佳实践3:将这两种方法结合使用,可以在N次失败尝试后生效的固定的短时间延迟,例如:
{分块}
或者,随着固定上限的增加,延迟增加,例如:
{分块}
该最终方案来自OWASP最佳实践建议(必须阅读列表中的链接1),即使被认为是限制性的,也应被视为最佳实践。
As a rule of thumb however, I would say: the stronger your password policy is, the less you have to bug users with delays. If you require strong (case-sensitive alphanumerics + required numbers and symbols) 9+ character passwords, you could give the users 2-4 non-delayed password attempts before activating the throttling.
DoS攻击这种最终的登录限制方案将是非常不切实际的。最后,始终允许持久(cookie)登录(和/或者经过CAPTCHA验证的登录表单)通过,因此在攻击进行期间,合法用户甚至都不会受到延迟。这样,非常不切实际的DoS攻击就变成了极其不切实际的攻击。
此外,对管理员帐户进行更积极的限制是很有意义的,因为这些是最有吸引力的切入点
{分块}
顺便说一句,更高级的攻击者将尝试通过"传播其活动"来规避登录限制:
在僵尸网络上分发尝试以防止IP地址标记
他们将选择最常用的密码并针对50.000个用户尝试,而不是选择一个用户并尝试使用50.000个最常用的密码(由于我们的限制,他们无法这样做)。这样,它们不仅绕过了诸如CAPTCHAs和登录限制之类的最大尝试措施,而且成功的机会也增加了,因为最常见的数字1的可能性远大于49.995.
间隔每个用户帐户的登录请求,例如相隔30秒,以潜入雷达
在这里,最佳做法是记录系统范围内失败登录的次数,并使用站点的错误登录频率的运行平均值作为基础,然后对所有用户强加上限。
太抽象了吗?让我改一下:
假设网站在过去3个月中平均每天有120次错误登录。使用该值(运行平均值),系统可能将全局限制设置为-ie的3倍。 24小时内360次失败尝试。然后,如果所有帐户的失败尝试总数在一天之内超过了该次数(甚至更好,监视加速速率并以计算出的阈值触发),它将激活系统范围的登录限制,这意味着所有用户的延迟很短(除了Cookie登录名和/或者备用CAPTCHA登录名之外)。
我还发布了一个具有更多细节的问题,并就如何避免防御分布式暴力攻击进行了很好的讨论。
{分块}
凭据可能会受到威胁,无论是利用漏洞,写下或者丢失密码,笔记本电脑的钥匙被盗,还是用户在登录网站时输入登录名。可以通过两因素身份验证进一步保护登录,两因素身份验证使用带外因素,例如从电话,SMS消息,应用程序或者软件狗接收到的一次性代码。几个提供程序提供两因素身份验证服务。
身份验证可以完全委派给单点登录服务,其他提供商在该处处理收集凭据。这将问题推给了受信任的第三方。 Google和Twitter均提供基于标准的SSO服务,而Facebook提供类似的专有## 解决方案。
{分块}