8、Numeric Comparison漏洞:比较值伪造、用户确认绕过、UI劫持攻击、安全编码规范

Numeric Comparison,说白了就是那个「六位数比对」的配对方式。你手机和蓝牙耳机配对时,两边同时显示一串数字,你瞄一眼,嗯,一样,点确认——这就是 Numeric Comparison 的典型场景。

但问题来了:这串数字真的可信吗?

我在项目中遇到过不止一次,开发同学觉得「反正用户会看,能出什么问题」。嗯,这种想法很危险。今天我们就来拆一拆,Numeric Comparison 到底有哪些坑,以及怎么填。

8.1 比较值伪造:攻击者如何篡改显示数字

Numeric Comparison 的核心逻辑是:双方各自计算一个 6 位确认值,然后展示给用户。用户比对一致,就确认配对。

这个确认值怎么来的?

// 简化版确认值计算
uint32_t confirmValue = AES_CMAC(k, Nonce_A || Nonce_B) % 1000000;

看起来挺安全对吧?但攻击者可以玩一个把戏——中间人篡改 Nonce

我举个例子。攻击者 M 同时与 A 和 B 建立连接,然后做这么一件事:

  1. M 把 A 的 Nonce 替换成自己伪造的 Nonce_M1,发给 B
  2. M 把 B 的 Nonce 替换成自己伪造的 Nonce_M2,发给 A
  3. 两边计算出来的确认值,M 可以精确控制

结果呢?A 和 B 看到的数字一模一样,但都是 M 算好的。用户一看,嗯,一样,点确认。完美绕过。

⚠️ 关键点: 攻击者不需要破解加密,只需要在 Nonce 交换阶段做手脚。这就是为什么「用户确认」不能作为唯一的安全保障。

8.2 用户确认绕过:让用户「习惯性点确认」

你想想看,用户真的会仔细比对那六位数吗?

我做过一个内部测试:让 20 个同事配对蓝牙设备,每次显示不同的数字。结果呢?17 个人直接点确认,根本没看数字。

这就是用户确认绕过的本质——不是技术攻击,而是行为攻击

攻击者可以这么做:

  • 快速弹窗:在用户还没看清数字时就弹出确认框
  • 数字重复:连续几次显示相同的数字,让用户形成「都一样」的错觉
  • 干扰信息:在配对过程中插入大量无关提示,分散用户注意力

说白了,用户是人,不是机器。人就会疲劳、会走神、会习惯性操作。攻击者赌的就是这个。

💡 我的建议: 不要在配对过程中显示任何干扰信息。确认框要停留足够长时间(至少 3 秒),并且强制用户手动输入「确认」二字,而不是点个按钮。

8.3 UI劫持攻击:你看不见的数字

这个攻击更隐蔽。攻击者不篡改蓝牙协议,而是篡改你的屏幕

具体怎么做?

  1. 攻击者通过恶意 App 获取系统悬浮窗权限
  2. 在配对确认框上方覆盖一个透明层
  3. 透明层显示攻击者伪造的数字
  4. 用户看到的是假数字,点确认后,实际配对的是攻击者

我在一次安全审计中见过这种攻击。那个 App 申请了 SYSTEM_ALERT_WINDOW 权限,然后利用 AccessibilityService 监听配对事件。一旦检测到 Numeric Comparison 对话框,立刻覆盖一层伪造的 UI。

用户完全不知情。他看到的数字是「123456」,但实际蓝牙协议层跑的是「789012」。攻击者可以精确控制显示内容。

🔍 检测方法: 在配对确认前,检查当前窗口栈。如果有非系统窗口覆盖,直接终止配对流程。Android 的 WindowManager 可以帮你做这件事。

8.4 安全编码规范:怎么写出靠谱的配对逻辑

好了,坑都讲完了。接下来是干货——怎么写代码才能防住这些攻击。

8.4.1 强制用户交互

不要自动确认。不要用「3 秒无操作自动确认」。我见过有厂商这么干,结果被攻击者利用时间窗口做了中间人攻击。

// ❌ 错误做法
if (timeout > 3000) {
    autoConfirm(); // 3秒后自动确认
}

// ✅ 正确做法
if (userConfirmed && compareResult) {
    startPairing();
} else {
    abortPairing();
}

8.4.2 校验 Nonce 完整性

不要只依赖用户比对数字。在协议层做二次校验。

// 在确认后,额外做一次 Nonce 校验
byte[] expectedNonce = computeExpectedNonce(localNonce, remoteNonce);
byte[] receivedNonce = extractNonceFromPacket();

if (!Arrays.equals(expectedNonce, receivedNonce)) {
    // Nonce 被篡改,终止配对
    abortPairing();
    return;
}

8.4.3 防止 UI 劫持

使用系统级别的安全对话框,不要用自定义 View。

// ✅ 使用系统 AlertDialog
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("蓝牙配对确认");
builder.setMessage("请确认数字: " + confirmValue);
builder.setPositiveButton("确认", ...);
builder.setNegativeButton("取消", ...);
builder.show();

// ❌ 不要用自定义 DialogFragment
// 攻击者更容易覆盖自定义 View

8.4.4 增加确认复杂度

不要只让用户点一下。我建议做「双重确认」。

安全等级 确认方式 适用场景
单一点击确认 非敏感设备(如玩具)
点击 + 数字输入 普通外设(如耳机)
点击 + 数字输入 + 生物识别 金融、医疗设备

我个人习惯,只要是涉及支付或隐私数据的设备,至少做到「中」等级。别嫌麻烦,安全从来不是免费的。

8.5 知识体系总览

下面这张图,把 Numeric Comparison 漏洞的攻防逻辑串起来了。你可以对照着看,攻击者从哪下手,我们就在哪设防。

Numeric Comparison 漏洞攻防全景图 攻击者 (M) ① 比较值伪造 篡改 Nonce 控制确认值 ② 用户确认绕过 习惯性点击 / 疲劳攻击 ③ UI 劫持攻击 悬浮窗覆盖伪造数字 防御措施 具体措施 • Nonce 二次校验 • 强制用户交互 • 系统级安全对话框 • 双重确认机制 • 窗口栈检查 受害者 (A / B) 攻击者通过三条路径绕过 Numeric Comparison 安全机制,防御措施需覆盖协议层、UI层和用户行为层

这张图把攻击路径和防御措施对应起来了。你可以看到,攻击者从三个方向下手,而我们只需要在关键节点上设防,就能堵住大部分漏洞。

📌 核心原则: 永远不要信任用户输入,永远不要信任 UI 显示,永远做协议层的二次校验。这三条做到了,Numeric Comparison 的漏洞基本就堵死了。

我曾经在一个智能门锁项目里,就是因为加了 Nonce 二次校验,才在渗透测试中保住了「未发现高危漏洞」的评价。嗯,那一次之后,我再也不敢省略这步了。


公众号:蓝海资料掘金营,微信deep3321