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 知识体系总览
下面这张图,帮你把签名绕过的几种手法串起来。你可以看到,攻击者的目标都是“让验证逻辑认为一个非法的镜像合法”。
4.5 防护建议总结
说了这么多攻击手法,最后给几条实在的防护建议。这些都是我在实际项目中验证过的。
- 公钥必须硬件保护:烧死在OTP或eFuse里,或者用HSM管理。别放Flash里。
- 签名验证必须完整:检查长度、格式、返回值,一个都不能少。
- 使用强哈希算法:SHA-256起步,别碰MD5和SHA-1。
- 证书链必须完整验证:从根证书到叶子证书,每一级都要验。
- 时间戳要有防篡改机制:要么用安全RTC,要么用序列号替代。
- 定期做安全审计:代码审查、渗透测试,别等出事了再补。
最后说一句:安全启动不是一锤子买卖。攻击手法在进化,你的防护也得跟着升级。保持学习,保持警惕。