缓冲区溢出漏洞
1988年11月2日,缓冲康奈尔大学23岁的区溢研究生罗伯特·莫里斯按下回车键,释放了后来被称为“莫里斯蠕虫”的出漏程序。这个程序本身不携带恶意载荷,缓冲却让约6000台Unix服务器陷入瘫痪——占当时整个互联网的区溢十分之一。攻击手法?出漏正是缓冲区溢出。
什么叫缓冲区溢出?缓冲打个比方:你往一个只能装8个鸡蛋的纸盒里硬塞20个鸡蛋,蛋液必然淌得到处都是区溢。程序在内存里划分固定大小的出漏空间存放数据,如果写入内容超出预定边界,缓冲多余字节就会“溢出”到相邻内存区域。区溢那些多出来的出漏数据,恰好可以伪装成CPU下一条要执行的缓冲指令——于是攻击者把恶意代码写进溢出部分,程序就乖乖执行了。区溢
这个漏洞的出漏可怕之处在于它难以察觉。你写的代码看起来完全正常:
char name[64];
gets(name);
只是从标准输入读一行字符串存进数组。可如果用户输入超过63个字符呢?没有边界检查,多余字符直接覆盖栈上的返回地址。程序不知道,它继续运行,直到函数结束要“回家”时,发现门牌号被换了——跳到攻击者指定的位置执行任意代码。
我前几年翻看某大型银行的安全审计报告,发现他们内部系统的登录模块还存在类似问题。测试人员输入一长串“A”直接让服务崩溃。修复方案倒是简单:把gets换成fgets,限制读取长度,一行代码的事。可为什么当年写代码的人没这么做?因为图省事。C语言的设计哲学是信任程序员,而程序员往往高估自己的细心程度。
现代防御手段不少——栈保护(canary)、地址随机化(ASLR)、非执行内存页(NX),把利用难度抬高了不少。但攻击者也在进化。2017年,Google Project Zero发现Linux内核的udp_sendmsg函数存在栈溢出,攻击者利用它获得了本地root权限。那次漏洞在2010年就被引入内核,潜伏了整整七年。
说句掏心窝的话,缓冲区溢出就像家里水管老化——你永远不知道哪滴漏出来的水会泡坏楼下天花板。它不会消失,因为总有人用C/C++写底层代码,总有人忘记检查边界,总有人在性能优化时去除安全校验。我见过最离谱的案例是某嵌入式设备制造商,为了省几KB内存,主动关闭了编译器的堆栈保护选项。省下来的那点存储空间,换来的是一台能被远程完全控制的智能门锁。谁愿意家门口装个别人能随意开关的锁?
所以每次有人问我“这漏洞是不是过时了”,我都给他看最新统计:微软2023年修复的漏洞中,内存安全问题(缓冲区溢出是主力)占63%。这不是历史教科书里的名词,而是每天真实发生的攻防拉锯。写代码时多写一行边界检查,可能就少一个被蠕虫瘫痪的夜晚——莫里斯当年要是多检查一次输入长度,互联网的童年或许会安稳些。

喜欢
讨厌