1、常见问题1.1、验证码简单且有效时间太长短信验证码太简单同时短信验证码时效风险:一般4位的短信验证码可以在1-2分钟内暴力破解成功。所以短信验证码内容要结合字符和数字,比较严格的场景可以添加特殊字符。同时合理设置短信验证码有效时间。
1.2、响应包返回短信验证码用户点击发送短信后的响应包中返回了短信验证码。攻击者拦击返回包后可以直接获取短信验证码。
1.3、短信炸由于发送短信接口没有设置频次上限或者业务逻辑上没有控制接口请求频次,导致攻击者可以无限制的给同一个用户或者不同的用户发送短信。
1.4、短信内容前端可控用户可以通过前端参数修改短信内容。
2、安全设计2.1、短信验证码结合图形验证码考虑到用户体验可以在第一次发送短信后前端“锁定”发送按钮。密码错误或者短信错误次数过多后弹出图形验证码。图形验证码结合短信验证码是目前非常普遍的防御机制。图形验证码的具体选用类型有文字(字母数字)验证码、滑动验证码、选字验证码等,可根据具体的业务场景来选取。
2.2、短信发送总次数限制限制手机号在特定时间段内获取短信验证码次数的上限。采用这种策略时在产品设计过程中,要考虑几种情况
谨慎定义上限值。根据业务真实的情况,甚至需要考虑到将来业务的发展定一个合适的上限值,避免因用户无法收到短信验证码而带来的投诉。
谨慎定义锁定时间段。可以是24小时,可以是12小时、6小时。需要根据业务情况进行定义。
2.3、发送短信时间间隔设置同一个号码重复发送的时间间隔,一般设置为60-120秒。该手段可以在一定程度上防止短信接口被恶意攻击,且对用户体验没有什么伤害。但是不能防止黑客更换手机号进行攻击。
2.4、短信验证码内容使用单纯的数字或字母作为短信验证码内容,则容易出现短信验证码有效期内被暴力破解的问题。所以短信内容要使用数字和字母结的结合。
2.5、设置有效时间和有效次数由于发送验证码存在发送延迟的现象,如果用户申请发送验证码之后未收到验证码,用户就会短时间多次申请验证码,在有效期内申请的验证码相同,防止用户申请多次验证码后成功之前的验证码但校验失败。但这种方式只适用于一些不算太严谨的场合。如果客户在转账阶段,为了保证安全的同时优化用户体验,在短信验证码有效时间(2-3分钟)内,错误3次后销毁短信验证码。防止短信验证码的暴力破解;
2.6、使用令牌服务在第三方平台上运行的服务可以使用第三方提供的短信令牌来登录,但为了使用令牌服务需要让客户安装其他APP、同第三方共享客户敏感数据或者持有硬件等额外操作时请审慎考虑引入改方案。
3、校验伪代码修改密码场景
发送短信验证码
================请求、响应===================
//请求
https://a.com/api/v1/SendSMSCode/
ua:xx
xx:xx
Content-Length:xx
{
"phonenumber":RAS(13011113333),
//图形验证码
"text":"EAS4",
//图形验证一一映射的uuid
"uuid":"xxxxxxxxxxxxx"
"timestap":"1705286217"
}
header:xx
{
"sendstatus":"ok"
}
================后端--发送短信验证码===================
//校验
BOOL APICheck(phonenumber,text,uuid,timestap){
//校验是否满足期望参数格式,避免畸形数据,特殊字符
if( regex_check(phonenumber,text,uuid,timestap) == Flase)
return False;
//检查图形验证码是否正确,是否被重复利用
if(cheakpicturetext(picturetext,uuid) ==Flase)
return False;
return True;
}
BOOL cheakpicturetext(picturetext,uuid){
if(GetTextByUUID(uuid) == picturetext ){
//
//
Mutex.Lock()
//成功删除redis里的图形验证码则返回True
//删除失败redis里的图形验证码则返回False
// ----------------------⚠️注意⚠️----------------------
// redis删除时要用del而不是unlink,确保同步删除,防止多线程并发情况下打时间差
// ----------------------⚠️注意⚠️----------------------
BooL result=DeletePicturetextInRedis(uuid)
Mutex.Unlock()
if(result == True){
return True:
}
else{
return False;
}
}
else{
return False;
}
}
//给用户发送短信验证码
Void SendSMSCode(phonenumber,token){
generate_and_send(phonenumber)
//关联数据写入redis
userid=getuidbytoken(token)
WritetoRedis(smscode,userid)
}
修改密码操作
================短信验证码===================
//请求
https://a.com/api/v1/changepwd/
ua:xx
xx:xx
cookie:token=xxxx
Content-Length:xx
{
"oldpwd":RSA("13011113333"),
"newpwd":RSA("111"),
"smscode":"12rt45",
"timestap":"1705286217",
"sign":"xxxxx"
}
================后端-===================
BOOL APICheck(phonenumber,text,uuid,timestap){
//校验是否满足期望参数格式,避免畸形数据,特殊字符
if( regex_check(phonenumber,text,uuid,timestap) == Flase)
return False;
//校验是否用户是否登录
if(token not in Redis)
return False;
//校验数据是否篡改
if( ECDSACheckSign(smscode,timestap) != sign )
return False;
//检查短信验证码是否正确
if(checksmscode(smscode) ==Flase)
return False;
return True;
}
BOOL checksmscode(smscode,token){
if(smscode not in redis){
return False;
}
else{
userid1=getuidbytoken(token);
userid2=getuid_by_smscode_from_redis(smscode);
if(userid1 == userid2){
deletesmscode(smscode);
}
else
return False;
}
}