游客发表
去年秋天,验证我帮朋友测试他公司刚上线的码绕码找用户系统。注册流程没什么特别,过密输入手机号,回漏收验证码,洞靶填进去,验证设置密码。码绕码找一切正常。过密直到我点开“忘记密码”的回漏链接——页面跳转到一个输入手机号的表单,提交后要求填写短信验证码,洞靶然后直接进入重置密码界面。验证没有旧密码验证,码绕码找没有安全问题,过密没有邮箱确认。回漏就一个验证码。洞靶
我当时用两个手机号做了个简单实验。A手机号走正常找回流程,收到验证码,填进去,重置密码成功。B手机号在输入验证码的环节,我故意填了A手机号收到的那个验证码。页面返回“验证码错误”。看起来没问题。但接下来我做了第三步:在B手机号的验证码输入框里,填入A手机号收到的验证码,同时用抓包工具把请求里的手机号参数从B改成A。结果密码重置成功,账户变成了A手机号的。
这就是典型的验证码绕过。逻辑漏洞不在于验证码本身被破解,而在于服务端校验验证码时,没有把验证码和请求中的手机号做绑定。它只检查“这个验证码是否有效”,不检查“这个验证码是不是发给当前这个手机号的”。攻击者用自己的手机号走流程,拿到一个有效验证码,然后把请求里的手机号替换成受害者的,验证码照样通过。密码重置链接就发到了攻击者手里。
靶场环境里复现这个漏洞更直观。我搭了一个简易的PHP页面,模拟短信验证码的发送和校验。数据库里存两张表:一张记录手机号和对应的验证码,另一张存用户账户。发送验证码时,用rand(100000,999999)生成六位数,存进第一张表。校验时,代码只做了一件事:SELECT * FROM sms_codes WHERE code = '用户输入'。如果查得到,就认为验证通过。没有AND phone = '请求中的手机号'这个条件。
攻击流程在靶场里跑一遍:注册两个账号,一个当受害者,一个当攻击者。攻击者触发找回密码,收到验证码。打开Burp Suite,拦截提交验证码的请求。把请求体里的phone=攻击者手机号改成phone=受害者手机号,验证码保持不变。放行。页面跳转到重置密码,账户名显示的是受害者的。输入新密码,提交。用受害者手机号加新密码登录,成功。
这个漏洞的修复方案不复杂。服务端在校验验证码时,必须同时匹配手机号和验证码。SQL语句改成SELECT * FROM sms_codes WHERE phone = '请求手机号' AND code = '用户输入'。另外,验证码使用后立即失效,设置五分钟过期时间。请求里的手机号不能由客户端完全控制,应该从会话或令牌里取。很多开发者在写找回密码功能时,注意力放在“验证码有没有填对”,忽略了“这个验证码属于谁”。
我见过更隐蔽的变种。有的系统把验证码校验和手机号校验拆成两步:先校验验证码,通过后返回一个临时token,再用token加手机号去重置密码。但token里如果只编码了“验证通过”这个状态,没有绑定手机号,攻击者拿到token后照样能替换手机号。还有的系统用JWT做临时凭证,payload里写{ "verified":true},不写手机号。一样能绕。
靶场代码我放在GitHub上,不到两百行。跑起来只需要PHP和MySQL。每次给新人讲逻辑漏洞,我都先让他们在这个靶场里把攻击链走通。走通之后再去看修复方案,理解会深很多。验证码绕过不是什么高深技术,它考验的是开发者有没有把“谁”和“什么”绑在一起思考。短信验证码本身没问题,问题出在验证码和用户身份的关联上。
友情链接