52

不就是个短信登录API嘛,有这么复杂吗?

 5 years ago
source link: https://insights.thoughtworks.cn/sms-authentication-login-api/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

引子

上联:这个需求很简单

下联:怎么实现我不管

横批:今晚上线

Part 1:暴力破解

早上开完站会,小李领了张新卡,要对登录功能做升级改造,在原来只支持用户名密码登录模式的基础上,新增手机号和短信验证码登录。

emMrYjE.jpg!web

业务分析师薇薇早就准备好了故事卡,并且也考虑到这个功能的特殊性,除了平常的业务性验收标准外,还专门添加了一些和安全有关的条目。这张故事卡看上去是这样的:

故事卡-274

作为用户,我可以通过手机号和短信验证码登录,以便于我更方便的登录。

安全验收标准:

  • 短信验证码有效期5分钟
  • 验证码为4位纯数字
  • 每个手机号60秒内只能发送一次短信验证码

小李看到故事卡中提到,验证码长度只有4位而且还是纯数字,隐约觉得强度有些不够,担心万一黑客来个多线程并发请求,或者拿一个集群来暴力登录,有可能会赶在有效期内破解出合法的验证码。

小李把自己的担心讲给了业务分析师薇薇,并且建议把验证码长度增加到6位,或者在保持4位长度的情况下,改为数字和字母的组合,目的是增加验证码复杂性,提高暴力破解的门槛。

薇薇听了这两种选择后直摇头,说道:“我理解你的担心,可是业务部门那边的需求很明确,就是为了优化用户登录体验,所以才决定做手机号和验证码登录,如果把验证码弄的这么复杂,那用户体验也好不到哪里去,不符合这个故事卡的初衷啊。”

“对于用户而言,4位数字验证码确实好记好填,可是对于黑客而言,就能很容易的完成暴力枚举,理论上最多1万次请求就能遍历完所有的验证码,更何况黑客没那么倒霉,要尝试到第1万次才猜对……”,小李说道。

为了满足用户体验而在安全性上做出妥协,这种事情小李觉得自己无法说服自己,正准备掏出纸和笔跟薇薇做详细解释黑客攻击手段的时候,团队技术负责人老罗听见了他们两的讨论,慢慢脱下帽子,摸了摸正在朝着“地中海”模式演进的乌黑的秀发,说道:“那啥,服务器在验证登录请求的时候,不管验证码匹配还是不匹配,存在Redis里的验证码只要被取出来就立即作废,根本不给黑客暴力破解的机会。”

RJbQBjq.jpg!web

小李的团队已经搭建好了Redis,用来存储登录过程中发给用户的短信验证码,是一个手机号和验证码的键值对。

“对啊”,小李感觉眼前一亮,说道,“服务器在比对请求中的验证码和Redis中保存的这个用户手机号所对应的验证码的时候,如果发现不匹配,那依然还是直接把Redis中的这个验证码作废。这样黑客发第二次登录请求的时候,会因为Redis中找不到对应的记录而登录失败。这样既避免了暴力枚举攻击,同时也不再需要增加验证码的强度,导致用户体验的下降了。”

小李建议把刚才的讨论结果写到故事卡里,而薇薇提议能否不要立即作废:“万一用户输入验证码的时候手滑输错了,岂不是要等几十秒的时间再重新发第二个验证码?”

“可以做到验证码3次输入错误后就作废吗?”薇薇问到。

“可以的,这个不难”小李坚定的回答到。

“好,那我们加一条安全验收标准吧”,薇薇边说边修改了故事卡,新增加了一条:

保存于服务器端的验证码,至多可被使用3次(无论和请求中的验证码是否匹配),随后立即作废,以防止暴力攻击

“对了小李”,老罗喝了口咖啡,最近连续的加班让老罗感觉很疲惫,只能靠喝咖啡强打精神。“60秒内只能发1次短信那条,别忘了前后端都要做检查。”

“知道知道,前端做不做都无所谓,关键是在后端要做限制。”小李连连点头。

