为什么这段代码容易受到缓冲区溢出攻击?

时间:2015-04-28 04:46:21

标签: c security buffer-overflow

class water

此代码容易受到缓冲区溢出攻击,我正在试图找出原因。我认为这与int func(char* str) { char buffer[100]; unsigned short len = strlen(str); if(len >= 100) { return (-1); } strncpy(buffer,str,strlen(str)); return 0; } 被宣布为len而不是short有关,但我不确定。

有什么想法吗?

5 个答案:

答案 0 :(得分:193)

在大多数编译器中,unsigned short的最大值为65535。

上面的任何值都会被包围,因此65536变为0,而65600变为65。

这意味着正确长度的长字符串(例如65600)将通过检查,并溢出缓冲区。

使用size_t存储strlen()的结果,而不是unsigned short,并将len与直接编码buffer大小的表达式进行比较。例如:

char buffer[100];
size_t len = strlen(str);
if (len >= sizeof(buffer) / sizeof(buffer[0]))  return -1;
memcpy(buffer, str, len + 1);

答案 1 :(得分:28)

问题在于:

strncpy(buffer,str,strlen(str));
                   ^^^^^^^^^^^

如果字符串大于目标缓冲区的长度,strncpy仍将复制它。您将字符串的字符数作为要复制的数字而不是缓冲区的大小。正确的方法如下:

strncpy(buffer,str, sizeof(buff) - 1);
buffer[sizeof(buff) - 1] = '\0';

这样做会限制复制到缓冲区实际大小的数据量减去空终止字符的数据量。然后我们将缓冲区中的最后一个字节设置为空字符作为添加的安全措施。这是因为strncpy将复制到n个字节,包括终止空值,如果strlen(str)< len - 1.如果没有,那么就不会复制null并且你有崩溃的情况,因为现在你的缓冲区有一个未终止的字符串。

希望这有帮助。

编辑:经过进一步审查和其他人的意见后,可能会对该函数进行编码:

int func (char *str)
  {
    char buffer[100];
    unsigned short size = sizeof(buffer);
    unsigned short len = strlen(str);

    if (len > size - 1) return(-1);
    memcpy(buffer, str, len + 1);
    buffer[size - 1] = '\0';
    return(0);
  }

由于我们已经知道了字符串的长度,我们可以使用memcpy将字符串从str引用的位置复制到缓冲区中。请注意,根据strlen(3)的手册页(在FreeBSD 9.3系统上),说明如下:

 The strlen() function returns the number of characters that precede the
 terminating NUL character.  The strnlen() function returns either the
 same result as strlen() or maxlen, whichever is smaller.

我将其解释为字符串的长度不包括null。这就是为什么我复制len + 1个字节以包含null,并且测试检查以确保长度<缓冲区的大小 - 2.减1,因为缓冲区从位置0开始,减去另一个缓冲区,以确保空的空间。

编辑:事实证明,某事物的大小从1开始,而访问从0开始,所以前面的-2是不正确的,因为它会返回任何错误> 98字节,但应该是> 99字节。

编辑:虽然关于无符号短路的答案通常是正确的,因为可以表示的最大长度是65,535个字符,但这并不重要,因为如果字符串比这长,则值将环绕。它就像取75,231(即0x000125DF)并屏蔽前16位,给你9695(0x000025DF)。我看到的唯一问题是超过65,535的前100个字符,因为长度检查将允许复制,但它只会复制到字符串的前100个字符,并且null终止字符串< / strong>即可。因此,即使存在环绕问题,缓冲区仍然不会溢出。

这可能会也可能不会产生安全风险,具体取决于字符串的内容以及您使用它的内容。如果它只是人类可读的直文,那么通常没有问题。你只是得到一个截断的字符串。但是,如果它类似于URL甚至是SQL命令序列,那么您可能会遇到问题。

答案 2 :(得分:11)

即使你正在使用strncpy,截止的长度仍然取决于传递的字符串指针。你不知道该字符串有多长(空终止符相对于指针的位置,即)。因此,单独调用strlen会让您感受到漏洞。如果您想更安全,请使用strnlen(str, 100)

更正的完整代码将是:

int func(char *str) {
   char buffer[100];
   unsigned short len = strnlen(str, 100); // sizeof buffer

   if (len >= 100) {
     return -1;
   }

   strcpy(buffer, str); // this is safe since null terminator is less than 100th index
   return 0;
}

答案 3 :(得分:4)

包装的答案是正确的。但是我认为有一个问题没有被提及 if(len&gt; = 100)

如果Len是100,我们会复制100个元素,我们没有尾随\ 0。这显然意味着任何其他函数取决于正确结束的字符串将超出原始数组。

来自C的字符串是IMHO无法解决的。你最好在通话前有一些限制,但即便如此也无济于事。没有边界检查,所以缓冲区溢出总是可以,不幸的是会发生....

答案 4 :(得分:3)

除了多次调用strlen所涉及的安全问题之外,通常不应该对长度准确已知的字符串使用字符串方法[对于大多数字符串函数,只有一个非常狭窄的情况应该使用它们 - 在可以保证最大长度的弦上,但是不知道精确的长度]。一旦输入字符串的长度已知并且输出缓冲区的长度已知,就应该确定应该复制一个区域的大小,然后使用memcpy()来实际执行相关副本。虽然复制只有1-3个字节左右的字符串时strcpy可能胜过memcpy(),但在许多平台上memcpy()的速度可能会快两倍以上在处理更大的字符串时。

虽然在某些情况下安全性会以性能为代价,但这种情况下安全方法更快。在某些情况下,编写对于奇怪行为输入不安全的代码可能是合理的,如果提供输入的代码可以确保它们表现良好,并且如果防止不良行为会影响性能。确保仅检查字符串长度一次可以提高 性能和安全性,但即使手动跟踪字符串长度,也可以做一件额外的事情来保护安全性:对于每个预期有尾随空值的字符串,显式写入尾部空值而不是期望源字符串拥有它。因此,如果有人写了strdup等价物:

char *strdupe(char const *src)
{
  size_t len = strlen(src);
  char *dest = malloc(len+1);
  // Calculation can't wrap if string is in valid-size memory block
  if (!dest) return (OUT_OF_MEMORY(),(char*)0); 
  // OUT_OF_MEMORY is expected to halt; the return guards if it doesn't
  memcpy(dest, src, len);      
  dest[len]=0;
  return dest;
}

请注意,如果memcpy处理了len+1个字节,则通常可以省略最后一个语句,但另一个线程是修改源字符串,结果可能是非NUL终止的目标字符串。