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

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 ,但是我不确定。

有任何想法吗?

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

任何高于该值的值都将被缠绕,所以65536变为0,65600变为65。

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


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

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

问题在这里:

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

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

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

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

希望这可以帮助。

编辑:经过进一步的检查和其他人的input,可能的function编码如下:

 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); } 

由于我们已经知道string的长度,所以我们可以使用memcpy将string从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. 

我认为这是string的长度不包括null。 这就是为什么我复制len + 1个字节以包含null,并且testing检查以确定缓冲区的长度<size – 2.减1,因为缓冲区从0开始,减去另一个以确保有空间为null。

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

编辑:虽然有关无符号短的答案通常是正确的,因为可以表示的最大长度是65535个字符,它并不重要,因为如果string比这更长,值将环绕。 这就像75,231(这是0x000125DF)和屏蔽前16位给你9695(0x000025DF)。 我看到的唯一的问题是前65535的前100个字符作为长度检查将允许复制, 但它将只复制到string的前100个字符在所有情况下和null终止string 。 所以,即使在包装问题,缓冲区仍然不会溢出。

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

即使你正在使用strncpy ,截断的长度仍然依赖于传递的string指针。 你不知道这个string有多长(相对于指针的空终止符的位置)。 所以单独调用strlenstrlen漏洞。 如果你想更安全,使用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; } 

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

那么如果Len是100,我们就会复制100个元素,我们不会有\ 0。 这显然意味着取决于正确结束的string的任何其他函数将走出原来的数组。

从C有问题的string恕我直言不能解决。 在通话之前,你总是会有一些限制,但即使这样也没有帮助。 没有边界检查,所以缓冲区溢出总是可以,不幸的是会发生….

除了涉及多次调用strlen的安全问题之外,通常不应该在string的长度已知的情况下使用string方法[对于大多数string函数,只有一个非常窄的情况,应该使用它们 -最大长度可以保证,但确切的长度是未知的]。 一旦知道了inputstring的长度并知道输出缓冲区的长度,就应该找出应该复制多大的区域,然后使用memcpy()来实际执行所涉及的副本。 尽pipe在复制一个只有1-3个字节的string时, strcpy可能优于memcpy() ,但在许多平台上, memcpy()在处理较大的string时可能会快两倍以上。

尽pipe在某些情况下,安全会以牺牲性能为代价,但这种情况下安全的方法也是最快的。 在某些情况下,编写对于奇怪行为input不安全的代码可能是合理的,如果提供input的代码可以确保它们的行为良好,并且防止不良行为会影响性能。 确保只检查一次string长度可以提高性能和安全性,但是即使在手动跟踪string长度时也可以做一些额外的事情来帮助保护安全性:对于每个预期具有结尾空值的string,都明确地写入尾部空值比期望源string拥有它。 因此,如果你正在写一个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个字节,则通常可以省略最后一个语句,但是另一个线程修改源string的结果可能是非NUL终止的目标string。