函数strcpy是否总是危险的?

4

像strcpy、gets等函数总是不安全的吗?如果我写了下面这样的代码,会怎么样:

int main(void)
{

char *str1 = "abcdefghijklmnop";
char *str2 = malloc(100); 
strcpy(str2, str1);


}

这种方式的函数不接受参数,str变量的长度始终相同...在这里是16或稍微长一点,具体取决于编译器版本...但是从2011年3月开始,100就足够了 :). 黑客有没有办法利用上面的代码呢? 谢谢!


2
如果在Wikipedia上有提到,那么它一定是安全的! - David Heffernan
1
另一方面,也许他们不需要任何更多的动力!! ;-) - David Heffernan
一个完美的答案可能需要一些背景说明为什么字符串函数本身可能被认为是不安全的。并不是每个人都知道什么是缓冲区溢出攻击... - Merlyn Morgan-Graham
嗯...我想每个人都应该知道这是什么:)。即使不是那么高级的程序员...像我一样。幸运的是,现在有“东西”像防火墙、dep、alsr、gcc警告来保护甚至最可悲的程序员免受BO的侵害...还有c# : )。对于D.Hefferman:维基百科只有84%的准确率:)...但在这个例子中我认为没问题。 - someoneyeah
为了安全起见,您可以查看:https://randomascii.wordpress.com/2013/04/03/stop-using-strncpy-already/ - ch271828n
9个回答

15

绝对不是这样的。与微软针对他们的非标准函数的营销活动相反,strcpy是安全的,当使用正确时。

上面的代码有些冗余,但大体是安全的。唯一潜在的问题是您没有检查malloc的返回值,因此可能会出现null引用(正如kotlinski所指出的)。实际上,这很可能会导致立即的SIGSEGV和程序终止。

一种不正确和危险的使用方式是:

char array[100];
// ... Read line into uncheckedInput
// Extract substring without checking length
strcpy(array, uncheckedInput + 10);

这段代码不安全,因为 strcpy 可能会溢出,导致未定义的行为。 实际上,这很可能会覆盖其他本地变量(这本身就是一个重大的安全漏洞)。 这些变量之一可能是返回地址。 通过 return to lib C 攻击,攻击者可能能够使用像 system 这样的 C 函数来执行任意程序。 溢出还可能有其他可能的后果。

然而,gets 确实是固有不安全的,并将在下一个 C 版本(C1X)中被删除。根本没有办法确保输入不会溢出(导致与上述相同的后果)。 有些人会认为在使用已知输入文件时是安全的,但真的没有理由去使用它。 POSIX 的 getline 是一个更好的选择。

此外,str1 的长度不会因为编译器的不同而变化。 它应该始终为17,包括终止的 NUL 字符。


+1,但是“Contract to Microsoft's”...你的意思是相反吗?如果不是,我从未听过“contract”被用于那种方式。 - Merlyn Morgan-Graham
大多数情况下都同意 - 只是你不能争论有些人会更喜欢使用gets()而不是getline等 - 因此gets并不总是危险的。而且,大小并不总是17 - 例如,在gcc 2.95之后,您最有可能在一个定义了确实只能容纳100字节的缓冲区中获得106或100多个字节 - 因为栈每16个字节对齐而不是逐字节对齐。 - someoneyeah
@某人,字符串的长度为17。堆栈对齐很重要,但它是内部实现相关的。我认为gets总是很危险的,因为输入文件可能会意外更改。 - Matthew Flaschen

7

你正在强制把完全不同的事物归为一类。

gets函数总是存在安全隐患。无论你采取什么措施,多么防御性地编程,都不可能对gets调用进行安全保证。

如果你愿意采取必要简单的措施来确保你对strcpy的调用安全,那么strcpy函数就是完全安全的。

这已经将getsstrcpy归为完全不同的类别,它们在安全方面没有任何共同之处。

