使用 malloc 识别简单程序中的漏洞

信息安全 缓冲区溢出 C 安全编码
2021-09-11 13:14:10

我的课没有教科书,也没有任何结构化的学习材料。我所有的学习都是通过谷歌搜索完成的,当我边做边学的时候,说实话会很慢。

课堂作业:

文件 prog-bad.c 中有一个程序(有点伪代码)。这个程序不是为了编译或运行而编写的,而是为了阅读。

找出漏洞是什么,以及这些漏洞可能导致什么,并简要说明您发现的内容。

#include <stdio.h>
int main() {
int studentId;
char studentName[100], buffer[100], percentage[10];

char* firstName = malloc(sizeof(char)*50);
char* lastName = malloc(sizeof(char)*50);
char* graduateLevel = NULL;

printf("\n**********Welcome to Student Registration Service**********\n");
printf("\nPlease enter your 10 digit Student ID: ");
scanf("%d", &studentId);

printf("\nPlease enter your first name: ");
gets(firstName);

printf("\nPlease enter your last name: ");
gets(lastName);

{
    char level[2];
    printf("\nPlease enter your graduate level (UG/PG): ");
    gets(level);
    graduateLevel = &level;
}

strcpy(studentName, firstName);
strcat(studentName, lastName);

printf("\nPlease enter your percentage in Highschool (like 85.50%): ");
gets(percentage);
// Student record saving in file
updateStudentRecord();

printf("\nStudent record saved with the following details: \n");
printf("\nStudent ID: %d", studentId);
printf("\nStudent Name: %s", studentName);
printf("\nStudent percentage in Highschool: ");

printf(percentage);

free(firstName);
free(firstName);

return(0);
}

对于我的回答,我说的是使用所有打开程序以缓冲溢出攻击的scanfgets和函数。printf但是我有点矛盾,因为我看到程序使用了malloc,我认为这是一种通过将所有内容移动到不可执行堆来保护堆栈的方法。

那么这是否意味着该程序并不像我想象的那样不安全,这是一个棘手的问题?

1个回答

Malloc 用于 -

char* firstName = malloc(sizeof(char)*50);
char* lastName = malloc(sizeof(char)*50);

你是对的,这些都在堆上。但是这些数据会发生什么?这些功能是如何工作的?

还从用户那里读取了什么?如何?


在下方回复您的评论

因此,正如我在有限的范围和理解中看到的那样,它的价值是:所有的 printf,studentid 的 scanf

studentid 是一个整数。所以我相信对它的操作是安全的。

, 获取名字和姓氏

但是因为 malloc 用于名字和姓氏,这是否有点保护他们的所有获取实例。

获取后立即的数据将在堆上是的。但这能保证安全吗?如果用户在任一字段中输入超过 50 个字符会怎样?我建议阅读Heap Overflow即使损坏堆数据不是问题,也请查看代码对该数据的处理方式。strcpy 和 strcat 会做什么?

获取级别/获取百分比

是的。不仅会导致这两个溢出,而且在“graduateLevel = &level;”之后会发生什么 线?


回复第二条评论——

对你最后一点感到困惑,“graduateLevel=&level;”之后会发生什么?

“字符级别[2];” 在正上方的花括号内声明。这意味着它仅在该块范围内受到保护。该范围在“graduateLevel = &level;”之后的行结束。所以graduateLevel 指向level 曾经所在的地址。但是编译器不再有义务确保级别不被覆盖。在此之后使用 level 的任何操作都是未定义的行为。

现在这个问题非常小。编译器很有可能不会重用它。与从获取调用中读取的级别相比,它可以忽略不计。但是这样的代码经常会导致奇怪且难以追踪的错误——其中一些可以被利用。

我有点困惑,因为我已经阅读了一些 strcpy 和 strcat 是问题的地方,但我也发现它们不是?

首先,您需要阅读并理解strcpystrcat的定义。让我们来看看 strcpy -

将 src 指向的以 null 结尾的字节字符串(包括 null 终止符)复制到第一个元素由 dest 指向的字符数组中。

因此,它将源数组中的字符复制到目标数组中,直到遇到空终止符('\0' - 通常是字节值 0)。注意警告 -

如果 dest 数组不够大,则行为未定义。如果字符串重叠,则行为未定义。如果 dest 不是指向字符数组的指针或 src 不是指向以 null 结尾的字节字符串的指针,则行为未定义。

所以 - 对于这条线

strcpy(studentName, firstName);

这些适用吗?Dest 是 studentName - 一个 100 个字符的数组。Source 是 firstName - 一个指向 50 个字符数组的指针。但是 firstname 是否以 null 结尾?那么它的价值来自 -

gets(firstName);

获取定义为-

将 stdin 读入 str 指向的字符数组,直到找到换行符或出现文件结尾。在读入数组的最后一个字符之后立即写入一个空字符。换行符被丢弃但不存储在缓冲区中。

随着警告 -

gets() 函数不执行边界检查,因此该函数极易受到缓冲区溢出攻击。它不能安全使用(除非程序在限制标准输入上出现的内容的环境中运行)。出于这个原因,该功能已在 C99 标准的第三次勘误中被弃用,并在 C11 标准中完全删除。fgets() 和 gets_s() 是推荐的替代品。

永远不要使用gets()。

所以。如果我在 firstName 中输入“Dylan”,将包含“Dylan\0”。如果我输入 200 个字节会怎样?

该标准表示其未定义,但对于大多数实际系统而言,它很容易猜测。它只会继续写入 firstName 之后的任何内存。那会发生什么?好吧 - 如果它离开内存空间,进程可以合法地写入操作系统可能会杀死它。但如果它没有,那么它将继续运行,直到出现足以导致它崩溃的错误(任何应该存储的东西都不再存在)或程序终止。现在,正如您指出的那样,损害是有限的——这些数据存储在堆上。

所以。让我们假设在 strcpy 之前一切都没有问题。那里会发生什么?strcpy 不知道 firstName 应该只有 50 个字符。或者那个 studentName 应该只有 100 个字符。它只会继续复制,直到遇到空终止符。

lastName 和 strcat 有完全相同的问题。