Troy Hunt最近发布了一篇博客,标题是“Authentication guidance for the modern era”。里面针对网站该使用什么样的密码规则给出了很多忠告,并引用了政府的特别建议,这对成功忽悠你的同事和老板非常有用。
我在google上班时做的其中一个项目是统一账户系统(特别是其反劫持功能)。大多数网站都有用户登录系统,所以读Troy的文章鼓励了我把构建这些系统的建议整理到一起。
1.最好的做法是,不做
不管你网站的业务是什么,用户认证都不是你的核心竞争力所在。现代登录系统被期望实现很多功能,密码管理只是一个开始。如果你做的比较成功,你最后又会想要这些玩意儿:
- 忘记密码的重新找回
- email地址验证
- 用户登出,这可比它看起来的要难多了(后面会讲)
- 密码暴力破解的防护
- 通过短信、app、硬件密钥的双因素认证
- 对于账户劫持的防护(当攻击者已经知道了正确的密码而用户又没有使用双因素认证的时候)
- 地区/语言/名字/头像照片的偏好
- 对手机/电脑注册的支持
- 关于异常行为的通知
- 只允许手机登录
随着大公司们提升了用户期望,而攻击者也变得更牛逼,保持自己更上步伐的努力正变得越来越不切实际。幸运的是,你可以把认证功能外包给那些使用OAuth的公司。
Web开发者通常把“通过Facebook登录”或者“通过Google登录”按钮看作是一种可选的锦上添花的可选功能,在实现了自己的账户系统之后才会考虑。如果你看这篇文章是因为你正在从头做一个新网站,那么我的建议你把“通过…登录”作为提供给用户的唯一选项。这个年代构建自己的账户系统,就像是建造自己的数据中心而不是直接使用AWS。这是一种相当昂贵的消遣。
有时候人们会担心如果他们只提供第三方登录,那么大的ID提供商可能会在某一天尝试窃取他们的用户。这一担忧的常见表现是用户通过第三方登录之后会被要求设置一个密码。不要担心这一点:如果这种不太可能的事情真的发生了,你总是可以把用户中心迁移到一个你自己的新系统,只需要简单地给用户发送一个链接就好了。
2.使用email/手机号来识别用户
别要求用户挑用户名,哪怕你是想提供像论坛那样的面向用户名的使用体验。对于你而言,总是通过email和/或手机号来识别用户的,如果你希望一个不同的名字用于用户之间的相互识别(一个显示的名字),那么应该单独选择。为什么呢?
- 无论如何你总是会要求提供email地址的。
- 如果用户名在你的服务中变成了一种表达自我的形式,那么用户会三天两头地修改。
- 用户容易忘记用户名,而不是他们的email地址和手机号。
- 挑用户名是一件会让人沮丧的事。每一次用户看到选择的名字已经被占用的时候,有些人就直接放弃了,于是你获得用户的通道就变窄了。
- 把用户名和显示名分开,将会减少对用户名形式加上各种限制的诱惑,比如,禁止使用空格。
3.不要使用密码
如果你没有准备好完全依赖第三方ID供应商,那么磅友你至少帮帮忙不要让用户设置密码。
这并不像听起来那么蠢。你已经要求用户提供自己的email地址了。登录系统上线之后你会加上的第一个特性就是找回忘记的密码,而这肯定是以给用户发送一封包含链接的邮件来实现的。所以任何一个可以看你用户邮件的人都可以用他们的身份登录,你的网站上的用户密码完全没有增加额外的安全性。
所以跳过这第一步直接进入第二步吧——你的登录系统可以简单地直接给用户发一封包含链接的邮件,点击后设置一个登录cookie。举个例子,Medium.com就是这样做的。
这个解决方案只要是在用户用于登录的所有设备上都有邮件客户端就可以。对于桌面电脑、笔记本、手机、平板等这个条件都是成立的。游戏机和电视就没这条件了,但是这些也不太可能是你面向的客户设备。如果碰巧是的话,那么你最好使用类似于蓝牙的配对处理,因为这些东西没有方便的键盘。
过去有意见说如果没有密码输入框的话用户会难以理解,但是Google网站的现代化登录体验就是从只要求用户提供email地址开始的,所以用户已经不太可能会因此感到困惑了…而且这样做的好处真的很大。
这方案还有个额外的好处:有些用户有手机号,但是却没有email,尤其是在发展中国家。所以如果这些国家是你网站潜在的目标市场,你最终可能会需要支持那些只能通过用手机接收代码来登录的用户。这样的账户将根本就没有密码,所以你如果假定所有用户都有密码的话,你就不得不转头为那些安全敏感的代码执行路径增加大量的特殊案例(这很容易引起致命错误)。
4.不要使用密保问题
如果你不得不使用密码——也许你就是懒得去跟老板解释为什么你是如此特立独行——至少不要再让用户使用密保问题来恢复密码了!
- 密保问题的答案通常很容易猜,用户发现要想出一个只有他们自己能回答的问题真的是太难了。
- 如果预设密保问题的话,答案就更容易猜了。
- 预设的密保问题通常还会有文化偏见,从而对大部分用户而言是没有意义的(比如,你高中学校的吉祥物是什么?)
- 有些聪明的用户意识到自己想不出难猜的问题之后,索性就直接把密保问题当作了第二个密码字段,这就又回到了忘记密码的问题。
- 通过滥用密码恢复流程实现黑客攻击的案例有很多,你不会希望自己成为其中之一。
Google曾经也遇到过很多关于密保问题的严重问题。我的一些老同事发布了关于这些问题的研究,值得一看。
以下是一些有问题的密保Q/A的例子:
- Q:最喜欢的食物。A:批萨。答案总是批萨。用这个回答你只要猜一次就可以侵入英语国家20%的账户。猜10次你就可以侵入英语国家里三分之一的设置了这个问题的账户。对于韩国的账户,猜不到10次就可以侵入其中的43%。
- Q:你结婚是在礼拜几?A:礼拜四。这是个自定义的密保问题,有致命的缺陷。攻击者只需要猜5次就够了,这对于检测暴力破解而言实在是太少了。
- Q:我在哪个城市出生?A:首尔。在有些国家,几乎所有人都生活在屈指可数的几个大城市里。观察身份验证界面所用的语言就能显著缩小可能城市的范围。对于韩国的账户你只需要猜10次就能侵入40%的使用该问题的账户。
- Q:我的第一个老师叫啥?A:赵老师,赵建国,老赵,建国。所有这些回答都是正确的,但是一个简单的实现方案是无法做好匹配的。我为这类问题增加了模糊匹配,因为用户总是“差一点”回答正确。匹配逻辑通常需要在一定程度上理解密保问题(Levenshtein距离本身对于街道地址这样的东西是低效的)。在为你的产品支持的所有语言实现这类匹配的时候,哦,菩萨保佑你!
毫不意外,专业的账户系统不会只使用密保问题来允许用户恢复密码。这只是众多信号中的一个。我保证你只有不到2%的可能性能够写出足够精密的系统使得功能正确。这就是为什么Google逐步淘汰了密保问题,改而支持使用短信恢复密码。基于短信的密码恢复功能也有它自身存在的问题,但与密保问题相比仍然好太多。
5.避免使用图片验证码
图片验证码是登录表单的常见特性。我在Google的时候也对这方面有过一些投入。不幸的是,验证码在今天已经没有什么价值,并且经常被实现得很糟糕。
关于验证码,需要理解的是它们只是在对自动化攻击施加基本的流量限制时才有用。它们无法保护你的系统避免批量注册。除了账户安全之外我还花了几年时间在Google的注册滥用上。看着垃圾邮件发送者解决我们数以百万计最难的验证码,几乎成了我们的日常。有些专业的破解验证码的公司,如DeathByCaptcha,混合使用了OCR技术和人工识别。传统的验证码阻挡了盲人用户进行注册,这也是一个问题,但基于语音识别的验证码要么轻松被计算机解决,要么就是人识别不了。
验证码最有用的地方是阻塞对密码的暴力破解。针对某个账户的暴力破解可能会需要数百万次的尝试才能找出正确的密码。在不影响到用户的情况下阻止暴力破解的简单方法是,在最近几次登录失败之后开始要求输入验证码。就算是很简单的验证码也足以给自动循环碰撞带来一小段延时。
而对于阻止批量的账户注册,验证码就没那么有用了。构建一套系统来检测并阻止这类行为是完全不同的博弈,我曾花费了好几年的时间参与在其中。相对其困难程度有一个感性认识的话,可以去buyaccs看一下不同的地下账户贩卖者提供的报价之间的巨大差异。较高的价格意味着面对防御能力更强的系统。除非你是Big5之一,不然你不太可能超过我们在账户注册安全方面的成果——这是另一个把登录系统外包给专业公司的理由。
如果你仍然坚持要用验证码的话,那就使用reCAPTHA,并确保你的验证码能够抵御重放攻击。不要试图自己实现,或者使用在Github上找到的工具,这类验证码毫无例外地被现代OCR技术轻易破解,除了减少你的用户注册率之外不会带来任何影响。
6.把双因素认证外包出去
现在双因素认证已经是一个常见特性。同样的,要做好这个功能很难、很贵,所以不要试图自己去实现。
- 短信并不可靠,尤其是在某些国家。恢复码它就是偶尔无法收到。你将最终想要实现利用了语音合成技术的电话方案,因为打电话要可靠得多。但是现在你就需要一个多语言的语音合成引擎了。
- 发大量短信、打很多电话都是相当费钱的事情,哪怕你能跟运营商协商得到好的折扣。
- 人们经常无法使用自己的手机号。如果依赖email那么你的密码恢复流程流程就能相当简单;一旦可靠双因素认证被引入,你的密码恢复流程就成了系统最弱的地方。你不升级功能,攻击者就能轻松绕过;你要是成功阻止了他们,接着你会发现…
- 黑了账户的黑客也会给账户添加双因素认证,来防止账户的真正所有者在恶意行为期间找回账户。
- 电话号码在端口攻击面前是脆弱的,所以现在的趋势是要求用户安装手机app或密钥。要实现这些意味着更大的工作量,并且毫无疑问它们也能被破解,所以你最终仍旧需要客服来帮助用户恢复密码。
- 正如你看到的,由于你不能再仅仅给用户发送邮件或者设置密保问题,双因素认证增加了大量的人工客服作业。这是很花钱的。
上述中的一部分是很根本的问题,但其中绝大多数都已经被大公司解决,这些公司将会免费给你支付手机账单和客服的工资。
就算你不打算利用这些大公司,也有创业公司能够替你解决双因素认证难题里的一小部分。
7.不要强制用户修改密码
Troy的文章已经很好的包含了这一问题所以我就不重复了,只是在强调下这真的很重要。不要只是因为已经使用了一长段时间就要求用户修改密码。
- 有些用户不会忍受这样的处理,你会失去他们。
- 有些用户比你更聪明,会轮流修改使用几个密码,这意味着你得保存用户最近使用过的几个历史密码来阻止这样的套路。但是我打赌你最初的实现不会想到做这些事情。
- 这完全没有提高安全性。
8.不要过期终止会话
又一个假的最佳实践。它引诱你把会话cookie设置为过期。有些人之所以认为这能够提升安全性,完全是出于和过期用户的密码一样的理由。
- 黑客倾向于立即做出恶意行为,所以过期处理并没有什么用。
- 会话过期会潜移默化地让用户认为意料之外的密码输入提示是正常的,这让他们极其容易被钓鱼。
- 会话的随机过期会导致产生大量耗费开发人员时间的bug。你网站的大部分都不会处理行为期间会话突然过期的情况,所以你就得回过头去解决这些异常,如果你能意识到这个问题的话。过期将会以用户报告的各种难以追踪的随机异常的形式表现出来。
9.别忘了登出
没有做好用户登出操作,是不成熟的账户系统的显著共同点。表面上听起来这功能很简单,但大部分显而易见的实现方法都有缺陷。
- 简单地删除会话cookie对于用户来说是很方便,但你则没法从XSS中恢复。一旦发现了XSS,你也许会希望让那些可能被窃取的会话cookie失效,但如果登出操作刚刚“请求浏览器把cookie删除”那么你就无能为力了。
- 给会话cookie加上时间戳并设置一个“上次登出时间”,要求每个动作都从账户数据库检查用户会话是不是太旧了。这会拖慢速度,然后引诱开发人员在调优时删除检查处理(毕竟这样做看上去没什么危险的)。如果恰好移除了对黑客感兴趣的终端的检查,你就又回到了第一点提到的问题。另外,还导致从浏览器或设备的登出会使得用户从所有这些地方都登出,这并不是期待的行为。
正确的做法是在内存缓存中记录失效的会话cookie的列表。但对于大多数公司来说,有一个成本更低而又够好的方法:让用户的登出链接只用来清除会话cookie,不再做其它事情,然后让会话cookie失效并自动默默地每5分钟左右进行替换。替换一个过期会话cookie的行为会去数据库查询管理员是否强制做了登出。如果用户提交了一个过期cookie,就会被要求重新登录。这里能够看出,很少会有cookie的清除动作是发生在被盗之后。
10.把账户相关的email跟营销邮件分开
发送密码恢复和注册验证链接的显而易见的方法是使用你的企业邮件服务器。不幸的是,你公司里总有几个贱人试图通过发送用户不想看到的推销邮件来跟用户“发生关系”。
即使用户在注册时同意了接收这些玩意儿,大部分人还是会感到厌烦,有些还会直接把它们报告为垃圾邮件。对于那些发现多点几次“报告垃圾邮件”之后就不会再见到这些邮件的聪明的用户来说,这是个很有用的解决办法,而不需要浪费脑力寻找那个小小的用白底浅灰6pt的字体写的“取消订阅”,或者…噢我的天…编写email过滤规则。
很不幸,这个完全正常的用户行为会降低你的邮件域名的受信度。来自你的账户系统的邮件开始被放到用户的垃圾邮件文件夹里。这就是为什么我们经常看到注册或者密码恢复流程提示我们去检查垃圾邮件文件夹。
解决该问题的一个办法是购买一个独立的顶级邮件域名来发送账户相关的邮件,并确保配置了DKIM。但接着,一些用户会注意到邮件域名的不同,并把你的邮件报告为钓鱼邮件。最好的解决方式是从一个不同的DKIM域发送你的市场营销邮件,但这样很有可能会涉及到跟你的产品团队的争斗。同样的,当你决定自己做的那一刻你已经接受了这样的痛苦,还记得吧?
11.好好保护你的密码数据库
如果你采用了密码,你就会有一个攻击者想要(并经常要到)的数据库。他们才不care你的公司,他们只是想要在更有价值的目标上尝试这些密码。但密码泄露依然是很尴尬的事情,并引起大额的处罚,尽管你的用户受到的直接影响很小。一个OAuth令牌的数据库的价值对于攻击者来说就要低很多,因此你就不太可能会成为攻击目标。
结论
对于账户系统我能写的还有很多。保护你的网站免于针对账户系统的恶意行为完全可以写一本书。我写不出这本书,但如果你感兴趣的话可以看我在2012年的一个演讲视频。
公平地说,这是一个带有欺骗性的庞大任务。这就是为什么我一直建议你咬咬牙吧账户管理功能外包给大公司去做。捣鼓图片验证码不是你的核心业务;为“用户登出”编写涉及文档不是你的核心业务;分析为什么你失去了那些忘记密码的用户的喜爱也不是你的核心业务;分析为什么发送到秘鲁的短信不可靠同样不是你的核心业务。你在这些事情上花的每一块钱,都被你那些使用“通过…登录”的竞争对手花在核心业务上了。
所以,扔掉你的密码数据库,别再回头。