SecMap - 找回密码
SecMap - 找回密码
SecMap - 找回密码
系列开篇
1 月份的时候我发过一篇文章,提到我今年最重要的学习计划是整理自己的技能树。思来想去过好几种方式,不管是经典的思维导图,还是比较新的链式笔记,都有各自好与不好的地方。最后还是觉得思维导图最好用,最简单,可移植性比较强,也容易共享。
整理的方式也让我一度陷入纠结,安全相关的知识实在是太多了,有基础知识,有攻击手段,有检测手段,有预防手段...以及到底要按照漏洞去梳理呢,还是要按照服务/功能去梳理呢?最后确定了一版最合适的分类方案,我打算按照服务按部就班地整理知识点。由于整理还在进行中,所以我也没法做整体详细的规划,最后的结果估计要整理完才能发出来,所以目前我打算先按照每一个知识点走,边整理边发文章,最后再发整体的思维导图,所以每篇文章目前看起来没啥关联性,但到最后就能看到关联性了。
还有一点要说明的是,由于微信公众号无法更新文章(只能改几个字...),所以这个系列后续的更新都会放到博客上去。
整个整理计划分为几大块,我给自己的梳理计划起了个名字:SecMap
,以后大家看到文章标题是这个开头的就是这个系列的啦。
这篇是第一篇,先从 http 服务入手:找回密码 功能点。
功能点介绍
找回密码相关的安全问题,基本上都是逻辑问题。找回密码的途径一般有两种:
- 通过回答密保问题重置密码
- 通过绑定的手机/邮箱重置密码
第一种没啥好说的,可以考虑猜解或者社工,有时候数据包里面可能自带了密保答案,比如在 js 里...第二种,现在一般都是往账号绑定的手机发一个 token,或者邮箱发一个重置密码的链接。而发给邮箱的重置密码链接,其实都包含了特殊的参数,本质上也是 token。所以不管是手机短信验证码还是邮件链接,本文统一称为 token,故本文就以邮箱为例。
而细分的话又有 2 种形式,一种是不需要输入账号绑定的邮箱,点击忘记密码,后台会自己从数据库中获取账户的邮箱,然后在用户点击确认之后,发送一个 token;另一种是要求用户输入此账号绑定的邮箱,然后再发 token。显然,前者的用户体验更好,也避免了用户不但忘记密码同时还忘记手机号/邮箱的尴尬局面。
攻击思路
客户端直接回显 Token
有些程序会将 token 放在响应包中,这个时候只要拦截发送的请求,查看响应就可以获得 token 了。如果是后台自动获取的邮箱,那么就可以重置任意用户的密码。织梦 CMS 就出现过这样的漏洞。
修复方法:避免将 token 回显在响应包中,验证的比对应该放在服务端进行。
Token 可暴力破解
暴力破解可分为几种:
好且快
的暴力破解:后端没有对 token 的尝试次数做限制时,则可暴力破解 token。知道 token 格式后,例如是 4 位数字、6 位数字等等,然后爆破就完事了。差但快
的暴力破解:后端未对错误次数做限制,只对 token 的有效期做限制,比如 30 分钟,如果是 4 位数字,就很容易爆破出来;对于更加复杂的 token,比如 6 位数字,攻击者可以考虑通过多线程、多进程、分布式等手段加快速度,赶在 token 失效之前尝试完毕(显然,这个速度是有上限的,取决于信道容量、网速、目标服务器的并发量)。差且慢
的暴力破解:后端未对错误次数做限制,只对 token 的有效期做限制(比如一天甚至无限),并限制了提交频率,比如一分钟只能提交(验证)5 次。这个限制会有效降低攻击者尝试的速度,但如果 token 的有效期足够长,攻击者还是可以慢慢试出来的,取决于攻击者在目标的预期成本。
修复方法:对 token 错误的次数做限制、按照业务需求,给定过期时间,限制提交频率,避免被爆破;必要时可以再加大其复杂度(6 位数字,甚至添加英文字母)
Token 可重放
这个很好理解,如果 token 验证通过之后依然有效,只要它没被覆盖,攻击者获得之后就可以重置密码。
修复方法:token 一旦验证成功,就要销毁,防止重放攻击。
篡改 用户名或找回地址
- 点击忘记密码时,后端生成页面的时候就自动拿到了邮箱,然后放在前端展示,用户点击确定之后,后端收到一个请求,这个请求包含了用户名和前端传来的邮箱。如果后端直接使用这个用户名和邮箱,生成此用户重置密码的 token,并且发送这个邮箱去,那么就可以篡改了:
- 前端填受害者的用户名,这个时候前端给后端的邮箱是受害者的邮箱,然后拦截数据包,篡改里面的邮箱为自己的,这样后端会发送重置密码的 token 到自己的邮箱,就可以重置受害者的密码。
- 前端填自己的用户名,这个时候前端给后端的邮箱是自己的邮箱,然后拦截数据包,篡改里面的用户名为受害者的,就可以重置受害者的密码。
- 流程进行到最后一步,填密码的时候,前端会把用户名发到后端去,后端以这个用户名为准来重置密码,那么只要修改这个用户名就可以重置此用户的密码。
- 找回密码凭证发到邮箱中,url 中包含用户名以及 token,这个 token 可以用于修改其他用户的密码。即服务端只校验 token 是否有效,而不校验 token 是否对应特定某个用户,那么只要修改用户名就可以重置任意用户的密码。
显然,这几种都可以重置任意用户的密码。
修复方法:生成 token 的时候,要验证用户名与邮箱是否对应;验证 token 的时候,注意用户名、token 要对应。
跳过 Token 的验证流程
填写 token 之后提交,后端收到 token 验证通过后,服务器返回类似 ok、1、success、true 这种的状态值。我们知道这个只需要拦截响应包就可以欺骗前端进入下一步,一般就可以修改密码了。填写新的密码之后,如果后端没有校验步骤是否进行到这一步,那么就可以跳过 Token 的验证流程,直接重置任意用户的密码。
修复方法:后端在进行找回密码的最后一步:覆盖数据库中的密码 之前,验证一下此账户找回密码的流程是否达到了最后一步(最好就是在整个流程中,每步都验证上一步的操作是否已经完成);可以缩短流程,把校验 token 和修改密码合并到一个步骤里。
session 复用
需要满足 2 个条件:
- 在用户进入到找回密码页面的时候,后端没有 set-cookie
- 后端在进行找回密码的最后一步的时候,从 session 中取的用户名
攻击的时候,先用自己的账户找回密码,收到邮件的时候,后端的 session 里的用户是我们自己。接下来新开一个标签页,用受害者的用户名进行找回密码,此时用的还是上一步的 session-id,所以等到邮件发出之后,此时后端的 session 里的用户名就变成受害者了,然后打开刚才收到的链接进行重置密码操作就可以重置受害者的密码。橘友们可以自行搜索 wooyun-2014-085843
,是一个比较经典的案例。
当然啦,这个也是任意用户密码重置。
修复方法:找回密码功能,如果要依赖 session 完成,那么一定要做好 session-id 的销毁机制,避免 session 里重要的值被覆盖。
Token 可预测
重置密码的 token 应该是极难预测的。但是有些实现的就不安全,例如:
- 使用时间戳作为 token
- 用户 id 或者时间戳 经过 md5/base64 作为 token
- 有个特定的 api 可以生成 token,若未授权,攻击者可以直接调用
如果后端没有验证找回密码流程,则找到规律后,直接伪造 url 就可以重置任意用户的密码;如果后端验证了找回密码的流程,就按照流程正常走,发完 token 后,猜解一下 token,完成任意用户密码重置。
修复方法:token 尽可能随机,不要有规律可循;token 尝试次数做限制;验证找回密码的流程是否到了最后一步(最好就是在整个流程中,每步都验证上一步的操作是否已经完成)。
用户骚扰
这里以绑定的手机号为例。
不断地重复找回密码的过程,让后端不断发送 token。如果后端不验证账户与手机号的话,则可以短信轰炸任意手机号;如果后端在验证了账户与手机号之后,才发送 token 的话,则只能轰炸指定的手机号。
修复方法:限制找回密码的频率;注意,攻击者可以恶意消耗找回密码次数,导致真正的用户无法使用找回密码功能,所以加验证码提高攻击者恶意消耗次数的成本,也是一个非常常见的措施。
其他
重复注册
可以注册一个已存在的用户,导致密码被覆盖,但原用户的所有信息(用户的信息,姓名、身份证、手机号等等)却没有改变。
案例:wooyun-2014-088708
总结
找回密码是一个非常常见的功能,但不是那么容易写得完善的,需要注意的地方有很多,逻辑稍有不完善就会出现漏洞。
上面的几点都好理解,最后不妨再思考一个问题:假如有个网站,它的找回密码功能没有对 token 的有效时间加以限制,也就是说派发的 token 是没有过期时间的,但是有错误次数限制,如何看待这样的设计方案?
首先,过期时间实现起来要比次数限制简单,业务方一般喜欢做简单的;如果过期时间与次数限制只能做一个的话,对于防御爆破来说,肯定是次数限制来得彻底;但是只做次数限制的话,增加了 token 泄露的风险,因为 token 不会过期,在漫长的生命周期里有很大的可能泄露,而且不是所有的 token 都会被使用(用户可能手抖了或者是由攻击者发起的找回密码),如果还有 token 可重放的问题,那风险就更大了。所以我认为这样的方案并不完美,但是可以接受。
逻辑漏洞的类型其实挺多的
毕竟你不知道开发会写出什么样的神仙代码
另外现在虽然有乌云的镜像站
但是很多漏洞有点旧了
所以参考一下就好