原文作者:Jeff Atwood
因为安全漏洞,社区网站被攻破或摧毁的案例已经不在少数。也因此,我们在立项做Discourse.org的时候,就把那些教训铭记于心。尽管开源的社区软件已经成千上万,我们还在为开发一个在骨子里就非常安全的软件而努力。
与此同时,我们也重视可移植性——你能随意将数据从Discourse.org导入或导出。这也是Discourse遵从Creative Commons协议的原因——这一点有别于其他的论坛软件。作为一名普通用户,你可以在用户信息页面,轻轻松松地将你在Discourse上发表的文字导出并下载下来。
注:Creative Commons(知识共享)是一个相对宽松的版权协议。使用者可以明确知道所有者的权利,不容易侵犯对方的版权,作品可以得到有效传播。
如果你是站长,你可以在管理面板上轻易地备份或者还原整个网站的数据库。在浏览器里操作就行。我们一开始就为你设置好了每周的自动备份。为你考虑得这么周到,算是你有福了!我可是亲身经历了一次惨痛的教训之后,才在备份方面成为行家里手的。
这么多年下来,我们意识到,在安全和数据可移植性之间求得平衡是很难的。能把整个数据库下载下来,最开心的莫过于黑客了,这意味着他们很快就能在你的系统中获取据点。那可是终极大奖!
为了缓解这种威胁,我们对Discourse的备份在很多方面逐步加强了限制:
说到“安全”措施,无非就是深度防御,因此所有这些加固手段都是有益的……但是,我们仍然需要假设,网上的一些坏家伙能够通过某种方式获得你的数据库的一份拷贝。然后怎么办呢?好吧,我们看看数据库里有些啥?
l 身份cookie
众所周知,cookie是浏览器用以识别用户身份的。cookie通常以哈希值的形式存储,而不会是裸值。因此,暴露哈希值不至于让你冒充目标用户。况且,大部分现代的web框架都会快速回收cookie,因此它们的有效期只有短短的10~15分钟。
l Email地址
尽管用户对于自己的email被曝光这件事,多少还是有些担心的。但现如今把email地址当成至宝的人应该也为数不多吧。
l 所有的文章
我们假设这是一个完全公开的网站,没人会在那里发布特别敏感的东西。既然所有的文章都是公开的,我们也就不必担心什么商业机密或其他秘密资料被揭露了(至少眼下不用担心)。如果我们确实有一些机密资料要保护,那我以后可以专门写一篇文章来探讨这个问题。
l 密码的哈希值
剩下的就是密码的哈希值了。而且,那确实是个严重的问题!
现在的问题是,攻击者拿到了你的数据库,他们通过大规模的离线攻击,或者在他们财力能够承受的范围内充分利用云计算资源,攻破了密码哈希。一旦他们得逞了,他们就能以那个用户的身份登录……一直这样冒充着,直到那个用户修改密码。
★ 所以说:如果你知道(甚至只是怀疑)你的数据库被暴露了,你应该做的第一件事就是重置所有人的密码。
然而,如果你没有意识到被拖库了,怎么办呢?你需要先发制人,像世界最流氓大公司的IT部门那样,每隔30天就重置所有人的密码吗?那样的用户体验显然很糟糕,还会把自身带入一种严重的病态。现实是,当你的数据库被暴露之后,你可能并不会知道,而等你知道的时候已经太晚了,那时采取任何措施都已枉然。因此,关键还在于拖慢攻击者的节奏,为自己争取时间去处理和应对。
这样的话,你可以提供给用户唯一真正的保护,就只有你存储的密码哈希的抗攻击性了。说到哈希的强度,有两个因素:
1. 哈希算法。应该尽可能的慢,并且最好跑在GPU上时尤其慢。原因后文细述。
2. 工作因子或迭代次数。尽可能设置高一点,不过也不至于让你陷入DoS攻击为限。
我看到过这样的建议,说你应该把全局的工作因子设得足够高,以至于在目标平台上计算一个密码的哈希值至少需要耗时8毫秒。事实证明,Sam Saffron(和我一样是Discourse的联合创始人)早在2013年选择NIST(美国国家标准与技术研究院)推荐的PBKDF2-HMAC-SHA256和64k迭代,是多么明智的一个决定啊!我们测试过,这个算法在我们目前的服务器(相当高端的Skylake 4.0 GHz)上,运行我们现有的Ruby登录代码,确实需要耗费大概8毫秒。
不过,那是4年前的事了。现如今,我们数据库里的密码哈希还这么安全吗?4年以后或者10年以后呢?我们在做的是一个开源软件,是一个长期项目,我们需要确信我们做出合理的决定,以保护每一个人。俗话说得好,防人之心不可无,是时候戴上达斯头盔演一回坏人了——让我们自己来攻击自己的哈希!
我们先用恶贯满盈的单GPU(GTX 1080 Ti)小试一下。先给些参考数据:PBKDF2-HMAC-SHA256在1080型号上可以达到1180 kH/s(千次哈希/秒),而在1080 Ti上可以达到1640kH/s。看吧,还是同一代视频卡呢,在攻击哈希的效率方面竟然能提升将近40%。细思极恐!
首先,我们来做一个小小的试验,看看是否行得通。我下载了Hashcat。(注:Hashcat是当前最强大的开源密码恢复工具,可以访问Hashcat.net了解更多详情。)我登录进我们的演示网站try.discourse.org,然后创建了一个新帐号,并设置了密码0234567890。接着,我查看了数据库,发现为那个新用户生成了如下的哈希值和盐:
hash 93LlpbKZKficWfV9jjQNOSp39MT0pDPtYx7/gBLl5jw=
salt ZWVhZWQ4YjZmODU4Mzc0M2E2ZDRlNjBkNjY3YzE2ODA=
Hashcat对输入文件的格式是有要求的:一行只描述一个哈希,须注明哈希类型,迭代次数,盐和哈希值,并且各部分之间用冒号隔开,如下:
sha256:64000:ZWVhZWQ4YjZmODU4Mzc0M2E2ZDRlNjBkNjY3YzE2ODA=:93LlpbKZKficWfV9jjQNOSp39MT0pDPtYx7/gBLl5jw=
让我们把它交给Hashcat,看它是否搞得定!
./h64 -a 3 -m 10900.\one-hash.txt 0234567?d?d?d
注意:这里故意降低了计算量,只让它猜3个数字。而不出所料的是,我们很快就把密码攻破了!看到最后的密码了吗?我们得手了!
sha256:64000:ZWVhZWQ4YjZmODU4Mzc0M2E2ZDRlNjBkNjY3YzE2ODA=:93LlpbKZKficWfV9jjQNOSp39MT0pDPtYx7/gBLl5jw=:0234567890
到目前为止,我们知道上面的攻击方法是可行的。然后让我们进入正题。不过,我们一开始先简单一点——蛮力攻击最简单的8位数字组成的Discourse密码需要多久呢——也就108个组合,1个亿吧。
Hash.Type........:PBKDF2-HMAC-SHA256
Time.Estimated...:Fri Jun 02 00:15:37 2017 (1 hour, 0 mins)
Guess.Mask.......:?d?d?d?d?d?d?d?d [8]
即使用的是一款极品GPU……记住,这里我们只是测了一个哈希,也就是说,表中的每一行(用户)都需要耗费你1小时。还有更打击你的消息:Discourse已经禁用8个字符的密码有段时间了。如果我们尝试破解更长的数字型密码,需要花多少时间呢?
?d?d?d?d?d?d?d?d?d[9]
Fri Jun 02 10:34:422017 (11 hours, 18 mins)
?d?d?d?d?d?d?d?d?d?d[10]
Tue Jun 06 17:25:192017 (4 days, 18 hours)
?d?d?d?d?d?d?d?d?d?d?d[11]
Mon Jul 17 23:26:062017 (46 days, 0 hours)
?d?d?d?d?d?d?d?d?d?d?d?d[12]
Tue Jul 31 23:58:302018 (1 year, 60 days)
要知道,全数字的密码模式太简单了,小朋友才会用!要不要试一些真实的密码,比如只含小写字母,或者小写+大写+数字的组合?
Guess.Mask.......:?l?l?l?l?l?l?l?l [8]
Time.Estimated...:Mon Sep 04 10:06:00 2017 (94 days, 10 hours)
Guess.Mask.......:?1?1?1?1?1?1?1?1 [8] (-1 = ?l?u?d)
Time.Estimated...:Sun Aug 02 09:29:48 2020 (3 years, 61 days)
目前看来,像这样的蛮力攻击——拿字母或数字一个一个去试——在高端GPU上的表现也不算惊艳。但如果我们把数字除以8呢……手段是,在一台机器里装上8张显卡。这是小公司财力所能及的,或者有些有钱的个人也能做到。遗憾的是,38个月除以8得出的结果也并没有大幅降低攻击时间。别急,如果以举国之力来做这件事呢?他们财力雄厚,可以使用几千个这样的GPU(1.1天),甚至可能几万个GPU(2.7小时),然后呢……好了,即使密码的最低要求是10个字符,你在那种情况下也麻烦大了!
如果我们想让Discourse扛得住全国性的攻击,很显然,我们还需要努力。Hashcat有一个方便的基准模式,它在一台装有8张Nvidia GTX 1080 GPU的设备上测试,然后根据结果按顺序列出了最强(最慢)的哈希算法。从那份清单上,我看到了脱颖而出的bcrypt、scrypt和PBKDF2-HMAC-SHA512。
我用Hashcat的测试结果给了我一些信心,说明Discourse存入数据库的密码哈希并没有太偏离正确轨道。但是,我想要的是完全的确信,于是我请了一位有安全和穿透测试背景的人,来尝试破解当前托管在我们这儿的两个非常热门的Discourse站点的密码哈希(当然签了保密协议)。结果是这样的:
有人提供给我两套来自两个不同的Discourse社区的密码哈希,分别包含5909个和6088个哈希值。两者都使用了PBKDF2-HMAC-SHA256算法,并且工作因子是64k。我的机器配有Nvidia GTX1080 Ti GPU,使用Hashcat计算哈希的速率大约是每秒27000次。
所有Discourse社区都共同遵循的密码规则如下:
利用上述密码规则制定出模板,我花了大概3周时间在11997个哈希里破解出了39个,其中25个是xxx社区的,14个是yyy社区的。
这是一位安全领域的研究员,他经常做如此这般的审计。因此,所有的攻击都使用了字典,并且根据他以前的经验总结出来的高效攻击模式——这不是真正意义上的蛮力攻击。他恢复出来的密码如下(其中有一个是重复的):
007007bond |
l3tm3innow |
如果我们把攻击力度提升到8倍,并且允许的时间翻倍,假设碰到一个打了鸡血的攻击者,或者他有一套很灵光的字典和模板,可以预见,他可能最后破解出 39 x 16 = 624个密码,也就是相当于所有用户中的5%。这是合理的估算,但仍然比我想象的要高。在Discourse的未来版本里,我们肯定会规划加入哈希类型表。这样我们就能在将来1~2年里,切换到一种更为安全(慢得多)的密码哈希策略。
bcrypt$2*$, Blowfish (Unix)
20273 H/s
scrypt
886.5 kH/s
PBKDF2-HMAC-SHA512
542.6 kH/s
PBKDF2-HMAC-SHA256
1646.7 kH/s
通过这次演练,我现在对我们最糟糕的安全场景有了一个更为深入的理解,也就是,数据库被盗走,然后在线下遭遇专业的密码哈希攻击。同时也让我更加自信地推荐和支持我们的开发工作,使得Discourse的用户人人都很安全。因此,如果你在安全实施方面并不完全确信(像我一样),是时候来验证一下那些假设啦。别等着黑客来攻击你——作为黑客,要勇于自黑!