4、签名绕过技术:签名验证逻辑缺陷、哈希碰撞攻击、证书伪造手法

签名验证,说白了就是安全启动的最后一道门。你前面做了那么多校验、度量、链式信任,最后都得靠签名来拍板——这镜像到底能不能跑?

但有意思的是,我见过太多产品,前面做得固若金汤,偏偏在签名验证这一步翻车。而且翻车的姿势五花八门,有的甚至让人哭笑不得。

这一章,我们就来聊聊攻击者是怎么绕过签名验证的。我自己在项目里踩过不少坑,也帮客户补过不少洞,希望能帮你少走弯路。

核心观点:签名验证不是“有就行”,而是要“正确且完整地验证”。任何简化、假设、遗漏,都可能成为突破口。

4.1 签名验证逻辑缺陷

这类问题最常见。说白了,就是代码写得不够严谨,留下了逻辑上的空子。

4.1.1 只验签名,不验公钥

嗯,你没看错。有些实现会写死一个公钥在代码里,然后验证签名。但问题是,如果攻击者能替换掉这个公钥呢?

我曾在某款IoT设备上见过这种实现:公钥存储在Flash的固定偏移处,签名验证函数直接去那里读。攻击者只要把固件里的公钥换成自己的,再拿自己的私钥签个名,就完美绕过了。

避坑指南:公钥必须被安全地存储和验证。要么烧死在OTP(一次性可编程)区域,要么通过硬件安全模块(HSM)保护。我曾经见过一个项目,公钥放在可写Flash里,还美其名曰“方便升级”——这简直是给攻击者送钥匙。

4.1.2 签名长度检查缺失

RSA签名长度是固定的,比如2048位就是256字节。但有些实现会偷懒,不检查签名长度就直接拿去验。

攻击者可以传一个很短的签名,比如1个字节。如果底层库处理不当,可能会发生缓冲区溢出,或者更糟——直接返回验证通过。

// 有缺陷的代码示例
int verify_signature(uint8_t *data, uint32_t data_len, 
                     uint8_t *sig, uint32_t sig_len) {
    // 没有检查 sig_len 是否等于 RSA_KEY_LEN
    return rsa_verify(data, data_len, sig, sig_len, public_key);
}

我的习惯:在验证签名之前,先做一次严格的参数检查。长度、格式、范围,一个都不能少。别相信任何输入。

4.1.3 返回值的误用

有些签名验证库,返回0表示成功,非0表示失败。但有些开发者会搞反,或者只检查“是否等于某个特定值”。

我记得有个案例:某厂商用的签名库,成功返回1,失败返回0。但他们的代码写的是 if (verify() == 0) { /* 验证通过 */ }。结果呢?所有签名都通不过,他们干脆把这个检查给注释掉了——安全启动形同虚设。

4.2 哈希碰撞攻击

哈希碰撞,就是找到两个不同的输入,但它们的哈希值相同。如果能做到,攻击者就可以用合法的签名去签一个恶意镜像。

4.2.1 理论上的可能性

MD5和SHA-1已经被证明存在实际的碰撞攻击。SHA-256目前还是安全的,但理论上也存在碰撞,只是计算量巨大。

哈希算法 碰撞难度 实际攻击案例 推荐状态
MD5 极低(秒级) 2004年已公开碰撞 禁止使用
SHA-1 较低(2017年SHAttered) Google已演示碰撞 禁止使用
SHA-256 极高(目前不可行) 无公开实际攻击 推荐使用
SHA-384/512 极高 无公开实际攻击 推荐使用

4.2.2 实际攻击场景

攻击者先找到一个合法的、已签名的镜像,然后通过哈希碰撞,构造一个恶意镜像,使得两者的哈希值相同。这样,合法的签名就可以直接用在恶意镜像上。

你想想看,这有多可怕?你辛辛苦苦搭建的信任链,一个哈希碰撞就全毁了。

避坑指南:绝对不要用MD5或SHA-1做签名验证。我见过一些老产品还在用SHA-1,理由是“性能考虑”。拜托,安全启动的性能瓶颈不在哈希,而在非对称签名运算。别因小失大。

4.3 证书伪造手法

证书伪造,主要针对的是基于PKI(公钥基础设施)的签名验证体系。攻击者试图伪造一个合法的证书,来通过链式信任验证。

4.3.1 自签名证书注入

如果设备只验证证书链的格式,而不验证根证书的真实性,攻击者就可以注入一个自签名的根证书。

具体做法:攻击者生成自己的根证书和私钥,然后用这个根证书签发一个中间证书,再用中间证书签一个代码签名证书。只要设备不验证根证书的来源,这个伪造的证书链就能通过验证。

4.3.2 证书链截断攻击

有些实现会验证证书链,但只验证到某个中间证书就停了,不去验证根证书。攻击者可以伪造一个中间证书,只要它能跟设备信任的某个中间证书对上就行。

我记得有个项目,他们的证书链是“根CA → 中间CA1 → 中间CA2 → 代码签名证书”。但代码里只验证到中间CA1,后面的就不管了。攻击者伪造了一个中间CA2和代码签名证书,就轻松绕过了。

4.3.3 证书时间戳绕过

证书都有有效期。但有些设备没有实时时钟(RTC),或者RTC可以轻易被篡改。攻击者可以把设备时间改到证书的有效期内,然后使用一个已经过期的、但曾经合法的证书。

我的建议:对于没有RTC的设备,可以考虑使用“固定时间窗口”或者“证书序列号递增”的方式来防止重放。我曾经在一个项目里,把证书的序列号跟固件版本号绑定,版本号只能递增,这样就杜绝了旧证书的重放。

4.4 知识体系总览

下面这张图,帮你把签名绕过的几种手法串起来。你可以看到,攻击者的目标都是“让验证逻辑认为一个非法的镜像合法”。

签名绕过技术知识体系 签名绕过技术知识体系 签名绕过攻击 签名验证逻辑缺陷 哈希碰撞攻击 证书伪造手法 公钥未保护 长度检查缺失 返回值误用 MD5/SHA-1碰撞 构造恶意镜像 自签名注入 链截断攻击 时间戳绕过 核心原则:签名验证必须完整、严谨、无假设。任何简化都可能成为突破口。

4.5 防护建议总结

说了这么多攻击手法,最后给几条实在的防护建议。这些都是我在实际项目中验证过的。

  1. 公钥必须硬件保护:烧死在OTP或eFuse里,或者用HSM管理。别放Flash里。
  2. 签名验证必须完整:检查长度、格式、返回值,一个都不能少。
  3. 使用强哈希算法:SHA-256起步,别碰MD5和SHA-1。
  4. 证书链必须完整验证:从根证书到叶子证书,每一级都要验。
  5. 时间戳要有防篡改机制:要么用安全RTC,要么用序列号替代。
  6. 定期做安全审计:代码审查、渗透测试,别等出事了再补。

最后说一句:安全启动不是一锤子买卖。攻击手法在进化,你的防护也得跟着升级。保持学习,保持警惕。