当前位置:首页 > 百科

如何防御缓冲区溢出漏洞

2014年,何防OpenSSL的御缓溢出Heartbleed漏洞让全球三分之二的HTTPS网站陷入危险。攻击者只发一个构造过的冲区数据包,就能从服务器内存里读出密码、漏洞私钥。何防这个漏洞的御缓溢出本质,就是冲区一次缓冲区越界读取。缓冲区溢出离我们并不远,漏洞它至今仍是何防CVE漏洞库里的常客。

要防住它,御缓溢出先得搞清楚它怎么发生的冲区。C语言里,漏洞char buf[64]; strcpy(buf,何防 user_input); 这行代码如果 user_input 超过63个字符,多出来的御缓溢出字节就会覆盖栈上相邻的内存。攻击者精心构造这些字节,冲区可以改写函数返回地址,让程序跳到自己注入的shellcode上执行。栈溢出只是其中一种,堆溢出、整数溢出导致的缓冲区问题同样凶险。

最直接的防线是写代码时别用不检查边界的函数。strcpy、sprintf、gets 这些应该从代码库里彻底消失。换成 strncpy、snprintf、fgets,并且传入正确的长度参数。但光换函数不够——strncpy 如果长度算错,照样溢出。我见过有人把 sizeof(dest) 和 strlen(src) 搞混,结果目标缓冲区比源字符串短,还是写穿了。

编译器层面能加一层硬防护。GCC和Clang的 -fstack-protector-strong 会在栈帧里插入canary值,函数返回前检查它是否被改写。一旦发现异常,程序直接abort。Visual Studio的/GS选项做类似的事。这招能挡住大部分栈溢出攻击,但对堆溢出和某些精确覆盖canary的攻击无效。

操作系统也提供了缓解措施。ASLR让栈、堆、共享库的地址每次运行都随机化,攻击者很难猜中shellcode的位置。DEP(数据执行保护)把栈和堆标记为不可执行,就算跳转过去也跑不了代码。Linux上的 execstack 和 execmem 可以检查二进制文件是否违规。这些机制需要配合使用,单独一个都能被绕过。

语言选择是更根本的方案。Rust在编译期就检查数组越界,直接拒绝编译有问题的代码。Go的切片操作会做运行时边界检查,越界直接panic。Python、Java这些带GC的语言压根不给你手动管理内存的机会。代价是性能开销和生态迁移成本,但安全收益是实打实的。

代码审计和模糊测试不能省。静态分析工具像Coverity、Fortify能扫出可疑的缓冲区操作。AFL、libFuzzer这类模糊测试工具,用随机数据疯狂轰炸程序入口,往往能在几小时内触发你手写测试几个月都碰不到的边界条件。我参与过一个项目,AFL跑了一晚上就找出了三个栈溢出点,全是手工测试漏掉的。

最后,别忽略编译器的警告。-Wall -Wextra 打开后,很多缓冲区问题在编译阶段就会报出来。有人嫌警告烦,直接关掉,等于把免费的安全检查扔了。把警告当错误处理(-Werror),强制自己修干净,长期看省下的调试时间远超那点麻烦。

缓冲区溢出防御没有银弹。每一层防护都有绕过方法,但叠起来足够让攻击成本高到多数人放弃。写代码时多问一句“这个长度谁保证”,比事后打补丁管用得多。

分享到: