一个六位数的验证码,我拿到了全站用户的明文密码

admin 2026-03-03 08:29:54 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文剖析短信验证码漏洞挖掘技巧,列举回显、轰炸、爆破及逻辑绕过等七类缺陷,结合真实SRC案例展示攻击链组合。文中提供自动化检测脚本与防御自查清单,强调漏洞组合提升危害等级,建议开发者严控服务端校验与频率限制,测试者注重深挖与联动利用。 综合评分: 88 文章分类: WEB安全,渗透测试,SRC活动,漏洞分析,实战经验


cover_image

一个六位数的验证码,我拿到了全站用户的明文密码

原创

逍遥喝醉后写! 逍遥喝醉后写!

逍遥子讲安全

2026年2月18日 00:06 广东

一个六位数的验证码,决定了你是月入500还是月月5000。

2024下半年年某头部SRC年度报告显示:验证码相关漏洞占所有逻辑漏洞的37%,但平均奖金只有800元——不是因为这个洞不值钱,而是90%的人只挖到了最浅的那层。

我见过最值钱的短信验证码漏洞,是一个“置空验证码”导致的任意用户登录,奖金8000元。也见过最亏的,一个“验证码回显”提交者只拿了500元——他不知道这个漏洞可以组合成全站用户数据泄露

短信验证码是SRC里出现频率极高、但价值方差极大的漏洞类型。差距不在漏洞本身,在你能走多深

本文将首次完整公开我的短信验证码深度狩猎体系——从6大类漏洞、12个实战案例到自动化武器库,全是干到拧不出水的干货。

第一章 验证码漏洞的“七宗罪”

1.1 验证码回显:开发者的“低级错误”

本质:服务器在返回“发送成功”的响应时,直接把验证码明文包含在返回包中。

挖掘方法

  • 点击“获取验证码”,用Burp抓取响应包
  • 在JSON/XML响应中搜索codeverifyCodesmsCodevCode等字段
  • 重点关注登录、注册、密码找回接口

案例:某平台登录页抓包,响应包中直接返回:

{  "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。

挖掘思路

  1. A账号下单,获取订单ID1
  2. B账号下单,获取订单ID2
  3. 尝试用A的验证码操作B的订单

实战案例:某电商平台,验证码只校验是否正确,未校验与订单ID的从属关系。导致攻击者可以用自己的验证码确认他人的订单,实现越权支付

1.6 响应包篡改绕过

场景:前端根据后端返回的statuscode判断验证码是否正确。

挖掘方法

  1. 输入错误验证码,拦截响应包
  2. "code": 400改为"code": 200
  3. 放包,观察是否绕过

进阶技巧:返回包中直接包含token,可替换为其他用户的token实现登录。

1.7 验证码复用(多阶段校验)

场景:一个验证码可用于多个操作(如注册和登录),且不失效。

挖掘方法

  1. 注册时获取验证码123456
  2. 登录其他账号时,使用同一个验证码123456
  3. 若成功,则存在复用漏洞

第二章 实战案例库(完整攻击链)

【案例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,参数mobilesmsType=2
  2. 测试单手机号限制:发送3次后提示频繁
  3. 遍历smsType参数(1-20),发现smsType=1,2,3,5,7,11均返回成功
  4. 组合利用:一次请求可发6条短信
  5. 同时发现订单支付时验证码也走同一接口,订单ID可遍历
  6. 编写脚本循环生成订单ID,每个订单可发5次验证码
  7. 结果:10分钟内对单手机号发送200+条短信

漏洞价值:短信轰炸导致厂商短信费用损失(每条0.05元,200条=10元),看似不大,但攻击者可无限消耗,且可针对所有手机号

【案例3】验证码回显+信息泄露+密码重置

目标:某政务系统 耗时:20分钟 奖金:1500元

攻击路径

  1. 注册页面抓包,点击获取验证码
  2. 响应包中直接包含:
json{  "success": true,  "verificationCode": "285731"}
  1. 直接输入验证码,注册成功
  2. 进一步测试:密码找回接口同样回显验证码
  3. 输入任意手机号,获取其验证码,重置密码成功
  4. 登录该账号,查看用户信息(含身份证、住址)

漏洞组合:验证码回显 + 密码重置

【案例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. 尝试支付订单后,余额扣减成功,但订单属于他人
  8. 最终可造成任意用户资金损失

【案例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 短信轰炸检测脚本

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个错误

  1. 验证码明文回显:调试代码未删除
  2. 无频率限制:单手机号/单IP可无限发送
  3. 验证码不绑定身份:A的验证码可验证B的请求
  4. 验证码有效期过长:30分钟甚至永久有效
  5. 错误次数无限制:可暴力破解
  6. 前端校验依赖:改响应包就绕过
  7. 验证码可复用:一个验证码多次使用

4.2 企业自查清单

  • 返回包中是否包含明文验证码?
  • 单手机号/单IP是否有发送频率限制(如60秒1次,每日5次)?
  • 验证码有效期是否≤5分钟?
  • 验证码错误次数是否限制(通常3-5次)?
  • 验证码是否绑定用户会话(session或token)?
  • 验证码是否绑定操作对象(订单ID等)?
  • 验证码校验是否在服务端完成,而非依赖前端?
  • 是否存在验证码复用漏洞?(使用一次后立即失效)

4.3 正确实现姿势

  1. 生成验证码:随机6位数字,存储到服务端,绑定手机号和过期时间
  2. 发送限制:同一手机号60秒内只能发一次,每日最多5次
  3. 校验机制:验证码使用一次即失效,且必须与手机号、会话绑定
  4. 错误处理:错误次数达3次,需重新获取验证码
  5. 返回内容:响应包仅返回“发送成功”,永远不包含验证码

第五章 结语:漏洞的价值在于组合

一个验证码回显,单独提交可能只有500元。 但如果组合上密码重置接口,就能重置任意用户密码,价值2000元。 如果再组合用户ID遍历,就能批量获取全站用户数据,价值5000元。

漏洞的价值,不在于它本身,而在于你能用它做什么。

下次挖到短信验证码漏洞时,别急着提交。先问问自己:

  • 这个验证码还能用在哪些接口?
  • 有没有其他功能点也存在同样缺陷?
  • 能不能组合成一条完整的攻击链?

当别人还在提交“短信轰炸”时,你已经能用它重置管理员密码了。 当别人还在报告“验证码回显”时,你已经拖走全站用户数据了。

这就是500和5000的差距。

-新年文章 祝各位安全技术员新年快乐,做自己热爱的事情就好像每天在游乐场玩一样


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:逍遥子讲安全 逍遥喝醉后写! 逍遥喝醉后写!《一个六位数的验证码,我拿到了全站用户的明文密码》

马年新春快乐! 网络安全文章

马年新春快乐!

文章总结: 该文档是一篇简短的节日祝福图文,内容为“马年新春快乐!”,由中龙技术于2026年2月17日发布。文档没有提供任何技术内容、安全分析或实质性信息,仅包
评论:0   参与:  0