针对strcpy安全性的普遍批评完全基于社会观察,而非实际事实,例如“程序员懒惰而且无能,所以不能让他们使用strcpy”。当然,在C编程环境中,这纯属胡言乱语。如果按照这种逻辑,我们也应该宣布除法运算符因为完全相同的原因而不安全。

事实上,strcpy不存在任何问题。正如我上面提到的,gets则完全不同。


检查缓冲区的大小,然后如果大小小于...则调用gets?这样会有效吗?或者我猜需要更高级的方法...比如异步调用来模拟按键输入,这样实际上系统只使用get - 并且系统没有恶意提供比可能更多的数据给gets。 - someoneyeah
@someoneyeah:我不明白你的意思。gets从外部世界读取数据。在gets实际开始读取并填充缓冲区之前,无法预测将有多少数据存在。 - AnT stands with Russia
@某人,你无法提前检查缓冲区大小,因为你事先不知道输入行的长度。 - Matthew Flaschen
好的...那我想我应该说不要检查缓冲区的大小,而是定义一个变量的大小,然后看它是否超过了定义的大小。无论如何,这是一个非常简单的概念。 - someoneyeah
1
@某人,不是这样的。无论有多少个变量,都无法使“gets”方法能够安全地处理未知输入。因为你根本无法提前知道输入行的长度。 - Matthew Flaschen
如果您在使用 gets() 之前“检查”行的长度 - 那么您可以在“检查”函数中读取数据。这就打败了首次使用 gets() 的目的。 ;) 再说一遍,使用 gets() 时您无法预先知道其大小,如果您确实知道它的大小 - 那么为什么数据还没有被存储呢? - Xupicor

6

是的,这很危险。经过5年的维护,你的代码将会变成这样:

int main(void)
{

char *str1 = "abcdefghijklmnop";

{enough lines have been inserted here so as to not have str1 and str2 nice and close to each other on the screen}

char *str2 = malloc(100); 
strcpy(str2, str1);


}

到那时,有人会更改str1为:

str1="这是一个非常长的字符串,如果在复制它时不采取预防措施来范围检查字符串的限制,它将会溢出任何正在使用的缓冲区。当修复一个五年前存在的漏洞时,很少有人记得这样做."

然后忘记查看str1的使用位置,接着随机错误就开始发生了...


2
这是关于楼主的示例代码的讨论,当然这不是一个真实的应用程序,但并没有回答问题。 - Jim Balter
1
是的,它回答了问题。使用strcpy是危险的。无论您在编码时多么小心,由于API允许引入错误的便利性(因为调用者和被调用者之间存在隐含的假设,即dest有足够的空间),以及错误的严重后果(最好是核心转储,最坏的情况下会演变成随机内存错误和缓冲区溢出攻击)。 - csd
2
叹气。p = malloc(strlen(s)+1); if(!p) error; strcpy(p, s) 并不危险。按照你的论点,所有编程,特别是在C语言中,都是危险的,但这显然不是问题所在。 - Jim Balter

1
唯一可能导致 malloc 失败的方式是发生内存不足错误,这本身就是一场灾难。您无法可靠地从中恢复,因为几乎任何事情都可能再次触发它,并且操作系统很可能会终止您的进程。

1
你的代码不安全。未检查malloc的返回值,如果失败并返回0,则strcpy将产生未定义行为。
除此之外,我没有看到其他问题,基本上这个示例什么也没做。

好的,我的错 - 感谢你们的回答和Peaker指出的问题 - 但是你在这里提出的建议并不严格涉及strcpy和缓冲区溢出 - 这实际上是我提问的主要动机。但无论如何 - 你是对的...是我的错误。 - someoneyeah

1

strcpy并不危险,只要你知道目标缓冲区足够大以容纳源字符串的字符; 否则,strcpy将愉快地复制比目标缓冲区更多的字符,这可能会导致几种不幸的后果(堆栈/其他变量覆盖,可能导致崩溃、堆栈破坏攻击等)。

