1 缓冲区溢出防范
缓存溢出出现在当一些数据拷贝到内存区域中产生,如果内存的缓存区不能够容纳这些数据,到拷贝成功的时候,目标内存的边界部分就会被覆盖。程序中的变量可以被存在程序栈(stack)中也可以存在程序堆(heap)中,因此我们也可以常听到这些字汇,栈溢出和堆溢出,相比较这两个溢出类型,栈溢出的利用多数情况下会比较容易点。
现在许多有缓存溢出风险的函数都存在相对安全的替代函数,它们都通过传递参数来限制多少数据被调用。如:
Strcpy
strcpy():此函数没有执行任意长度的确认,而使用strncpy()函数可以限制长度,当使用strncpy()的时候,需要以NULL终结字符串,因为有NULL终止符隐含定义源缓存区的大小。
不正确情况
正确情况
|
strcat
strcat():此函数类似与strcpy(),也不检查字符串的长度,使用strncat()可以很好的限制被拷贝的长度,strncat()函数只会追加定义在大小参数中的长度,NULL终止符将终止字符串。
不正确情况
正确情况
sprintf |
sprintf():此函数用来格式化字符串变量,是很容易产生缓存溢出的地方。使用snprintf()可以很好的限制拷贝到缓存中的打印数据长度,snprintf()函数返回要传递过去的数据字符总数,如果它大于能传递字节大小的值,那么没有任何数据写到缓存中,用户必须检查snprintf()的值以保证要被打印的缓存。
不正确情况
正确情况
| gets |
gets:此函数天生就存在漏洞,在应用程序中应该永远不要使用,一般在编译的时候也会被警告,gets()没有规定任意长度,永远将导致缓存溢出,它从标准输入中读取数据并一直到换行符或者EOF标识接收到,所读的数据全部填充到定义的缓存,并不检查任何长度。fgets()函数用来代替不安全的gets()函数,此函数会至多读取’sizeof(buffer) – 1’的文件流 。当然中途读取到EOF或者换行符也会停止。
不正确情况 |
void func(char *str)
{
char buffer[256];
gets(buffer);
return;
}
正确情况
void func(char *str)
{
char buffer[256];
fgets(buffer, sizeof(buffer) – 1, stdin);
return;
}
Scanf\fscan\sscan
scanf()、fscan()、sscan():在使用这些函数的时候需要小心读取要放到固定长度缓存区的数据,必须确保规定要读到缓存区的数据长度。如下图,在正确的情况下只能读取255个字符到规定的缓存中,而在不正确的情况下,就会无限制的读取,并导致函数的堆栈被破坏。
不正确情况
正确情况
memcpy |
Memcpy():由于不安全的使用memcpy()会导致不少溢出,当规定了长度的memcpy()函数可以受外界操作的时候,就可能发生溢出,所以保证拷贝到内存结构中的数据不大于规定大小将非常重要,下面一个示例将说明溢出是怎样产生的:
|
上面的示例来自与一个出现在BIND上的实际漏洞,在这里我们做了简化,上面的函数拷贝hp->h_length字节到’address’变量中(4字节),在正常情况下, 因为网络地址hp->h_length一般都是4字节,但是,攻击者确可以利用h_length变量,如果他伪造一个假的DNS回应,他就可以提供更长的地址通过hp-h_addr_list变量传递,这将导致超过4个字节大小拷贝到’address’变量,溢出变量,拷贝更多的数据到堆栈。
安全编码规范:
缓冲区溢出主要是一个C/C + + 问题,要想避免缓冲区溢出,在编写函数时应该遵循以下三个原则。
1. 要求代码传递缓冲区的长度
要求调用者传递缓冲区的长度,根据长度判断是否能够安全地复制数据。同时也不应该盲目地信任调用者提供的长度。
2. 探测内存
通过向目标缓冲区写入一个固定的已知值,可以在源缓冲区太大时,强制代码失败。同样这样也可以在开发过程中及早发现错误。与其运行攻击者的恶意有效代码,还不如让程序失败,这就是不直接复制缓冲区的原因。
3. 采取防范措施
探测虽然很有用,但并不能完全阻止攻击。真正安全的办法是编写防范性的代码。前面的代码已经具有防范性了,它将检查进入函数的数据是否不超过内部缓冲区。然而,有些函数在处理或复制不可靠的数据时,如果使用不当,则会存在潜在的严重安全问题。需要注意的函数包括诸如 Strcpy、Strcat、gets 等常见函数
代码检查关键词:strcpy,strcat,sprintf,gets,scanf,fscan,sscan,memcpy