欢迎光临

木子屋

标题: 不就是个短信登录API嘛，有这么复杂吗？[转]

作者: dnawo

日期: 2020-02-03 03:06 PM

分类: Web编程

内容: 引子
引用内容上联：这个需求很简单
下联：怎么实现我不管
横批：今晚上线
Part 1：暴力破解
早上开完站会，小李领了张新卡，要对登录功能做升级改造，在原来只支持用户名密码登录模式的基础上，新增手机号和短信验证码登录。
业务分析师薇薇早就准备好了故事卡，并且也考虑到这个功能的特殊性，除了平常的业务性验收标准外，还专门添加了一些和安全有关的条目。这张故事卡看上去是这样的：
引用内容故事卡-274
作为用户，我可以通过手机号和短信验证码登录，以便于我更方便的登录。
安全验收标准:
短信验证码有效期5分钟
验证码为4位纯数字
每个手机号60秒内只能发送一次短信验证码
小李看到故事卡中提到，验证码长度只有4位而且还是纯数字，隐约觉得强度有些不够，担心万一黑客来个多线程并发请求，或者拿一个集群来暴力登录，有可能会赶在有效期内破解出合法的验证码。
小李把自己的担心讲给了业务分析师薇薇，并且建议把验证码长度增加到6位，或者在保持4位长度的情况下，改为数字和字母的组合，目的是增加验证码复杂性，提高暴力破解的门槛。
薇薇听了这两种选择后直摇头，说道：“我理解你的担心，可是业务部门那边的需求很明确，就是为了优化用户登录体验，所以才决定做手机号和验证码登录，如果把验证码弄的这么复杂，那用户体验也好不到哪里去，不符合这个故事卡的初衷啊。”
“对于用户而言，4位数字验证码确实好记好填，可是对于黑客而言，就能很容易的完成暴力枚举，理论上最多1万次请求就能遍历完所有的验证码，更何况黑客没那么倒霉，要尝试到第1万次才猜对……”，小李说道。
为了满足用户体验而在安全性上做出妥协，这种事情小李觉得自己无法说服自己，正准备掏出纸和笔跟薇薇做详细解释黑客攻击手段的时候，团队技术负责人老罗听见了他们两的讨论，慢慢脱下帽子，摸了摸正在朝着“地中海”模式演进的乌黑的秀发，说道：“那啥，服务器在验证登录请求的时候，不管验证码匹配还是不匹配，存在Redis里的验证码只要被取出来就立即作废，根本不给黑客暴力破解的机会。”
小李的团队已经搭建好了Redis，用来存储登录过程中发给用户的短信验证码，是一个手机号和验证码的键值对。
“对啊”，小李感觉眼前一亮，说道，“服务器在比对请求中的验证码和Redis中保存的这个用户手机号所对应的验证码的时候，如果发现不匹配，那依然还是直接把Redis中的这个验证码作废。这样黑客发第二次登录请求的时候，会因为Redis中找不到对应的记录而登录失败。这样既避免了暴力枚举攻击，同时也不再需要增加验证码的强度，导致用户体验的下降了。”
小李建议把刚才的讨论结果写到故事卡里，而薇薇提议能否不要立即作废：“万一用户输入验证码的时候手滑输错了，岂不是要等几十秒的时间再重新发第二个验证码？”
“可以做到验证码3次输入错误后再作废吗？”薇薇问到。 “可以的，这个不难”小李坚定的回答到。 “好，那我们加一条安全验收标准吧”，薇薇边说边修改了故事卡，新增加了一条：
引用内容保存于服务器端的验证码，至多可被使用3次（无论和请求中的验证码是否匹配），随后立即作废，以防止暴力攻击
“对了小李”，老罗喝了口咖啡，最近连续的加班让老罗感觉很疲惫，只能靠喝咖啡强打精神。“60秒内只能发1次短信那条，别忘了前后端都要做检查。”
“知道知道，前端做不做都无所谓，关键是在后端要做限制。”小李连连点头。
“好，那就这么做，去忙吧”。老罗转身坐下，正准备继续刚才被打断的工作，此时一个念头快速在脑海里一闪而过，老罗在电脑上打开短信登录的这张故事卡，从头到尾又看了一次，最后目光停留在“短信验证码有效期5分钟”这条验收标准那里。
“短信每60秒发一次”，老罗心想：“但有效期是5分钟，那第61秒的时候假如又请求发送一次验证码，这时第一次发送的验证码还没过期，服务器端该怎么处理这第二个请求会比较稳妥呢？
显然，第二个验证码直接覆盖掉第一个会更加安全，也就是至始至终都只有一个处于有效状态的验证码，但这会不会给用户带来困惑？毕竟偶尔还是有手机信号不好，等了1分钟多钟之后才收到第一个验证码的情况。
如果不替换而是追加验证码呢？最极端的情况是会出现一个手机号有5个有效验证码的情况，会增加黑客暴力破解的成功概率。不过因为一个验证码最多只能被使用3次，之后就被作废了，所以实际上黑客暴力破解的难度依然很高。
总的来说，直接覆盖的做法用户体验不佳但更安全，依然有效的做法用户体验更好但相对而言安全性略有降低。”
经过反复思考后，老罗最终选择保留验证码5分钟有效期的设置。
Part 2：防不胜防
短信验证码登录的功能上线后，运行状态一直比较平稳，然而这种平静的氛围被一通电话打破了。
“喂，对，是我”，老罗桌上的电话响了，他忙着写代码，歪着脖子用肩膀和脸夹住话筒说道：“是客服部啊，有什么事我可以帮忙的？”
“是这样，我们今天突然收到很多顾客打来的电话，抱怨说收不到短信验证码，登录不了账户，他们基本都是新用户，只有用手机注册的账号，没有用户名密码，所以也不能用原先的用户名密码去登录账号。我们只好让顾客再等会儿试试，可能是信号不好，但后来他们反馈说还是收不到我们的短信，而且只是收不到我们的短信，所以，你们那边能帮忙看看是怎么回事吗？”电话那边一口气讲了一堆话。
“还有这种事，行，我知道了，我们马上调查分析一下。”老罗刚挂断电话，运维部的同事过来找到老罗，说短信配额今天消耗得很厉害，已经触发了2次告警了，运维同事做了一下简单的分析，发现早上10点和下午2点左右有两批次大量发送登录短信验证码的请求，但又没有观察到对应的后续登录请求，判断可能是被黑客攻击了，于是临时性的屏蔽了攻击来源IP地址的访问。
“来找你就是想和开发团队共同调查下这个问题，看接下来怎么处理会比较好。”运维部的同事说道。
老罗觉得这个事和刚刚接到的客服部门说的是同一件事，便把刚才电话里听到的信息和运维同事讲了一遍。
“这更能证实是黑客攻击了，而且看来他们的目标应该不是暴力登录，而是故意消耗短信发送配额，一旦配额被用完，用户就无法正常登录，也算是某种程度上的拒绝式服务攻击了。” 运维部的同事说完看向老罗。
老罗若有所思的说道：“没想到他们还能这么玩儿。我们目前只限制了一个手机号60秒内发一次验证码，却没有应对大量不同手机号的情况。”
“那现在怎么处理比较好呢？虽然临时禁用了攻击者的IP，但我们担心会误伤真实用户，而且黑客也可能会变换IP来继续进行攻击。”运维同事继续问道。
“有办法，在发短信验证码之前先要求输入图形验证码。” “也就是说，通过图形验证码来检查请求来源到底是真实用户还是自动化的脚本。那你们什么时候能做好上线？” “我现在就加”，老罗还没说完就已经开始写代码了：“一会儿弄完紧急上线。” “行，我回去安排一下，咱们运维部全力配合。”
“看来之前那张故事卡里的安全验收标准还差了一条”，老罗自然自语道：“如果加上一条图形验证码的要求恐怕就不会出这个事儿了。”
引用内容发送短信验证码之前，先验证图形验证码是否正确
Part 3：权衡
“喂喂喂，这搞的什么鬼？”用户体验设计师Jenny抓住路过的老罗说：“我不过就是休了两天假，回来之后怎么发现登录这里多了个图形验证码出来？”
老罗向Jenny解释了这个图形验证码的由来，是出于安全的考虑才增加的。
“我知道安全很重要，可是这图形验证码太伤害用户体验了，现在顾客登录过程中就要再多做一次输入，如果填错了还得重新再来一次，而且这图形验证码的风格和我们的App风格明显不匹配，另外，这图形验证码是不是也太扭曲了，我都看错好几回了……。”Jenny显然并不认同这个方案。
“风格我们可以修改，这不是还有你嘛。”老罗为难的说到：“难度高是因为现在的图像识别技术突飞猛进，简单图片验证码很容易被破解。”
“莫非就没有别的解决办法了吗？”Jenny继续问道。
“其实也有，就看公司舍不舍得花这笔钱了。”老罗接着说：“登录界面可以动态决定是否要求输入图形验证码，对于正常用户可以让他们无需输入图形验证码，对于黑客或者疑似黑客的人，就要求他们输入。”
“这听上去很好啊，另外，这和舍不舍得花钱有什么关系？”Jenny不太明白。
“要动态决定是否要求输入图形验证码这件事儿，其实就是判断当前用我们App的人是真实的顾客还是黑客。我们自己没这个判断能力，不过有提供这种服务的第三方API，只是他们都不是免费的，得花钱买。”老罗向Jenny解释到。
阿某云和腾某云等等都提供这类服务，其主要原理是，服务器在处理登录请求的时候，先尽可能多的收集该请求的上下文信息，例如登录请求的来源IP地址，时间，手机号，User-Agent等等数据，并且把这些数据传递给第三方API，由他们进行一次分析判断，并把结果返回给服务器，告诉服务器当前请求者是可信用户还是可疑用户。最终是否允许登录成功的决定权还是在服务器这边，只是借助了第三方API提供的分析结果来做判断而已。
“我不懂技术，不过好像也听懂了的样子。”Jenny笑着说道。
“用第三方API做登录判断这事儿我拍不了板，得找领导批准，说不定还得走采购流程。”但老罗觉得这条路的方向是对的。
“走，我们去问问领导的意见，我实在受不了现在这个图形验证码。”Jenny拉着老罗径直朝着总经理办公室走去。
尾声
最终，老罗他们团队用上了某云的第三方API做登录防护，去掉了令Jenny抓狂的图形验证码。经过和业务部门商量，验证码有效期最后缩短到了2分钟，长度改成了6位纯数字。
在这期间还出现了两个小插曲。运维部门的同事偶然间发现，应用程序日志文件里居然保存了所有用户的短信验证码，这是小李当初做调试的时候加上去的，后来忘记关掉了。好在团队立即修复了这个问题，没有进一步演变成数据泄露事故。
另一个小插曲是，团队做了微服务架构改造，把发送短信的功能拆分出来做成了一个独立微服务，但却没有给这个新的接口设置好访问控制权限，以至于任何人在无需登录的情况下，只要向这个接口发起请求就能成功发送一条短信给任意手机，短信内容还可以自定义。这个问题在安全团队做渗透测试的时候发现的，吓得老罗浑身冒冷汗。所幸发现及时，团队做了紧急修复，并没有造成安全事故。
薇薇后来把短信登录的故事卡作为案例保存了起来，把安全验收标准又重新做了一次梳理，所以最终的故事卡是这样的：
引用内容故事卡-274
作为用户，我可以通过手机号和短信验证码登录，以便于我更方便的登录。
安全验收标准:
短信验证码有效期2分钟
验证码为6位纯数字
每个手机号60秒内只能发送一次短信验证码，且这一规则的校验必须在服务器端执行
同一个手机号在同一时间内可以有多个有效的短信验证码
保存于服务器端的验证码，至多可被使用3次（无论和请求中的验证码是否匹配），随后立即作废，以防止暴力攻击
短信验证码不可直接记录到日志文件
（可选）发送短信验证码之前，先验证图形验证码是否正确
（可选）集成第三方API做登录保护
没成想，一个短信登录API背后，还能牵扯出这么多事儿来。
原文链接：https://mawei.blog/post/sms-login-security/

+ 查看当前日志评论 (0)

登录

────────────

木子屋

PJBlog3 v3.2.9.518 Inside.

Processed In 1.094 ms

返回

标题: 不就是个短信登录API嘛，有这么复杂吗？[转]

你没有权限发表评论

────────────

木子屋

PJBlog3 v3.2.9.518 Inside.

Processed In 1.094 ms

返回

暂无评论

返回

────────────

木子屋

PJBlog3 v3.2.9.518 Inside.

Processed In 1.125 ms

返回