【💰】【网络安全】转载:一个六位数的验证码,我拿到了全站用户的明文密码

21 条回复
112 次浏览

一个六位数的验证码,决定了你是月入 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 响应包篡改绕过

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

挖掘方法:

输入错误验证码,拦截响应包
"code": 400改为"code": 200放包,观察是否绕过
进阶技巧:返回包中直接包含 token,可替换为其他用户的 token 实现登录。

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

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

挖掘方法:

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

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

##【案例 1】置空验证码 → 任意用户登录 → 后台接管

目标:某金融平台 APP
耗时:45 分钟
奖金:8000 元

攻击路径:

1.登录接口抓包:POST /api/login参数为 mobilecode
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 次验证码
结果: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 的差距。

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

本文转载于 逍遥子讲安全

金币池
💰 755 金币

金币池金币数量会随着回复数量动态增加,回复有概率获得金币池中部分金币奖励。

大平衡者
Guardian

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

种子用户
OP

@bopomofo 不是的,验证码可以发到手机上,只是返回包里包含了验证码了,这样就可以绕过机主本人的验证码

种子用户
OP

技艺 7 了,刷了那么多贴还没到 8,是真的难啊

大平衡者
Guardian

技艺跟阅历很难,而且现在这里生活区的帖子偏多

种子用户
OP

@bopomofo 翻老帖子刷,反正没人发帖哈哈哈,你技艺还没到 10 呢,我先把技艺和阅历刷满,我金币不够刷不到 10

马上来

漏洞和漏洞链是两个东西,不能用漏洞链的奖金衡量单一漏洞的价值
不过文是好文

发表一个评论

R保持