漏洞概述
在登录接口的手机号输入字段中,通过逗号分隔输入多个手机号(如 13800000099,13800000000),可能导致短信网关同时向多个号码发送相同验证码,进而引发账号越权登录或账号信息覆盖等安全问题。
漏洞原理
多数短信网关(如阿里云SMS、腾讯云SMS)的批量发送接口本身支持逗号分隔的多手机号格式。当后端未对用户输入的手机号进行严格格式校验时,攻击者构造的多手机号字符串可能被直接透传至短信网关,触发批量发送逻辑。
攻击前提条件
- 登录/注册接口接受手机号作为身份标识
- 后端未对手机号字段做严格的单号码格式校验(如正则
^1[3-9]\d{9}$)
- 短信网关接口支持逗号分隔的批量发送
漏洞触发流程分支图

详细场景分析
情况A:单号码 + 验证码即可登录
触发条件: 后端在发送验证码时将输入拆分为多个号码分别发送,但在验证阶段仅校验「号码 + CODE」的匹配关系。
攻击流程:
- 攻击者在手机号字段输入
攻击者号码,目标号码
- 系统向两个号码发送相同验证码
- 攻击者用自己的手机接收验证码
- 攻击者提交
目标号码 + 验证码 完成登录
危害等级: 高危 — 可直接越权登录任意用户账号
实际利用场景:
POST /api/sms/send HTTP/1.1
Content-Type: application/json
{"phone": "13800000099,13800000000"}
-- 响应: 验证码已发送 --
POST /api/login HTTP/1.1
Content-Type: application/json
{"phone": "13800000000", "code": "123456"}
-- 响应: 登录成功,返回目标用户token --
情况B-1:整体字符串登录,后端校验顺序
触发条件: 后端在验证阶段也接受逗号分隔的字符串,并按顺序取第一个(或最后一个)号码作为登录身份。
攻击流程:
- 攻击者输入
目标号码,攻击者号码 发送验证码
- 攻击者用自己的手机接收验证码
- 攻击者提交
目标号码,攻击者号码 + 验证码 登录
- 后端取第一个号码(目标号码)作为登录身份
危害等级: 高危 — 通过控制号码顺序实现定向账号劫持
实际利用场景:
POST /api/sms/send HTTP/1.1
Content-Type: application/json
{"phone": "13800000000,13800000099"}
POST /api/login HTTP/1.1
Content-Type: application/json
{"phone": "13800000000,13800000099", "code": "654321"}
-- 后端逻辑: phone.split(",")[0] → 13800000000 --
-- 响应: 以13800000000身份登录成功 --
情况B-2:整体字符串作为新账号,覆盖原有数据
触发条件: 后端完全不校验手机号格式,将 13800000099,13800000000 视为一个合法的新用户标识进行注册/登录。
攻击流程:
- 攻击者输入
目标号码,攻击者号码 发送验证码并登录
- 系统创建新账号,标识为整个字符串
- 在某些实现中,新账号的创建过程会触发与第一个号码关联的数据覆盖(如唯一索引冲突时的 UPSERT 操作)
危害等级: 中高危 — 可能导致目标用户数据被覆盖或污染
实际利用场景:
POST /api/login HTTP/1.1
Content-Type: application/json
{"phone": "13800000000,13800000099", "code": "111222"}
-- 后端逻辑: 未找到该"手机号"对应账号,执行注册 --
-- 数据库操作: INSERT ... ON CONFLICT(phone_prefix) DO UPDATE --
-- 结果: 13800000000 的用户资料被覆盖 --
根因分析
| 环节 |
问题 |
说明 |
| 输入校验 |
手机号格式未做严格正则匹配 |
应限制为 ^1[3-9]\d{9}$ |
| 短信网关调用 |
未对入参做二次清洗 |
直接透传用户输入至网关API |
| 验证码校验 |
验证逻辑与发送逻辑的手机号处理不一致 |
发送时拆分,验证时未拆分(或反之) |
| 账号体系 |
未对用户标识做唯一性和格式约束 |
允许非标准格式的字符串作为账号标识 |
修复建议
1. 输入层严格校验(最关键)
import re
def validate_phone(phone: str) -> bool:
"""严格校验单个中国大陆手机号"""
return bool(re.match(r'^1[3-9]\d{9}$', phone.strip()))
2. 短信发送前二次校验
def send_sms(phone: str, code: str):
# 即使前端已校验,后端必须再次校验
if not validate_phone(phone):
raise InvalidPhoneError("手机号格式非法")
# 确保不包含分隔符
if any(sep in phone for sep in [',', ';', ' ', '
']):
raise InvalidPhoneError("手机号包含非法字符")
sms_gateway.send(phone, code)
3. 验证码绑定单一手机号
def verify_code(phone: str, code: str) -> bool:
# 验证时同样严格校验格式
if not validate_phone(phone):
return False
stored = redis.get(f"sms_code:{phone}")
return stored == code
4. 数据库层约束
ALTER TABLE users
ADD CONSTRAINT chk_phone_format
CHECK (phone ~ '^1[3-9][0-9]{9}$');
测试用例(验证修复效果)
| 测试输入 |
预期结果 |
13800000099 |
正常发送验证码 |
13800000099,13800000000 |
拒绝,提示格式错误 |
13800000099;13800000000 |
拒绝,提示格式错误 |
13800000099 13800000000 |
拒绝,提示格式错误 |
1380000009 (10位) |
拒绝,提示格式错误 |
23800000099 (非1开头) |
拒绝,提示格式错误 |
总结
该漏洞的核心在于输入校验缺失与短信网关批量发送能力的意外暴露。攻击者无需任何权限即可利用,且在情况A下可实现零交互的任意账号登录。修复优先级应为:输入校验 > 网关调用清洗 > 验证逻辑一致性 > 数据库约束。
## 漏洞概述
在登录接口的手机号输入字段中,通过逗号分隔输入多个手机号(如 `13800000099,13800000000`),可能导致短信网关同时向多个号码发送相同验证码,进而引发账号越权登录或账号信息覆盖等安全问题。
## 漏洞原理
多数短信网关(如阿里云SMS、腾讯云SMS)的批量发送接口本身支持逗号分隔的多手机号格式。当后端未对用户输入的手机号进行严格格式校验时,攻击者构造的多手机号字符串可能被直接透传至短信网关,触发批量发送逻辑。
## 攻击前提条件
- 登录/注册接口接受手机号作为身份标识
- 后端未对手机号字段做严格的单号码格式校验(如正则 `^1[3-9]\d{9}$`)
- 短信网关接口支持逗号分隔的批量发送
## 漏洞触发流程分支图

## 详细场景分析
### 情况A:单号码 + 验证码即可登录
**触发条件:** 后端在发送验证码时将输入拆分为多个号码分别发送,但在验证阶段仅校验「号码 + CODE」的匹配关系。
**攻击流程:**
1. 攻击者在手机号字段输入 `攻击者号码,目标号码`
2. 系统向两个号码发送相同验证码
3. 攻击者用自己的手机接收验证码
4. 攻击者提交 `目标号码 + 验证码` 完成登录
**危害等级:** 高危 — 可直接越权登录任意用户账号
**实际利用场景:**
```http
POST /api/sms/send HTTP/1.1
Content-Type: application/json
{"phone": "13800000099,13800000000"}
-- 响应: 验证码已发送 --
POST /api/login HTTP/1.1
Content-Type: application/json
{"phone": "13800000000", "code": "123456"}
-- 响应: 登录成功,返回目标用户token --
```
### 情况B-1:整体字符串登录,后端校验顺序
**触发条件:** 后端在验证阶段也接受逗号分隔的字符串,并按顺序取第一个(或最后一个)号码作为登录身份。
**攻击流程:**
1. 攻击者输入 `目标号码,攻击者号码` 发送验证码
2. 攻击者用自己的手机接收验证码
3. 攻击者提交 `目标号码,攻击者号码 + 验证码` 登录
4. 后端取第一个号码(目标号码)作为登录身份
**危害等级:** 高危 — 通过控制号码顺序实现定向账号劫持
**实际利用场景:**
```http
POST /api/sms/send HTTP/1.1
Content-Type: application/json
{"phone": "13800000000,13800000099"}
POST /api/login HTTP/1.1
Content-Type: application/json
{"phone": "13800000000,13800000099", "code": "654321"}
-- 后端逻辑: phone.split(",")[0] → 13800000000 --
-- 响应: 以13800000000身份登录成功 --
```
### 情况B-2:整体字符串作为新账号,覆盖原有数据
**触发条件:** 后端完全不校验手机号格式,将 `13800000099,13800000000` 视为一个合法的新用户标识进行注册/登录。
**攻击流程:**
1. 攻击者输入 `目标号码,攻击者号码` 发送验证码并登录
2. 系统创建新账号,标识为整个字符串
3. 在某些实现中,新账号的创建过程会触发与第一个号码关联的数据覆盖(如唯一索引冲突时的 UPSERT 操作)
**危害等级:** 中高危 — 可能导致目标用户数据被覆盖或污染
**实际利用场景:**
```http
POST /api/login HTTP/1.1
Content-Type: application/json
{"phone": "13800000000,13800000099", "code": "111222"}
-- 后端逻辑: 未找到该"手机号"对应账号,执行注册 --
-- 数据库操作: INSERT ... ON CONFLICT(phone_prefix) DO UPDATE --
-- 结果: 13800000000 的用户资料被覆盖 --
```
## 根因分析
| 环节 | 问题 | 说明 |
| ------------ | ------------------------------------ | ---------------------------------- |
| 输入校验 | 手机号格式未做严格正则匹配 | 应限制为 `^1[3-9]\d{9}$` |
| 短信网关调用 | 未对入参做二次清洗 | 直接透传用户输入至网关API |
| 验证码校验 | 验证逻辑与发送逻辑的手机号处理不一致 | 发送时拆分,验证时未拆分(或反之) |
| 账号体系 | 未对用户标识做唯一性和格式约束 | 允许非标准格式的字符串作为账号标识 |
## 修复建议
### 1. 输入层严格校验(最关键)
```python
import re
def validate_phone(phone: str) -> bool:
"""严格校验单个中国大陆手机号"""
return bool(re.match(r'^1[3-9]\d{9}$', phone.strip()))
```
### 2. 短信发送前二次校验
```python
def send_sms(phone: str, code: str):
# 即使前端已校验,后端必须再次校验
if not validate_phone(phone):
raise InvalidPhoneError("手机号格式非法")
# 确保不包含分隔符
if any(sep in phone for sep in [',', ';', ' ', '\n']):
raise InvalidPhoneError("手机号包含非法字符")
sms_gateway.send(phone, code)
```
### 3. 验证码绑定单一手机号
```python
def verify_code(phone: str, code: str) -> bool:
# 验证时同样严格校验格式
if not validate_phone(phone):
return False
stored = redis.get(f"sms_code:{phone}")
return stored == code
```
### 4. 数据库层约束
```sql
ALTER TABLE users
ADD CONSTRAINT chk_phone_format
CHECK (phone ~ '^1[3-9][0-9]{9}$');
```
## 测试用例(验证修复效果)
| 测试输入 | 预期结果 |
| ------------------------- | ------------------ |
| `13800000099` | 正常发送验证码 |
| `13800000099,13800000000` | 拒绝,提示格式错误 |
| `13800000099;13800000000` | 拒绝,提示格式错误 |
| `13800000099 13800000000` | 拒绝,提示格式错误 |
| `1380000009` (10位) | 拒绝,提示格式错误 |
| `23800000099` (非1开头) | 拒绝,提示格式错误 |
## 总结
该漏洞的核心在于**输入校验缺失**与**短信网关批量发送能力的意外暴露**。攻击者无需任何权限即可利用,且在情况A下可实现零交互的任意账号登录。修复优先级应为:输入校验 > 网关调用清洗 > 验证逻辑一致性 > 数据库约束。
温馨提示:本文内容仅用于合法授权的安全学习与研究交流,严禁用于未授权渗透测试、漏洞利用或任何违法行为。
涉及企业或平台的未公开漏洞信息,请遵循负责任披露原则,勿公开传播可直接复现的敏感细节。
如存在侵权、错误信息或不当内容,请联系站方处理,我们将及时核实并删除。邮箱:admin@baimaojianghu.com。
视作全新账号 直接被拒绝了
牛的
看看,学习
学到了
学到了
强太强师傅
厉害
太强了大佬
太强了
太强了👍
无敌大手
学到了
学到了
我学一下
好详细的介绍,学到了
无敌
学到了
第一次见tql
这也可以,太厉害了
哼😾?
66
现在应该很少了
可以的师傅
后面的资料覆盖之前没有想到tql
tql佬
tql牛
牛佬
强无敌
学到了
可以可以,很适合我这种新手
学到了
学到了
tql佬
tqlD佬