Java技术栈
www.javastack.cn
关注阅读更多优质文章
作者:哒哒哒哒打代码 链接:juejin.im/post/6862488906173022216
前言
上一篇文章:你的登录接口真的安全吗?
里面,我们主要聊了一下登录相关的一些安全风险,里面讨论到了一个使用手机验证码来进行登录验证的方式。
但是其实提供短信验证码、或者说任何可以触发短信发送的接口,都是存在风险的,很有可能被黑产或攻击者利用。我们今天主要聊一聊短信接口相关的风险和预防措施。
背景
短信被刷啦!短信又被刷啦!短信怎么还被刷啊!
很多人在短信服务刚开始建设的阶段,可能不会在安全方面考虑太多,理由有很多,比如:“需求这么赶,当然是先实现功能啊”,“业务量很小啦,系统就这么点人用,不怕的”等等。
有一些理由虽然有道理,但是该来的总是会来的。前期欠下来的债,总是要还的。越早还,问题就越小,损失就越低。
推荐阅读:如何设计一个安全的登录流程
所以大家在安全方面还是要重视。(血淋淋的栗子!)
正文开始
你打败了99%的人
您本次验证速度打败了99%的人
最简单的方式就是增加验证码啦,每次用户主动获取短信前,都需要先完成图片验证码/滑动验证码的校验。
示例代码:
代码语言:javascript复制if not validate_captcha():
return error('验证码错误')
send_message()
实现简单,也可以防止一部分攻击者,但是不管是图片还是滑动验证,都是可以被破解的,一但被破解,那你的短信接口相当于对攻击者毫不设防,非常危险。
没有人可以一直发短信
您的短信发送已达上限
一般普通的验证码类型一般的使用场景都是登录、修改密码、注册等场景,一般来说都不是高频操作,所以我们可以针对单个用户和全局做数量限制:
- 比如一个手机号1小时内只允许调用5次,一天内只允许调用20次。
- 另外再根据历史趋势,对全局设置限制。比如前30天每天验证码短信量总量都在30万上下浮动,那我们可以设置每天的短信调用上限为40万,超出则进行限制、告警。
示例代码:
代码语言:javascript复制day_limit = get_from_config(day_limit)
phone_limit = get_from_config(phone_limit)
if not validate_captcha():
return error('验证码错误')
# 发送数量可以存在redis
if total_send_count() > day_limit:
# do_something 告警
return error('短信接口已经达上限')
if phone_send_count() > phone_limit:
return error('你的短信发送已达上限,请稍后再试')
send_message()
上面这种上限的方式一定程度上可以在被攻击的情况下及时止损,但是也有小概率误杀或者局部影响整体的情况出现,所以需要看实际影响使用。
比如前几天趋势都是正常的,但是某天进行促销或活动,又或者是任何突发的流量进来,这时候这种全局上限的方式会影响正常用户的使用。 再比如说,用户当天可能由于各种原因,一段时间内某个操作频繁的获取验证码,导致短信验证达到上限,会影响到他所有短信接口都无法使用。
一个萝卜一个坑
您的短信内容未备案
上面我们是根据用户和全局做管控,实际上我们还可以根据短信的使用场景,通过短信模板来做管控。
对接过三方短信供应商的都知道,绝大部分三方供应端都是需要提供短信模板备案,才可以正常发送的。我们服务本身也可以,或者说也需要使用模板做管控。
- 根据模板来做发送量的限制
- 我们可以根据不同模板的发送量趋势来做对应的短信告警
- 某些场景下,不同模板的短信可以配置不同的供应商
示例代码:
代码语言:javascript复制def send_message(template_code, params = {}):
"""
发送短信不再是直接发送短信内容,而是通过模板来发送
:param template_code 注册过的短信模板编码,比如 login_code,可以根据code查询到对应的模板内容:
您好,您的验证码为:${code},xxxxxxx
:param 模板变量,比如: {code: 123456}
"""
# 模板内容和一些其它参数
template = get_template_by_code(template_code)
# 验证模板是否存在
if not template:
return error()
# 是否已达上限,全局或指定手机号
if template_limit(template_code, phone):
return error()
# 发送短信
do_send_message(phone, template)
log_template(phone, template)
# 注册短信模板,一般是在管理后台提供,需要人工审核
def register_template():
pass
增加短信模板更多的是让我们可以做到更全、更细粒度的管控,同时可以为后续的分析或风控提供数据依据。
实名制
请先进行实名认证后再进行该操作
我们可能会有一些非验证码类型的短信是可以被用户触发的,有可能是直接触发,也有可能是间接触发。
举个栗子:比如某个产品或活动新增了一个邀请功能,用户在登录后,通过某个按钮或功能,邀请用户注册或回归之类的行为。
这个时候,用户就可以间接的触发短信,比如:${nickName}邀请您注册/回归XXX产品。那么这种接口很可能被攻击者利用,比如把nickName修改为攻击者想要发送的内容。
这种情况下我们首先肯定是在活动的设计上就需要评估风险和有对应的预防措施,同时在短信服务这块怎么防御呢?
我们可以针对这些可能存在高风险的短信模板做特殊的处理,比如需要发送该模板内容,需要用户一定进行了实名认证之后才可以操作。
示例代码:
代码语言:javascript复制# 模板内容和一些其它参数
template = get_template_by_code(template_code)
# 其它校验略
validate(phone, template)
risk_level = template.risk_level
# 这里大家可以想想这种场景下可以使用什么设计模式~~
if risk_level > 60:
if not certification(phone):
return error()
if risk_leve > 30:
# do some_thing
pass
...
send_message(phone, template)
关于内容这块,理论上所有用户输入并且可以对外展示的内容,都是需要做内容检测的。这块这里不展开,大家可以自行了解,或者关注公众号Java技术栈搜索阅读更多。
风控?风控!
检测到您本次操作存在风险,操作被拒绝
当我们的业务越来越大,并且面向的用户越来越复杂的时候,上面我们提到的这些简单的规则很难应付业务或用户的复杂多变。
这时候就需要通过数据分析的方式,来动态的、实时的调整我们的规则和处理方式,以及提供风险分析、预测等功能。这时候我们可能需要有一个独立的风控服务。
做过银行业务的小伙伴可能会接触得比较多(我自己没有做过银行系统),可以给点经验和建议 - -!
那我们看到上面有看到,针对不同的模板的场景来确定风险等级,然后来做不同的操作,这块其实就涉及到风控相关了。只是比较初级,比如风险等级如何确定?每个风险等级需要做什么样的事情?如何进行动态的配置等等。
举个栗子:
- 我们可以收集用户的行为轨迹(注册时间、登录次数、页面访问情况等)来分析一个用户,确定用户的风险等级,再决定他可以发送哪些短信
- 根据模板的历史趋势,来自动判断相应短信模板的合理范围,如果达到上限,则认为存在风险操作,可以做对应的处理
- 配置相应的规则,如果某个模板的短信内容(和模板的区别是,变量一直没有变化)重复N次则认为存在风险
- 等等
风控不仅仅适用于短信接口的风险识别,还包括注册、登录、支付操作等等。这个也不是一蹴而就的,需要长时间的积累和建设。
比如上面说到的用户行为轨迹和模板趋势,都需要有全面的埋点和数据平台作为支撑。还有如果业务要求比较高,还需要开发适合自己业务的规则引擎。但是当风控系统建设起来之后,效果也是明显的!
对于未登录的用户,实际上也可以在用户访问页面时通过一系列的属性生成一个唯一token,用于标识这个用户。这样即使用户未登录,也会处于风控服务的监控下。关于token的使用教程可以关注公众号Java技术栈搜索阅读更多。
结论
上面我们简单说了一下如何防止短信接口被刷,这一块的安全不仅涉及到金钱(我见过短短10分钟被刷几万块、几十万的都有),也会影响到我们产品/品牌的声誉。
想象一个用户收到了一批垃圾短信,但是短信签名带的都是我们的签名,这对公司的品牌影响有多大!
希望这篇文章可以帮助到大家,愿天下没有被盗刷的短信!