但是:如果您有一个未经检查的通用char * 输入,唯一确定的方法是对该字符串应用strlen并检查它是否过大而无法容纳在您的缓冲区中; 但是,现在您必须两次遍历整个源字符串,一次用于检查其长度,一次用于执行复制。

这是次优的,因为如果strcpy稍微先进一点,它可以接收缓冲区的大小作为参数,并在源字符串过长时停止复制;在完美的世界中,这就是strncpy的表现方式(遵循其他strn***函数的模式)。然而,这不是一个完美的世界,strncpy并没有设计成这样。相反,非标准(但流行的)替代方案是{{link1:strlcpy}},它不会越过目标缓冲区的边界,而是截断。

一些CRT实现不提供此功能(特别是glibc),但您仍然可以获得其中一个BSD实现并将其放入应用程序中。标准(但速度较慢)的替代方案是使用snprintf"%s"作为格式字符串。

既然你正在使用C++进行编程(编辑我现在看到C++标签已被删除),为什么不避免所有的C字符串烦恼(当然,如果可以的话),改用std::string呢?所有这些潜在的安全问题都会消失,字符串操作也变得更加容易。

我并没有删除c标签......所提到的代码应该足够简单,适用于c和c++,顺便说一句,我并没有提到std命名空间 - 尽管我总体上更喜欢c++。 - someoneyeah

0
正如您所指出的,在受限情况下,strcpy并不危险。通常会将一个字符串参数传入并将其复制到本地缓冲区中,这时可能会导致缓冲区溢出而变得危险。请记住在调用strcpy之前检查您的复制长度,并在之后加上空终止符来保证安全。

1
strcpy总是将NUL字节写入目标,无论它是否溢出(它无法知道)。 - Matthew Flaschen
没错,我在考虑其中一个长度受限制的版本。谢谢! - Avilo

0

除了可能会取消引用 NULL(因为您没有检查 malloc 的结果)这是 UB 并且可能不是安全威胁之外,此代码没有潜在的安全问题。


“而且不太可能构成安全威胁” - 嗯,这并不完全正确(http://chargen.matasano.com/chargen/2007/7/3/this-new-vulnerability-dowds-inhuman-flash-exploit.html)... - BlueRaja - Danny Pflughoeft
@BlueRaja,他们没有对空指针进行解引用。他们正在对“NULL + 0x8f71ba90”(或任何偏移量)进行解引用。 - Matthew Flaschen
@Matthew:是的,我知道,我只是想发布这个有趣的故事(这是第一个也是我所知道的唯一的空指针漏洞实例)。 - BlueRaja - Danny Pflughoeft

0

gets()总是不安全的; 其他函数可以安全使用。
即使您完全控制输入,gets()也是不安全的 -- 总有一天,程序可能会被其他人运行。

使用gets()的唯一安全方法是将其用于单次运行:创建源代码;编译;运行;删除二进制文件和源代码;解释结果。


为什么关于strcpy()的问题的答案只涉及gets()?我理解两者之间的联系,但是... - BlackBear
@BlackBear:答案的第一个版本确实只涉及到gets函数,但是当你发表评论时的当前版本已经说过:“其他函数可以安全使用”:这意味着strcpy等函数。 - pmg
我不认为这是一个答案,尽管我没有给你点踩。顺便说一句,strcpy()并不总是安全的(请参见@Matthew Flaschen的回答)。 - BlackBear
1
我刚才说strcpy可以安全使用,但这与总是安全是不同的。两个无符号值的相加总是安全的;而两个有符号值的相加是不安全的(尽管比strcpy更安全)。i++是不安全的——谁会检查溢出呢? - pmg
<pedantic>实际上我见过使用gets函数安全的例子,传递给它一个指向已分配内存页和后面的守卫页的指针。</pedantic> - Matteo Italia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接