早上开完站会,大壮领了个新任务,要对登录功能做升级,在原来只支持用户名+密码登录模式的基础上,增加手机号+短信验证码动态登录。
业务分析师美美早已经准备好了故事卡,并且也考虑到了这个功能的特殊性,除了通常的业务性验收标准外,还专门添加了一些和安全有关的条目。
故事卡-126 作为用户,我可以通过手机号和短信验证码更方便的登录。 安全验收标准: 短信验证码有效期5分钟 验证码为4位纯数字 每个手机号60秒内只能发送一次短信验证码
大壮看到故事卡中提到验证码长度只有4位,而且还是纯数字,觉得强度有些不够,担心如果黑客来个多线程并发请求进行暴力登录,完全可能会在有效期内破解出合法的验证码。
大壮把自己的担心告诉了美美,建议把验证码长度增加到6位,或者改为4位数字和字母的组合,以增加复杂性,提高暴力破解的难度。
美美听后摇摇头,说道:“我理解你的担心,可是业务部门的需求很明确,这次就是为了优化用户登录体验,所以才决定做手机号和验证码登录,如果把验证码弄的这么复杂,不符合这个故事卡的初衷啊。”
“对于用户而言,4位数字验证码确实好记好填,可是对于黑客而言,最多1万次请求就能遍历完所有的验证码,更何况实际上不会那么倒霉……”,大壮说道。
为了满足用户体验而在安全性上做出这么大的妥协,而且风险看起来很大,大壮觉得自己无法说服自己,正准备要跟美美做详细解释黑客攻击手段的时候,技术老大老叶听见了他俩的讨论,慢慢脱下帽子,摸了摸发际线感人、“地中海”也之日可待的乌黑秀发,说道:“那啥,后台在验证登录请求的时候,不管验证码是否匹配,存在Redis里的验证码只要被取出来就立即作废,so easy。”
“对啊”,大壮感觉眼前一亮,说道,“后台在比对请求中的验证码和Redis中保存的这个用户手机号所对应的验证码的时候,不管匹不匹配,直接把Redis中的这个验证码作废。这样黑客发第二次登录请求的时候,会因为Redis中找不到该手机号对应的验证码记录而登录失败。这样既避免了暴力攻击,也不再需要增加验证码的强度,导致用户体验的下降了。”
“如果用户是因为手滑导致输错验证码,莫非还必须等几十秒的时间再重新发第二个验证码?”“能不能做到验证码输入错误3次后再作废?”美美接着问。
“没问题,这个不难”大壮答到。
“ok,那我们就加一条安全验收标准吧”
保存于服务器端的验证码,至多可被使用3次(无论和请求中的验证码是否匹配),随后立即作废,以防暴力破解
“对了大壮”,老叶喝了口咖啡,最近连续的加班让他感觉很疲惫,只能靠喝苦咖啡强打精神。“60秒内只能发1次短信那条,别忘了前后端都要做检查。”
“知道知道,永远不要相信前端的限制,后端一定要做限制!”大壮连连点头。
“好,那就这么做,去忙吧”。老叶转身坐下,忽然一个念头快速在脑海里一闪而过。“短信每60秒发一次,但有效期是5分钟,那第61秒的时候如果又请求发送一次验证码,这时第一次的验证码还没过期,服务端应该如何处理这个请求呢?“老叶心想。
显然,第二个验证码直接覆盖掉第一个会更加安全,也就是至始至终都只有一个处于有效状态的验证码,但这会不会给用户带来困惑?毕竟偶尔还是有因为手机信号不好或者短信通道堵塞等缘故,用户等了超过1分钟才收到验证码的情况。如果不覆盖呢?最极端的情况会出现一个手机号同时有5个有效验证码,这肯定会增加黑客破解的成功概率。不过因为一个验证码最多只能被使用3次(无论输入的是否匹配),之后就被作废了,所以实际上黑客暴力破解的难度依然很高。
经过反复思考后,老罗最终选择保留验证码5分钟有效期的设置。
手机号+短信验证码登录的功能上线后,运行状态一直比较平稳,然而这种平静的氛围被一通电话打破了。
“喂,对,是我”,老叶桌上的电话响了,他忙着写代码,歪着脖子用肩膀和脸夹住话筒说道:“是客服部啊,有什么事我可以帮忙的?”
“是这样,我们今天突然收到很多顾客打来的投诉电话,抱怨说收不到短信验证码,登录不了。而且他们基本都是新用户,现在有了手机号和短信验证码功能后,他们并不愿意再重新注册用户名和密码。我们只好让顾客再等会儿试试,可能是信号不好,但后来他们反馈说还是收不到我们的短信,而且只是收不到我们的短信,所以,你们那边能帮忙看看是怎么回事吗?”电话那边一口气讲了一堆话。
“还有这种事,行,我知道了,我们马上查一下。”老叶刚挂断电话,运维部的同事过来找到老叶,说短信配额今天消耗得很厉害,已经触发了2次告警了,运维同事发现早上11点和下午3点左右有两批次大量发送登录短信验证码的请求,但又没有发现对应的后续登录请求,判断极可能是被黑客攻击了,现在已经临时性的屏蔽了攻击来源IP地址的访问。老叶觉得这个事和刚刚接到的客服部门说的是同一件事,便把刚才电话里听到的信息和运维同事讲了一遍。
“这更能证实是黑客攻击了,看来他们的目标还不是暴力登录,而是故意消耗短信发送配额,一旦配额被用完,所有用户就无法通过短信验证码正常登录。” 运维部的同事说完看向老叶。
老叶若有所思的说道:“没想到还能这么玩儿。我们目前只限制了一个手机号60秒内发一次验证码,却没有限制大量不同手机号同时发送的情况。”
“那现在怎么处理比较好呢?虽然临时禁用了攻击者的IP,但我们担心会误伤真实用户,而且黑客如果利用大量肉鸡来继续攻击的话防范起来会更加困难。”运维同事继续问道。
“这个也不难,在发短信验证码之前要求先输入图形验证码。”
“嗯,有道理,你们什么时候能做好上线?”
“我现在就加”,老叶还没说完就已经开始写代码了:“一会儿弄完紧急上线。”
“行,我回去安排一下,运维部全力配合。”
“看来之前那张故事卡里的安全验收标准还差了一条”,老叶自然自语道:“如果加上一条图形验证码的要求恐怕就不会出这个事儿了。”
发送短信验证码之前,先验证图形验证码是否输入正确
“喂喂喂,这都什么鬼?”用户体验设计师嘉嘉抓住路过的老叶说:“我不过是休了两天假,回来之后怎么发现登录这里多了个图形验证码出来?”
老叶向嘉嘉解释了这个图形验证码的由来,是出于安全的考虑才增加的。
“我知道安全很重要,可是这图形验证码太伤害用户体验了,现在用户登录过程中就要再多做一次输入,如果填错了还得重新再来一次。莫非就没有别的解决办法了吗?”嘉嘉显然并不认同这个方案。
“其实也有,就看公司舍不舍得花这笔钱了。”老叶回道:“登录界面可以动态决定是否要求输入图形验证码,对于正常用户无需输入,对于黑客或者疑似黑客的人,就要求他们输入。”
“这个方案靠谱,不过,这和舍不舍得花钱有什么关系吗?”嘉嘉不太明白。
“要动态决定是否要求输入图形验证码这件事儿,核心就在于判断发起登录请求的人是真实的顾客还是黑客。我们自己现在没这个判断能力,不过有提供这种服务的第三方,但都是收费的。”老叶解释道。
“某大盗云和某鹅云等都提供这类服务。我们后台在处理登录请求的时候,先尽可能多的收集该请求的上下文信息,例如登录请求的来源IP地址、时间、手机号、User-Agent等等数据,并且把这些数据通过API调用的方式传递给他们,他们再利用自己的安全能力进行分析判断,并把当前请求者是可信用户还是可疑用户的结果返回给我们。最终是否允许登录成功的决定权还是在我们的后台服务这边,只是借助了第三方API提供的分析结果来做判断而已。”老叶接着说道。
“我不懂技术,不过逻辑还是很清楚。既然是钱的事,那走吧,我们去问问领导的意见,”嘉嘉笑着说道,然后拉着老叶径直朝着总经理办公室走去。
尾声
最终,老叶的团队用上了某云的第三方API做登录防护,去掉了令嘉嘉抓狂的图形验证码。并且经过和业务部门的商量,验证码的长度改成了6位纯数字,验证码有效期也缩短到了2分钟。
在这期间还出现了一个小插曲,团队把发送短信的功能拆分出来做成了一个独立微服务,但却没有给这个新的接口设置对应的访问控制权限,以至于任何人只要向这个接口发起请求就能将短信发送到任意手机,短信内容还可以自定义。这个问题是在安全团队做渗透测试的时候发现的,所幸发现及时,团队做了紧急修复,并没有造成安全事故。老叶为这事一直后怕,当他是个菜鸟的时候也一直以为开发团队内部服务/接口的调用不用进行身份认证和权限控制,后来恰恰是团队内部有人利用这些服务做了坏事,才知道很多时候安全事故反而是发生在内部而不是外部。
美美后来把短信验证码登录的故事卡作为案例保存了起来,把安全验收标准又重新做了一次梳理:
故事卡-126 作为用户,我可以通过手机号和短信验证码更方便的登录。 安全验收标准: 短信验证码有效期2分钟 验证码为6位纯数字 每个手机号60秒内只能发送一次短信验证码,且这一规则的校验必须在服务器端执行 同一个手机号在同一时间内可以有多个有效的短信验证码 保存于服务器端的验证码,至多可被使用3次(无论和请求中的验证码是否匹配),随后立即作废,以防止暴力攻击 短信验证码不可直接记录到日志文件 发送短信验证码之前,先验证图形验证码是否正确(可选) 集成第三方API做登录保护(可选)
没成想,一个手机号+短信验证码登录的背后,还能牵扯出这么多事儿来。