前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一次短信验证码“撞库”,发生的惨案!!!

一次短信验证码“撞库”,发生的惨案!!!

作者头像
Mandy的名字被占用了
发布2021-12-27 08:50:57
2.2K0
发布2021-12-27 08:50:57
举报

讲故事

故事要从一天中午开始说起,同事小张正在午休,睡的正酣,突然被产品经理给叫醒。运营反馈,大量用户打客服电话,说到没有注册平台却收到成功注册平台账号的短信内容。

小张心里,瞬间有一万只草泥马在奔跑。心想怎么会出现这种情况呢?马上打开短信发送平台,发现一分钟内有几万条注册短信发送。小张心里瞬间慌了,怎么会出现这种情况呢?小张边找到发送短信和注册的代码,发现也没有啥问题呀,便找到我协助帮助查看。虽然我心里也不爽,但无奈还是帮着查看,没几分钟变发现其中存在严重的bug。下面就来讲讲这次事故发生的原因。

注册验证逻辑

要说到这次事故,我们先来看看通过短信验证码注册的逻辑。下面两张图,就很好的说明了代码的逻辑。

a. 用户点击页面发送短信按钮,想服务端发起发送验证码的请求。

b. 服务端接收到之后,先会验证是否存在验证码,存在就提示用户60s内不能进行重新获取。如果没有,则调用短信服务,发送成功之后,就把验证码放在缓存中(Redis)并设置一个过期时间(例如60s)。

c. 如果短信发送失败,则告知客户端,发送失败,进行重新发起。

代码语言:javascript
复制
function sendCode(string $mobile)
{
  $redisClient = new Redis();
  // 1. 先验证缓存中是否存在
  if (!$redisClient->exists($mobile)) {
    $code = "xxxx";// 随机生成一个验证码
    $smsService = new SmsService();
    // 调用短信服务,进行验证码发送
    if ($smsService->sendSmsCode($mobile)) {
      // 将验证码添加到缓存中
      if ($redisClient->set($mobile, $code, 60)) {
        echo "设置成功!";
      }
    }
    echo "短信发送失败!";
  }
  echo "已发送验证码,一分钟内不能进行重复发送!";
}

// 调用短信验证码方法
sendCode((string)156xxxx2305);

上面只是一个伪代码,在实际中要考虑添加缓存和发送短信验证码的一致性,不能出现给用户成功发送了验证码,但是缓存每天就成功。这样就会出现,验证时误判验证码错误。

a. 当用户接收到短信验证码之后,点击页面注册按钮。前端会把验证码和手机号一并发送到服务端。

b. 服务端根据手机号去查询缓存(Redis)中是否存在验证码。

c. 如果存在验证码,则进行对比。看缓存中的验证码和提交的验证码是否一致,如果一致就进行注册。不一致就返回客户端,验证码错误。

d. 短信验证码一致,用户账号自动注册的同时把对应的短信验证码进行删除。

上面的两张图就是同事小张的一个代码逻辑,大家看到这里,可以先想想这种逻辑是不是正确的?为什么会出现文章开头说的情况?你平常在写短信验证码的服务时,是不是这么写的?

代码语言:javascript
复制
function verifyCode(string $mobile, string $code)
{
  $redisClient = new Redis();
  // 1. 直接查询缓存中是否存在验证
  $cacheCode = $redisClient->get($mobile);
  if (empty($code)) {
      if ($code == $code) {
        // 验证成功之后删除缓存中的验证码信息
        $redisClient->del($mobile);
        echo "验证成功!";
      }
      echo "验证码错误,请重新输入!";
  }
  echo "短信验证码不存在!";
}

// 调用短信验证码方法
verifyCode((string)156xxxx2305, (string)$code);

问题发现

看到这里,你可能发现其中的问题,并能总结出这次事故的原因。或许你还没有发现,下面我就来为大家揭秘一下具体的原因。这个问题修复之后,个人用这样的方式实现了一次,也发现会绕开短信验证。

接下来,就分析一下发送和验证存在的逻辑。a. 从正常的逻辑来看,发送短信验证码的逻辑,这样写是没有问题的。先验证缓存,在发送短信并添加缓存。

b. 但是缺乏验证。这里只验证了手机号,却没有一个全面的验证,例如IP限流、IP发送次数、IP黑名单、某个时段短信发送服务是否存在异常(例如突然大量增加)等等。在后来项目复盘也发现这个问题,同一个IP在一个时段,大量请求发送验证码的请求。推测是攻击者,购买的的手机号,进行发送,否者也不会出现部分用户投诉的情况。

c. 接下来,就是验证环境。验证按照上面的逻辑,其实很容易发生问题的。因为发送验证码是攻击者在操作,当验证环节,攻击者可以使用轮询的方式进行验证码撞库。根据验证码的位数,依次去一个一个的调用验证接口,如果对就注册成功,如果不对在发起下一次请求。

问题解决

通过上面的分析,后来针对系统做了紧急的迭代。发现还是存在大量的攻击者,但是没有发现一个成功的攻击操作。这里总结一下,在这次解决中的一些环节。

a. 短信发送环节。在网管层做了验证、限流。IP异常记录、IP黑名单。存在异常的情况下,依赖拉黑。

b. 短信服务监控,如果出现某一时段,大量发送短信服务进行异常报警并做限流控制。这里根据系统的实际业务处理,像系统营销活动,可能存在真实的峰值期。

c. 日志监控。日志发送监控、客户端请求监控等等。一旦发现异常,就进行报警,针对请求日志做分析并处理。我们在实际过程中,发现了大量的异常IP或者异常的手机号段。针对这批号段做了异常禁用、IP异常拦截。

d. 验证环节,做错误次数验证、异常IP封禁、异常号码封禁。一旦发现某一个号码出现三次验证错误,就进行封禁一分钟才能重新获取验证码。如果发现某一个手机号请求次数多,也同样当做异常号码处理。

总结

在日常开发中,我们尽量做好系统的日志监控、异常拦截等情况。可能业务量小,一直不会出现问题。但是业务量一上来,就是各种问题,因此就需要在前期就有一个完善的策略来应对。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 卡二条的技术圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 讲故事
  • 注册验证逻辑
  • 问题发现
  • 问题解决
  • 总结
相关产品与服务
验证码
腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档