缓冲区溢出示例:为什么这段代码很危险?
Buffer Overflow Sample: Why is this code dangerous?
我是 c 的新手,我正在阅读一本关于软件漏洞的书,我遇到了这个缓冲区溢出示例,它提到这会导致缓冲区溢出。我正在尝试确定这是怎么回事。
int handle_query_string(char *query_string)
{
struct keyval *qstring_values, *ent;
char buf[1024];
if(!query_string) {
return 0;
}
qstring_values = split_keyvalue_pairs(query_string);
if((ent = find_entry(qstring_values, "mode")) != NULL) {
sprintf(buf, "MODE=%s", ent->value);
putenv(buf);
}
}
我正在密切关注这段代码,因为这似乎是导致缓冲区溢出的地方。
if((ent = find_entry(qstring_values, "mode")) != NULL)
{
sprintf(buf, "MODE=%s", ent->value);
putenv(buf);
}
我想就到这里了,因为你的buf
只有1024,因为ent->value
可以有1024以上,所以这可能会溢出。
sprintf(buf, "MODE=%s", ent->value);
但取决于 split_keyvalue_pairs(query_string)
的实现。如果这个函数已经检查值并威胁它(我怀疑)。
这里有一件很重要的事情是你将一个指向局部变量的指针传递给了putenv
。当 handle_query_string
returns 时缓冲区将不复存在。之后它将包含垃圾变量。请注意,putenv
确实要求传递给它的字符串在程序的其余部分保持不变。来自 putenv 的文档(强调我的):
int putenv(char *string);
The putenv()
function adds or changes the value of environment variables. The argument string
is of the form name=value. If name does not already exist in the environment, then string
is added to the environment. If name does exist, then the value of name in the environment is changed to value. The string pointed to by string
becomes part of the environment, so altering the string changes the environment.
这可以通过使用动态分配来纠正。 char *buf = malloc(1024)
而不是 char buf[1024]
另一件事是 sprintf(buf, "MODE=%s", ent->value);
可能会溢出。如果字符串 ent->value
太长,就会发生这种情况。一个解决方案是使用 snprintf
代替。
snprintf(buf, sizeof buf, "MODE=%s", ent->value);
这可以防止溢出,但它仍然可能会导致问题,因为如果 ent->value
太大而无法放入 buf
,那么 buf
将出于显而易见的原因不包含完整的字符串.
这是纠正这两个问题的方法:
int handle_query_string(char *query_string)
{
struct keyval *qstring_values, *ent;
char *buf = NULL;
if(!query_string)
return 0;
qstring_values = split_keyvalue_pairs(query_string);
if((ent = find_entry(qstring_values, "mode")) != NULL)
{
// Make sure that the buffer is big enough instead of using
// a fixed size. The +5 on size is for "MODE=" and +1 is
// for the string terminator
const char[] format_string = "MODE=%s";
const size_t size = strlen(ent->value) + 5 + 1;
buf = malloc(size);
// Always check malloc for failure or chase nasty bugs
if(!buf) exit(EXIT_FAILURE);
sprintf(buf, format_string, ent->value);
putenv(buf);
}
}
由于我们使用的是 malloc
,分配将在函数退出后保留。出于同样的原因,我们事先确保缓冲区足够大,因此没有必要使用 snprintf
而不是 sprintf
。
理论上,这会导致内存泄漏,除非您在分配的所有字符串上使用 free
,但实际上,在退出 main
之前不释放很少有问题。不过知道还是不错的。
同样值得一提的是,尽管这段代码现在受到了相当程度的保护,但它仍然不是线程安全的。 query_string
的内容以及 ent->value
的内容可能会被更改。您的代码没有显示它,但很可能 find_entry
returns 一个指向 query_string
某处的指针。这个当然也可以解决,但是比较复杂
klutt 在之前的回答中很好地解决了这个问题,因此我将尝试更加具体和深入地了解代码中溢出的确切性质。
char buf[1024];
这一行在堆栈上分配了 1024 个字节,由名为 buf
的指针寻址。这里的大问题是它在堆栈上。如果您使用 malloc(或我最喜欢的:calloc)动态分配,它将在堆上。该位置不一定能防止或修复溢出。但它可以改变效果。就在上面(给或取一些字节)堆栈上的这个 space 将是函数的 return 地址,并且溢出可以改变导致程序在 returns 时重定向.
sprintf(buf, "MODE=%s", ent->value);
这一行实际上执行了溢出。 sprintf = "字符串打印格式。"这意味着目标是一个字符串 (char *),而您正在打印一个格式化的字符串。它不关心长度,它只取目的字符串的起始内存地址,一直写到写完。如果要写入的字符超过 1024 个(在这种情况下),那么它将越过缓冲区的末尾并溢出到内存的其他部分。解决方案是改用函数 snprint。 “n”告诉您它将限制写入目标的数量,并避免溢出。
最终要理解的是“缓冲区”实际上并不存在。这根本不是一回事。这是我们用来对内存中的区域进行排序的概念,但计算机不知道缓冲区是什么,它从哪里开始,或者从哪里结束。所以在写的时候,计算机并不关心它是在缓冲区内还是缓冲区外,也不知道从哪里停止写。所以,我们需要非常明确地告诉它允许写入多远,否则它会一直写入。
我是 c 的新手,我正在阅读一本关于软件漏洞的书,我遇到了这个缓冲区溢出示例,它提到这会导致缓冲区溢出。我正在尝试确定这是怎么回事。
int handle_query_string(char *query_string)
{
struct keyval *qstring_values, *ent;
char buf[1024];
if(!query_string) {
return 0;
}
qstring_values = split_keyvalue_pairs(query_string);
if((ent = find_entry(qstring_values, "mode")) != NULL) {
sprintf(buf, "MODE=%s", ent->value);
putenv(buf);
}
}
我正在密切关注这段代码,因为这似乎是导致缓冲区溢出的地方。
if((ent = find_entry(qstring_values, "mode")) != NULL)
{
sprintf(buf, "MODE=%s", ent->value);
putenv(buf);
}
我想就到这里了,因为你的buf
只有1024,因为ent->value
可以有1024以上,所以这可能会溢出。
sprintf(buf, "MODE=%s", ent->value);
但取决于 split_keyvalue_pairs(query_string)
的实现。如果这个函数已经检查值并威胁它(我怀疑)。
这里有一件很重要的事情是你将一个指向局部变量的指针传递给了putenv
。当 handle_query_string
returns 时缓冲区将不复存在。之后它将包含垃圾变量。请注意,putenv
确实要求传递给它的字符串在程序的其余部分保持不变。来自 putenv 的文档(强调我的):
int putenv(char *string);
The
putenv()
function adds or changes the value of environment variables. The argumentstring
is of the form name=value. If name does not already exist in the environment, thenstring
is added to the environment. If name does exist, then the value of name in the environment is changed to value. The string pointed to bystring
becomes part of the environment, so altering the string changes the environment.
这可以通过使用动态分配来纠正。 char *buf = malloc(1024)
而不是 char buf[1024]
另一件事是 sprintf(buf, "MODE=%s", ent->value);
可能会溢出。如果字符串 ent->value
太长,就会发生这种情况。一个解决方案是使用 snprintf
代替。
snprintf(buf, sizeof buf, "MODE=%s", ent->value);
这可以防止溢出,但它仍然可能会导致问题,因为如果 ent->value
太大而无法放入 buf
,那么 buf
将出于显而易见的原因不包含完整的字符串.
这是纠正这两个问题的方法:
int handle_query_string(char *query_string)
{
struct keyval *qstring_values, *ent;
char *buf = NULL;
if(!query_string)
return 0;
qstring_values = split_keyvalue_pairs(query_string);
if((ent = find_entry(qstring_values, "mode")) != NULL)
{
// Make sure that the buffer is big enough instead of using
// a fixed size. The +5 on size is for "MODE=" and +1 is
// for the string terminator
const char[] format_string = "MODE=%s";
const size_t size = strlen(ent->value) + 5 + 1;
buf = malloc(size);
// Always check malloc for failure or chase nasty bugs
if(!buf) exit(EXIT_FAILURE);
sprintf(buf, format_string, ent->value);
putenv(buf);
}
}
由于我们使用的是 malloc
,分配将在函数退出后保留。出于同样的原因,我们事先确保缓冲区足够大,因此没有必要使用 snprintf
而不是 sprintf
。
理论上,这会导致内存泄漏,除非您在分配的所有字符串上使用 free
,但实际上,在退出 main
之前不释放很少有问题。不过知道还是不错的。
同样值得一提的是,尽管这段代码现在受到了相当程度的保护,但它仍然不是线程安全的。 query_string
的内容以及 ent->value
的内容可能会被更改。您的代码没有显示它,但很可能 find_entry
returns 一个指向 query_string
某处的指针。这个当然也可以解决,但是比较复杂
klutt 在之前的回答中很好地解决了这个问题,因此我将尝试更加具体和深入地了解代码中溢出的确切性质。
char buf[1024];
这一行在堆栈上分配了 1024 个字节,由名为 buf
的指针寻址。这里的大问题是它在堆栈上。如果您使用 malloc(或我最喜欢的:calloc)动态分配,它将在堆上。该位置不一定能防止或修复溢出。但它可以改变效果。就在上面(给或取一些字节)堆栈上的这个 space 将是函数的 return 地址,并且溢出可以改变导致程序在 returns 时重定向.
sprintf(buf, "MODE=%s", ent->value);
这一行实际上执行了溢出。 sprintf = "字符串打印格式。"这意味着目标是一个字符串 (char *),而您正在打印一个格式化的字符串。它不关心长度,它只取目的字符串的起始内存地址,一直写到写完。如果要写入的字符超过 1024 个(在这种情况下),那么它将越过缓冲区的末尾并溢出到内存的其他部分。解决方案是改用函数 snprint。 “n”告诉您它将限制写入目标的数量,并避免溢出。
最终要理解的是“缓冲区”实际上并不存在。这根本不是一回事。这是我们用来对内存中的区域进行排序的概念,但计算机不知道缓冲区是什么,它从哪里开始,或者从哪里结束。所以在写的时候,计算机并不关心它是在缓冲区内还是缓冲区外,也不知道从哪里停止写。所以,我们需要非常明确地告诉它允许写入多远,否则它会一直写入。