“好,那就这么做,去忙吧”。老罗转身坐下,正准备继续刚才被打断的工作,此时一个念头快速在脑海里一闪而过,老罗在电脑上打开短信登录的这张故事卡,从头到尾又看了一次,最后目光停留在“短信验证码有效期5分钟”这条验收标准那里。

“短信每60秒发一次”,老罗心想:“但有效期是5分钟,那第61秒的时候假如又请求发送一次验证码,这时第一次发送的验证码还没过期,服务器端该怎么处理这个请求会比较稳妥呢?

显然,第二个验证码直接覆盖掉第一个会更加安全,也就是至始至终都只有一个处于有效状态的验证码,但这会不会给用户带来困惑?毕竟偶尔还是有手机信号不好,等了1分钟多钟之后才收到第一个验证码的情况。

如果不替换而是追加验证码呢?最极端的情况是会出现一个手机号有5个有效验证码的情况,会增加黑客暴力破解的成功概率。不过因为一个验证码最多只能被使用3次,之后就被作废了,所以实际上黑客暴力破解的难度依然很高。

总的来说,直接覆盖的做法用户体验不佳但更安全,依然有效的做法用户体验更好但相对而言安全性略有降低。”

经过反复思考后,老罗最终选择保留验证码5分钟有效期的设置。

Part 2:防不胜防

短信验证码登录的功能上线后,运行状态一直比较平稳,然而这种平静的氛围被一通电话打破了。

jIVbE3V.jpg!web

“喂,对,是我”,老罗桌上的电话响了,他忙着写代码,歪着脖子用肩膀和脸夹住话筒说道:“是客服部啊,有什么事我可以帮忙的?”

“是这样,我们今天突然收到很多顾客打来的电话,抱怨说收不到短信验证码,登录不了账户,他们基本都是新用户,只有用手机注册的账号,没有用户名密码,所以也不能用原先的用户名密码去登录账号。我们只好让顾客再等会儿试试,可能是信号不好,但后来他们反馈说还是收不到我们的短信,而且只是收不到我们的短信,所以,你们那边能帮忙看看是怎么回事吗?”电话那边一口气讲了一堆话。

“还有这种事,行,我知道了,我们马上调查分析一下。”老罗刚挂断电话,运维部的同事过来找到老罗,说短信配额今天消耗得很厉害,已经触发了2次告警了,运维同事做了一下简单的分析,发现早上10点和下午2点左右有两批次大量发送登录短信验证码的请求,但又没有观察到对应的后续登录请求,判断可能是被黑客攻击了,于是临时性的屏蔽了攻击来源IP地址的访问。

“来找你就是想和开发团队共同调查下这个问题,看接下来怎么处理会比较好。”运维部的同事说道。

老罗觉得这个事和刚刚接到的客服部门说的是同一件事,便把刚才电话里听到的信息和运维同事讲了一遍。

“这更能证实是黑客攻击了,而且看来他们的目标应该不是暴力登录,而是故意消耗短信发送配额,一旦配额被用完,用户就无法正常登录,也算是某种程度上的拒绝式服务攻击了。” 运维部的同事说完看向老罗。

老罗若有所思的说道:“没想到他们还能这么玩儿。我们目前只限制了一个手机号60秒内发一次验证码,却没有应对大量不同手机号的情况。”

“那现在怎么处理比较好呢?虽然临时禁用了攻击者的IP,但我们担心会误伤真实用户,而且黑客也可能会变换IP来继续进行攻击。”运维同事继续问道。

“有办法,在发短信验证码之前先要求输入图形验证码。”

“嗯,有道理,你们什么时候能做好上线?”

“我现在就加”,老罗还没说完就已经开始写代码了:“一会儿弄完紧急上线。”

“行,我回去安排一下,咱们运维部全力配合。”

“看来之前那张故事卡里的安全验收标准还差了一条”,老罗自然自语道:“如果加上一条图形验证码的要求恐怕就不会出这个事儿了。”

发送短信验证码之前,先验证图形验证码是否正确

Part 3:权衡

“喂喂喂,这搞的什么鬼?”用户体验设计师Jenny抓住路过的老罗说:“我不过就是休了两天假,回来之后怎么发现登录这里多了个图形验证码出来?”

VnQz22Y.jpg!web

