为什么这个程序不安全?

3
我有一份作业,需要分析这个C程序并回答三个问题。以下是该程序:
int main()
{
    // Define buffers to store username and password
    char username[16];
    char password[16];
    // Read username and password from user input
    printf("Enter your name: ");
    scanf("%s", username);
    printf("Enter your password: ");
    scanf("%s", password);
    printf("[SHOUTING OUT LOUD] Hello, %s!\n", username);
    return 0;
}

这些是问题:
  1. 存在一个安全漏洞,可能会将密码打印出来。这是如何发生的?
  2. 如何使程序崩溃?
  3. 更正程序,以便不再有安全漏洞。
我认为问题在于scanf方法,因为可以扫描的字符数量没有限制。所以我认为通过输入一个长字符串,可能会导致分段错误,因为您想要将字符保存在用户不得引用的内存中。这就是我对第二个问题的答案。对于第三个问题的答案是,可以通过限制可以扫描的字符数来纠正程序。但是我似乎找不到第一个问题的答案。你能帮我解决这个问题吗?

4
当用户输入一个长度超过15个字符的字符串时会发生什么? - 001
2
另一个提示:如果您输入的用户名或密码超过15个字符,不一定会出现分段错误。请考虑在哪些情况下不会发生此类错误(例如,您可能会越界缓冲区但仍然访问自己的内存)。 - lurker
2
顺便说一句,利用这个缓冲区溢出可以完全控制该程序。 - dan1st
1
你可能需要展示如何处理(3); 有多种方法可以做到这一点。 - Jonathan Leffler
2
说实话,这种代码有时仍然会出现,这就是为什么用 C 进行此类工作很危险的原因... 这种类型的代码可能会影响整个系统,具体取决于其他操作系统级别的安全措施是否存在。 - Michael Dorgan
2个回答

4

想象一下,如果实现将用户名和密码依次存储在内存中。非常重要的是,在输出密码之前停止打印语句。但是只有在用户名字段中遇到零字节时,它才会这样做。如果用户名太长,则用户名字段中将没有零字节,因此打印可能会继续输出密码字段的内容。


scanf会覆盖密码,直到写入\0为止,不是吗? - dan1st
2
@dan1st 它将使用密码后面跟着一个零字节来覆盖用户名中溢出的任何内容。因此,打印语句将输出用户名的前十六个字符,后跟密码,并在最后一个 scanf 写入的那个零处停止。 - David Schwartz
但是 printf 不会打印密码,它之前的所有内容都会被覆盖,而 \0 之后的所有内容也不会被打印。 - dan1st
我不明白你在说什么。用户名的输入不会在用户名字段中输入零。密码的输入将把密码放在密码字段中。然后打印机不会停止,直到它打印出密码字段中的所有内容,因为那里面没有零,这是唯一能让它停下来的东西。 - David Schwartz
我刚刚忘记了密码是在用户名之后输入的。 - dan1st

4

你的scanf调用并没有限制用户输入的字节数量。

如果精心制作输入,可能会导致任意代码执行,因为字符串可能会覆盖栈上的返回地址,注入任意代码等等。

注入的代码可以检查return后留下的栈帧内存,并打印密码。

密码在调用main函数时为password变量分配栈上的地址,在main返回后,密码值仍然保存在该内存中(即未被清除)。

这需要注入代码执行更改剩余内存的栈操作。虽然有些困难,但并非不可能。

要修复,请尝试(例如):

scanf("%15s",username);

另一个需要考虑的事情是,通常情况下,通过 scanf 输入的信息会被TTY驱动程序回显。

这是我们获取username时想要的。但是,在scanf读取password之前,我们需要在TTY层禁用回显。

我们需要使用 tcgetattrtcsetattr 进行设置。

但是,如果这样做,很难使其与scanf一起正常工作。因此,我们应该切换到纯粹的read(0,buf,1)调用(例如)。


"%99s" 对于一个15个字符的用户名来说有点过了... - dan1st

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