4. 常见固件漏洞类型:缓冲区溢出、命令注入、硬编码凭据、不安全的加密算法、逻辑漏洞
做固件安全这些年,我见过太多产品栽在几个老问题上。说白了,攻击者翻来覆去用的就是那几招。你只要把这五种漏洞类型吃透了,就能挡住绝大多数攻击。
咱们一个一个来看。
4.1 缓冲区溢出
这玩意儿是固件界的“老熟人”了。我入行时接手的第一个项目,就是修一个路由器的缓冲区溢出漏洞。当时厂商把用户输入的用户名直接拷进一个64字节的数组,连长度检查都没有。
核心原因:对输入数据长度缺乏校验,数据写入时越过了缓冲区边界。
缓冲区溢出分两种:
- 栈溢出:覆盖返回地址,劫持程序控制流。最常见,也最好利用。
- 堆溢出:破坏堆管理结构,实现任意地址写。利用起来更复杂,但危害更大。
看个典型例子:
// 漏洞代码
void process_login(char *user_input) {
char buffer[64];
strcpy(buffer, user_input); // 没有长度检查!
// ... 后续处理
}
// 修复后
void process_login(char *user_input) {
char buffer[64];
strncpy(buffer, user_input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
// ... 后续处理
}
嗯,这里要注意:strncpy 不会自动追加终止符,你得手动加。我曾经见过有人用了 strncpy 但忘了加 '\0',结果调试了整整两天。
我的习惯:只要涉及内存拷贝,一律用带长度参数的函数。snprintf 比 sprintf 安全,strlcpy 比 strcpy 靠谱。别嫌麻烦,这行代码可能救你一命。
4.2 命令注入
固件里调用系统命令太常见了——重启网络、更新配置、执行脚本。但你要是直接把用户输入拼进命令字符串,那就等于把枪递给了攻击者。
举个例子:
// 漏洞代码
void ping_host(char *ip) {
char cmd[128];
sprintf(cmd, "ping -c 4 %s", ip);
system(cmd); // 输入: 127.0.0.1; rm -rf /
}
// 修复后
void ping_host(char *ip) {
// 1. 白名单校验
if (!is_valid_ip(ip)) {
log_error("Invalid IP: %s", ip);
return;
}
// 2. 使用 exec 族函数替代 system
execlp("/bin/ping", "ping", "-c", "4", ip, NULL);
}
为什么 exec 比 system 安全?system 会启动 shell 解析命令,而 exec 直接加载程序,不经过 shell。攻击者就算传了分号、管道符,也只会被当成普通参数。
避坑指南:我曾经审计过一个智能网关固件,开发团队用黑名单过滤了 ; | & ` $ 等字符。结果攻击者用换行符 %0a 绕过了过滤。记住:黑名单永远防不住,白名单才是正道。
4.3 硬编码凭据
这可能是最“蠢”的漏洞,但也是最常见的。我拆过十几款 IoT 设备,超过一半都有硬编码凭据。厂商图省事,把密码写死在代码里,结果就是“一把钥匙开万把锁”。
| 凭据类型 | 常见位置 | 危害等级 |
|---|---|---|
| 调试串口密码 | UART 初始化代码 | 高 |
| 云端 API Key | 配置文件、字符串常量 | 严重 |
| SSH/ Telnet 密码 | shadow 文件、init 脚本 | 高 |
| 固件签名密钥 | 引导加载程序 | 严重 |
怎么修?
- 密码:首次启动强制修改,或者用安全元件(SE)存储。
- 密钥:使用硬件信任根(RoT),密钥永远不出芯片。
- API Key:每个设备单独生成,服务器端做绑定校验。
我的经验:有一次审计某摄像头固件,在二进制里搜到了“admin:123456”。更离谱的是,这个密码还写死在引导加载程序里,想改都改不了。最后只能建议客户召回产品。你说这得多尴尬?
4.4 不安全的加密算法
固件里用加密,本意是保护数据安全。但用错了算法,反而会给你一种“我很安全”的错觉。我见过太多用 MD5 做固件签名的案例了。
常见的“坑”有这些:
- MD5 / SHA-1:碰撞攻击早已成熟,别再用在安全场景。
- DES / 3DES:密钥太短,暴力破解不是梦。
- ECB 模式:同样的明文块产生同样的密文块,信息泄露严重。
- 硬编码 IV:初始化向量固定,降低了加密的有效强度。
- 自定义加密算法:相信我,你写的加密算法一定不安全。
看个对比:
| 场景 | 不推荐 | 推荐 |
|---|---|---|
| 固件签名 | MD5 / SHA-1 | SHA-256 / SHA-384 |
| 数据加密 | DES / 3DES / RC4 | AES-256-GCM |
| 密钥交换 | RSA-1024 | ECDHE / RSA-2048+ |
| 随机数生成 | rand() / srand() | 硬件 TRNG / getrandom() |
我的建议:别自己拼凑加密方案。用现成的库——mbedTLS、OpenSSL、WolfSSL 都行。它们经过了大量安全审计,比你从头写靠谱一万倍。
4.5 逻辑漏洞
这类漏洞最“阴”。它不涉及内存破坏,也不依赖注入,纯粹是业务逻辑设计有缺陷。攻击者只要“按正常流程走一遍”,就能拿到不该有的权限。
我遇到过几个典型场景:
- 权限绕过:普通用户直接访问管理接口,后端没做鉴权。
- 状态机混乱:固件升级过程中断电,设备变砖后还能通过特殊指令进入调试模式。
- 竞争条件:两个线程同时写同一个配置项,导致配置损坏。
- 整数溢出:计算缓冲区大小时,两个正整数相加溢出变成负数,绕过长度检查。
举个例子:
// 整数溢出漏洞
int allocate_buffer(int size_a, int size_b) {
int total = size_a + size_b; // 如果 size_a=0x7FFFFFFF, size_b=1, total 变成负数
char *buf = malloc(total);
if (!buf) return -1;
// 后续拷贝数据时,total 是负数,导致堆溢出
}
// 修复后
int allocate_buffer(int size_a, int size_b) {
if (size_a < 0 || size_b < 0) return -1;
if (size_a > INT_MAX - size_b) return -1; // 检查溢出
int total = size_a + size_b;
char *buf = malloc(total);
if (!buf) return -1;
// ...
}
避坑指南:逻辑漏洞没有“通用补丁”。你得把整个业务流程画出来,每个分支、每个状态转换都检查一遍。我曾经审计一个智能门锁固件,发现“恢复出厂设置”后管理员密码会重置为默认值。攻击者只要物理接触设备几秒钟,就能拿到控制权。这种漏洞,静态分析工具根本发现不了。
知识体系总览
下面这张图把这五种漏洞类型串起来了。你可以看到它们的攻击入口、影响范围和修复思路。
这五种漏洞类型,覆盖了固件安全审计中 90% 以上的高危问题。你想想看,攻击者拿到固件后,第一件事就是搜硬编码字符串、找 system 调用、看加密算法。咱们做防守的,得比攻击者更熟悉这些套路才行。
嗯,这一章的内容就到这儿。每种漏洞的具体挖掘方法和修复技巧,后面章节会逐个展开细讲。