老罗向Jenny解释了这个图形验证码的由来,是出于安全的考虑才增加的。

“我知道安全很重要,可是这图形验证码太伤害用户体验了,现在顾客登录过程中就要再多做一次输入,如果填错了还得重新再来一次,而且这图形验证码的风格和我们的App风格明显不匹配,另外,这图形验证码是不是也太扭曲了,我都看错好几回了……。”Jenny显然并不认同这个方案。

“风格我们可以修改,这不是还有你嘛。”老罗为难的说到:“难度高是因为现在的图像识别技术突飞猛进,简单图片验证码很容易被破解。”

“莫非就没有别的解决办法了吗?”Jenny继续问道。

“其实也有,就看公司舍不舍得花这笔钱了。”老罗接着说:“登录界面可以动态决定是否要求输入图形验证码,对于正常用户可以让他们无需输入图形验证码,对于黑客或者疑似黑客的人,就要求他们输入。”

“这听上去很好啊,另外,这和舍不舍得花钱有什么关系?”Jenny不太明白。

“要动态决定是否要求输入图形验证码这件事儿,其实就是判断当前用我们App的人是真实的顾客还是黑客。我们自己没这个判断能力,不过有提供这种服务的第三方API,只是他们都不是免费的,得花钱买。”老罗向Jenny解释到。

阿某云和腾某云等等都提供这类服务,其主要原理是,服务器在处理登录请求的时候,先尽可能多的收集该请求的上下文信息,例如登录请求的来源IP地址,时间,手机号,User-Agent等等数据,并且把这些数据传递给第三方API,由他们进行一次分析判断,并把结果返回给服务器,告诉服务器当前请求者是可信用户还是可疑用户。最终是否允许登录成功的决定权还是在服务器这边,只是借助了第三方API提供的分析结果来做判断而已。

BVJ3qyq.png!web

“我不懂技术,不过好像也听懂了的样子。”Jenny笑着说道。

“用第三方API做登录判断这事儿我拍不了板,得找领导批准,说不定还得走采购流程。”但老罗觉得这条路的方向是对的。

“走,我们去问问领导的意见,我实在受不了现在这个图形验证码。”Jenny拉着老罗径直朝着总经理办公室走去。

尾声

最终,老罗他们团队用上了某云的第三方API做登录防护,去掉了令Jenny抓狂的图形验证码。经过和业务部门的商量,验证码有效期最后缩短到了2分钟。

在这期间还出现了两个小插曲。运维部门的同事偶然间发现,应用程序日志文件里居然保存了所有用户的短信验证码,这是小李当初做调试的时候加上去的,后来忘记关掉了。好在并没有造成泄露,后来团队修复了这个问题。

另一个小插曲是,团队做了微服务架构改造,把发送短信的功能拆分出来做成了一个独立微服务,但却没有给这个新的接口设置好访问控制权限,以至于任何人在无需登录的情况下,只要向这个接口发起请求就能成功发送一条短信给任意手机,短信内容还可以自定义。这个问题在安全团队做渗透测试的时候发现的,吓得老罗浑身冒冷汗。所幸发现及时,做了紧急修复,并没有造成安全事故。

薇薇后来把短信登录的故事卡作为案例保存了起来,把安全验收标准又重新做了一次梳理,所以最终的故事卡是这样的:

故事卡-274

作为用户,我可以通过手机号和短信验证码登录,以便于我更方便的登录。

安全验收标准:

  • 短信验证码有效期2分钟
  • 验证码为6位纯数字
  • 每个手机号60秒内只能发送一次短信验证码,且这一规则的校验必须在服务器端执行
  • 同一个手机号在同一时间内可以有多个有效的短信验证码
  • 保存于服务器端的验证码,至多可被使用3次(无论和请求中的验证码是否匹配),随后立即作废,以防止暴力攻击
  • 短信验证码不可直接记录到日志文件
  • 发送短信验证码之前,先验证图形验证码是否正确(可选)
  • 集成第三方API做登录保护(可选)

没成想,一个短信登录API背后,还能牵扯出这么多事儿来。

更多精彩洞见,请关注微信公众号:ThoughtWorks洞见


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK