首页 文章

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

提问于
浏览
147
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 而不是 int 有关,但我不太确定 .

Any ideas?

5 回答

  • 27

    在大多数编译器中, 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);
    
  • 4

    问题出在这里:

    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系统上),说明如下:

    strlen()函数返回前面的字符数
    终止NUL字符 . strnlen()函数返回
    与strlen()或maxlen相同的结果,以较小者为准 .

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

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

    编辑:虽然关于无符号短路的答案通常是正确的,因为可以表示的最大长度是65,535个字符,但它不像75,231(即0x000125DF)并且屏蔽掉前16位给出9695(0x000025DF) . 我看到的唯一问题是超过65,535的前100个字符,因为长度检查将允许复制, but it will only copy up to the first 100 characters of the string in all cases and null terminate the string . 因此,即使存在环绕问题,缓冲区仍然不会溢出 .

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

  • 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

    包装的答案是正确的 . 但是如果(len> = 100)我认为没有提到问题

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

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

  • 192

    除了多次调用 strlen 所涉及的安全问题之外,通常不应对长度精确已知的字符串使用字符串方法[对于大多数字符串函数,已知's only a really narrow case where they should be used--on strings for which a maximum length can be guaranteed, but the precise length isn't] . 一旦输入字符串的长度已知并且输出缓冲区的长度已知,就应该确定应该复制多大的区域,然后使用 memcpy() 来实际执行相关的副本 . 虽然 strcpy 在复制只有1-3个字节左右的字符串时可能会胜过 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终止的目标字符串 .

相关问题