在任务 2.B 中,如果你已正确完成所有操作,但仍无法成功攻击,请检查 /tmp/XYZ 的所有权。 您会发现 /tmp/XYZ 的所有者已成为 root(通常应该是 seed)。 如果发生这种情况,你的攻击将永远不会成功,因为你的攻击程序以 seed 的权限运行,无法再删除或 unlink() 它。 这是因为 /tmp 文件夹有一个“粘性”位,这意味着只有文件的所有者才能删除该文件,即使该文件夹是全局可写的。
在任务 2.B 中,我们让你使用 root 的权限删除 /tmp/XYZ,然后再次尝试你的攻击。而不希望的情况随机发生,因此通过重复攻击(在 root 的“帮助”下),你最终将在任务 2.B 中取得成功。 显然,从 root 获取帮助并不是真正的攻击。 我们想摆脱它,并在没有 root 帮助的情况下做到这一点。
这种情况发生的主要原因是我们的攻击程序有问题,同样也有一个竞态条件问题,正是我们试图在受害者程序中利用的问题。 (这很有讽刺性!)
攻击程序在删除 /tmp/XYZ(即 unlink())之后,在将名称链接到另一个文件(即 symlink())之前立即执行 access 函数。删除现有符号链接并创建新符号链接的操作不是原子性的(它涉及两个单独的系统调用)。因此,如果函数执行发生在中间,并且目标 Set-UID 程序有机会运行其 fopen(fn, "a+") 语句,它将创建一个以 root 为所有者的新文件。 之后,你的攻击程序将无法再更改 /tmp/XYZ。
基本上,使用 unlink() 和 symlink() 方法,我们的攻击程序中存在竞态条件。因此,当我们试图利用目标程序中的竞态条件时,目标程序可能会意外地“利用”我们攻击程序中的竞争条件,从而击败我们的攻击。
为了解决这个问题,我们需要使 unlink() 和 symlink() 原子化。 幸运的是,有一个系统调用可以让我们实现这一点。 更准确地说,它允许我们原子地交换两个符号链接。 下面的程序首先创建两个符号链接 /tmp/XYZ 和 /tmp/ABC,然后使用 renameat2 的系统调用来原子地切换它们。 这允许我们在不引入任何竞争条件的情况下更改 /tmp/XYZ 指向的内容。
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
int main()
{
unsigned int flags = RENAME_EXCHANGE;
unlink("/tmp/XYZ"); symlink("/dev/null", "/tmp/XYZ");
unlink("/tmp/ABC"); symlink("/etc/passwd", "/tmp/ABC");
renameat2(0, "/tmp/XYZ", 0, "/tmp/ABC", flags);
return 0;
}
请使用此新策略修改你的攻击程序,然后再次尝试你的攻击。 如果一切都正确完成,你的攻击应该能够成功。