【缺陷周话】第26期:被污染的格式化字符串
字数 1899 2025-08-18 11:38:08
格式化字符串漏洞详解与防护指南
1. 格式化字符串漏洞概述
格式化字符串漏洞是指当程序使用外部可控的数据作为格式化字符串函数的参数时,由于缺乏适当的验证和过滤,可能导致程序崩溃、信息泄露或任意代码执行的安全问题。
1.1 常见的格式化字符串函数
以下C/C++函数容易受到格式化字符串攻击:
- 输入函数:
scanf - 输出函数:
printf,fprintf,vprintf,vfprintf - 字符串格式化函数:
sprintf,snprintf,vsprintf,vsnprintf
1.2 漏洞产生原理
当格式化字符串函数接受外部输入作为格式说明符时,攻击者可以:
- 通过
%x、%p等格式符泄露栈内存内容 - 通过
%n格式符向任意地址写入数据 - 通过精心构造的输入导致程序崩溃
2. 漏洞危害与真实案例
2.1 漏洞危害等级
- 信息泄露:可获取敏感数据(高)
- 拒绝服务:导致程序崩溃(中高)
- 任意代码执行:完全控制系统(严重)
2.2 近年CVE案例
| CVE编号 | 受影响软件 | 影响版本 | 漏洞描述 |
|---|---|---|---|
| CVE-2018-6875 | KeepKey | 4.0.0 | 格式化字符串漏洞导致信息泄露 |
| CVE-2018-6317 | ClaymoreDualMiner | ≤10.5 | 远程管理界面存在未授权格式化字符串漏洞 |
| CVE-2018-17336 | udisks2 | 2.8.0 | 文件系统标签处理存在格式化字符串漏洞 |
| CVE-2018-15749 | Pulse Secure Desktop | macOS 5.3RX <5.3R5, 9.0R1 | 本地信息泄露漏洞 |
3. 漏洞代码示例与分析
3.1 缺陷代码示例
// 示例源于Samate Juliet Test Suite for C/C++ v1.3
void bad()
{
char * data;
char dataBuffer[100] = "";
data = dataBuffer;
// 从环境变量获取污染数据
strncat(data, getenv("ADD"), 100);
// 危险:直接使用污染数据作为格式化字符串
fprintf(stdout, data); // 缺陷点
}
漏洞分析:
- 从环境变量获取外部输入数据
- 未经验证直接作为
fprintf的格式化字符串参数 - 攻击者可构造包含恶意格式说明符的环境变量
3.2 静态检测结果
使用360代码卫士检测会显示:
- 缺陷类型:被污染的格式化字符串
- 危险等级:高
- 定位:fprintf调用处
4. 修复方案
4.1 修复代码示例
void good()
{
char * data;
char dataBuffer[100] = "";
data = dataBuffer;
// 从环境变量获取数据
strncat(data, getenv("ADD"), 100);
// 修复:明确指定格式说明符
fprintf(stdout, "%s", data); // 安全用法
}
修复要点:
- 使用固定的格式字符串
"%s" - 将被污染数据作为参数而非格式字符串
- 确保格式说明符与参数类型匹配
4.2 修复后检测结果
使用360代码卫士验证:
- 原缺陷已消除
- 格式化字符串使用安全
5. 防护最佳实践
5.1 编码规范
-
永远不要使用外部输入作为格式字符串
- 错误:
printf(user_input); - 正确:
printf("%s", user_input);
- 错误:
-
使用安全的替代函数
// 不安全 sprintf(buffer, input); // 较安全 snprintf(buffer, sizeof(buffer), "%s", input); -
编译时启用保护选项
- GCC:
-Wformat-security(警告不安全格式字符串使用) - Clang:
-Wformat(包含格式安全检查)
- GCC:
5.2 静态分析工具
推荐使用以下工具检测格式化字符串漏洞:
- 360代码卫士
- Coverity
- Fortify
- Clang静态分析器
- GCC警告选项
5.3 运行时防护
-
地址空间布局随机化(ASLR)
- 降低利用格式化字符串漏洞攻击的成功率
-
格式化字符串保护
- Glibc的
FORTIFY_SOURCE选项 - 检测可疑的
%n使用
- Glibc的
-
内存保护机制
- NX/DEP (数据执行保护)
- Stack Canaries (栈保护)
6. 漏洞利用原理深入
6.1 信息泄露攻击
攻击者可利用%x、%p等格式说明符读取栈内存:
printf("%08x.%08x.%08x.%08x"); // 泄露4个32位栈值
6.2 任意内存写入
通过%n格式说明符可向指定地址写入数据:
int bytes_written;
printf("AAAA%n", &bytes_written); // 将4写入bytes_written
6.3 高级利用技术
- GOT覆写:通过覆盖全局偏移表实现代码执行
- DTORS利用:修改.dtors段函数指针
- 返回地址覆盖:修改栈上的返回地址
7. 相关CWE标准
CWE-134: Use of Externally-Controlled Format String
描述:软件使用外部提供的输入作为格式化字符串函数的格式参数,而没有进行适当验证。
相关弱点:
- CWE-20: 不恰当的输入验证
- CWE-74: 注入问题
- CWE-119: 内存缓冲区边界操作不当
8. 延伸阅读
通过遵循上述指南和最佳实践,开发人员可以有效预防格式化字符串漏洞,提高软件安全性。