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;
    // ...
}

避坑指南:逻辑漏洞没有“通用补丁”。你得把整个业务流程画出来,每个分支、每个状态转换都检查一遍。我曾经审计一个智能门锁固件,发现“恢复出厂设置”后管理员密码会重置为默认值。攻击者只要物理接触设备几秒钟,就能拿到控制权。这种漏洞,静态分析工具根本发现不了。

知识体系总览

下面这张图把这五种漏洞类型串起来了。你可以看到它们的攻击入口、影响范围和修复思路。

常见固件漏洞类型知识体系 常见固件漏洞类型与修复策略 固件安全审计 缓冲区溢出 命令注入 硬编码凭据 不安全加密 逻辑漏洞 核心修复策略 输入校验 → 白名单过滤 → 最小权限原则 → 使用安全库 → 完整状态机设计 攻击入口 网络/物理接口 攻击入口 Web/CLI 接口 攻击入口 固件提取/逆向 攻击入口 通信协议分析 攻击入口 业务逻辑遍历 共同影响:远程代码执行 | 权限提升 | 敏感信息泄露 | 设备完全控制 严重程度:高危 ~ 严重 | CVSS 评分通常 ≥ 7.5 审计建议 静态分析 + 动态模糊测试 + 手动逆向验证,三者缺一不可

这五种漏洞类型,覆盖了固件安全审计中 90% 以上的高危问题。你想想看,攻击者拿到固件后,第一件事就是搜硬编码字符串、找 system 调用、看加密算法。咱们做防守的,得比攻击者更熟悉这些套路才行。

嗯,这一章的内容就到这儿。每种漏洞的具体挖掘方法和修复技巧,后面章节会逐个展开细讲。