Race Condition Vulnerability Lab
Race_Condition_Vulnerability_Lab
1 概述
本实验的学习目标是让学生通过将他们从课堂上学到的关于漏洞的知识付诸行动,获得关于竞争条件漏洞的第一手经验。当多个进程同时访问和操作相同的数据时,就会出现竞争条件,并且执行的结果取决于访问发生的特定顺序。 如果特权程序存在竞争条件漏洞,攻击者可以运行一个并行进程来与特权程序“竞争”,意图改变程序的行为。
在这个实验室中,学生将获得一个具有竞争条件漏洞的程序; 他们的任务是开发一个利用漏洞并获得root权限的方案。 除了攻击之外,还将指导学生了解几种可用于对抗竞争条件攻击的保护方案。
学生需要评估这些计划是否有效并解释原因。 本实验涵盖以下主题:
- 竞争条件漏洞
- 粘滞符号链接保护
- 最小特权原则
实验环境
SEED Ubuntu 20.04
2 环境配置
2.1 关闭对策
Ubuntu 具有针对竞争条件攻击的内置保护。 该方案通过限制谁可以遵循符号链接来工作。 根据文档,“如果跟随者和目录所有者都与符号链接所有者不匹配,则全局可写的粘滞目录(例如 /tmp
)中的符号链接无法被跟随 。”
Ubuntu 20.04 引入了另一种安全机制,可防止 root 写入 /tmp
中属于其他人的文件。 在本实验中,我们需要禁用这些保护。 您可以使用以下命令来实现:
1 |
|
实验操作
2.2 一个易受攻击的程序
下面的程序是一个看似无害的程序。 它包含一个竞争条件漏洞。
1 |
|
- 上面的程序是一个root拥有的Set-UID程序; 它将用户输入的字符串附加到临时文件
/tmp/XYZ
的末尾。 由于代码以 root 权限运行,即其有效用户 ID 为0,因此它可以覆盖任何文件。 - 为防止自己不小心覆盖别人的文件,程序首先检查真实用户ID是否具有
/tmp/XYZ
文件的访问权限;这就是第 ① 行中access()
调用的目的。 如果真实用户 ID 确实具有权限,则程序在第 ② 行打开文件并将用户输入附加到文件中。 - 乍一看该程序似乎没有任何问题。 但是这个程序存在竞争条件漏洞:由于检查(
access
)和使用(fopen
)之间的时间窗口,存在access()
使用的文件与fopen()
使用的文件不同的可能性,即使它们具有相同的文件名/tmp/XYZ
. 如果恶意攻击者可以在时间窗口内以某种方式使/tmp/XYZ
成为指向受保护文件(例如/etc/passwd
)的符号链接,则攻击者可以将用户输入附加到/etc/passwd
,从而获得root权限。 易受攻击的程序以root
权限运行,因此它可以覆盖任何文件。
设置 Set-UID 程序
我们首先编译上面的代码,把它的二进制文件变成一个由root拥有的Set-UID程序。 以下命令可实现此目标:
1 |
|
实验操作
3 任务1:选择我们的目标
我们想利用程序中的竞争条件漏洞。 我们选择以普通用户不可写的密码文件/etc/passwd
为目标。 通过利用该漏洞,我们希望在密码文件中添加一条记录,目的是创建一个具有 root 权限的新用户帐户。
在密码文件中,每个用户都有一个条目,该条目由七个以冒号 (:) 分隔的字段组成。 下面列出了 root 用户的条目。
1 |
|
- 对于 root 用户,第三个字段(用户 ID 字段)的值为零。 即,当root用户登录时,其进程的用户ID设置为零,赋予该进程root权限。 基本上,root 帐户的权力不来自其名称,而是来自用户 ID 字段。 如果我们想创建一个具有 root 权限的帐户,我们只需要在这个字段中输入一个零。
- 每个条目还包含一个密码字段,这是第二个字段。 在上面的示例中,该字段设置为“x”,表示密码存储在另一个名为
/etc/shadow
(影子文件)的文件中。 - 如果我们按照这个例子,我们必须使用竞争条件漏洞来修改密码和影子文件,这不是很难做到。 但是,有一个更简单的解决方案。 我们可以简单地将密码放在那里,而不是将“x”放在密码文件中,这样操作系统就不会从影子文件中查找密码。
- 密码字段不保存实际密码; 它保存密码的单向哈希值。
- 要为给定的密码获取这样的值,我们可以使用
adduser
命令在我们自己的系统中添加一个新用户,然后从影子文件中获取我们密码的单向哈希值。 或者我们可以简单地从种子用户的条目中复制值,因为我们知道它的密码是dees
。 有趣的是,Ubuntu live CD 中有一个用于无密码帐户的魔法值,该魔法值是U6aMy0wojraho
(第 6 个字符为零,不是字母 O)。 如果我们将此值放在用户条目的密码字段中,我们只需要在提示输入密码时按回车键。
小任务
-
为了验证魔法密码是否有效,我们手动(作为超级用户)将以下条目添加到
/etc/passwd
文件的末尾。 请查看是否可以无需输入密码登录测试帐户,且是否拥有root权限。1
test:U6aMy0wojraho:0:0:test:/root:/bin/bash
-
完成此任务后,请从密码文件中删除此条目。 在接下来的任务中,我们需要以普通用户的身份来实现这个目标。 显然,我们不允许直接对密码文件执行此操作,但我们可以利用特权程序中的竞争条件来实现相同漏洞的目标。
警告
过去,一些学生在攻击过程中不小心清空了/etc/passwd
文件(这可能是由操作系统内核内部的一些竞争条件问题引起的)。 如果您丢失了密码文件,您将无法再次登录。 为避免此问题,请复制原始密码文件或拍摄 VM 快照。 这样,您就可以轻松地从事故中恢复过来。
实验操作
4 任务2:发起竞争条件攻击
此任务的目标是利用前面列出的易受攻击的 Set-UID 程序中的竞争条件漏洞。 最终目的是获得root权限。 攻击最关键的一步,使/tmp/XYZ
指向密码文件,必须发生在检查和使用之间的窗口内; 即在易受攻击的程序中的access
和fopen
调用之间。
4.1 任务 2.A:模拟慢速机器
让我们假设机器很慢,在 access()
和fopen()
调用之间有 10 秒的时间窗口。 为了模拟这一点,我们在它们之间添加了一个 sleep(10)
。 该程序将如下所示:
1 |
|
- 有了这个添加,
vulp
程序(重新编译时)将暂停并将控制权交给操作系统 10 秒钟。 你的工作是手动做一些事情使程序在 10 秒后恢复时,程序可以帮助你向系统添加一个 root 帐户。 请演示您将如何实现这一目标。
您将无法修改文件名 /tmp/XYZ
,因为它在程序中是硬编码的,但您可以使用符号链接来更改此名称的含义。 例如,您可以将/tmp/XYZ
设为指向/dev/null
文件的符号链接。 当您写入/tmp/XYZ
时,实际内容将写入 /dev/null
。 请参见以下示例(“f”选项表示如果链接存在,则先删除旧链接):
1 |
|
实验操作
-
更改
vulp.c
,重新编译,设为root所有的setuid
程序。 -
将
/tmp/XYZ
设为指向/dev/null
文件(权限位为rw-rw-rw-
)的符号链接。 -
运行
vulp
。用户输入为我们要写入/etc/passwd
的字符串:test:U6aMy0wojraho:0:0:test:/root:/bin/bash
,回车结束输入 -
在提供输入后的十秒内,执行命令
ln -sf /etc/passwd /tmp/XYZ
,使/tmp/XYZ
指向密码文件。 -
打开
/etc/passwd
,可以看到多了一行我们的输入。 -
同样,切换用户至
test
,无需密码即可获得root权限。
4.2 任务 2.B:真正的攻击
在之前的任务中,我们通过要求易受攻击的程序放慢速度来“欺骗”,这样我们就可以发起攻击。 这绝对不是真正的攻击。
在这个任务中,我们将发起真正的攻击。 在做任务前,确保从 vulp
程序中删除了 sleep() 语句。
竞争条件攻击的典型策略是与目标程序并行运行攻击程序,希望能够在那个时间窗口内完成关键步骤。 不幸的是,完美的时机很难实现,所以攻击的成功只是概率。 如果窗口很小,攻击成功的概率可能很低,但我们可以多次运行攻击。 我们只需要袭击到一次竞争条件窗口。
编写攻击程序
在模拟攻击中,我们使用ln-s
命令创建/更改符号链接。现在我们需要在一个程序中完成它。我们可以在C中使用symlink()
来创建符号链接。由于Linux不允许在链接已经存在的情况下创建链接,因此我们需要先删除旧链接。下面的C代码片段显示了如何删除链接,然后使/tmp/XYZ
指向/etc/passwd
。请编写您的攻击程序。
1 |
|
实验操作
攻击程序attack_process.c
如下:
1 |
|
运行易受攻击的程序并监视结果
由于我们需要多次运行易受攻击的程序,因此我们将编写一个程序来自动化此过程。为了避免手动向易受攻击的程序vulp
键入输入,我们可以使用输入重定向。也就是说,我们将输入保存在一个文件中,并要求vulp
使用vulp<inputFile
从该文件获取输入。我们也可以使用管道(稍后将给出一个示例)。
我们的攻击可能需要一段时间才能成功修改密码文件,因此我们需要一种方法来自动检测攻击是否成功。有很多方法可以做到这一点;一种简单的方法是监视文件的时间戳。下面的shell脚本运行ls-l
命令,该命令输出有关文件的多条信息,包括上次修改的时间。通过将命令的输出与之前生成的输出进行比较,我们可以判断文件是否已被修改。
下面的shell脚本使用echo命令(通过管道)提供的输入,在循环中运行易受攻击的程序(vulp
)。您需要决定实际输入的内容。如果攻击成功,即修改passwd
,shell脚本将停止。你需要有点耐心。通常,你应该能够在5分钟内成功。
1 |
|
实验操作
-
编译并运行
attack_process.c
-
更改
target_process.sh
中的输入部分: -
执行脚本
target_process.sh
,开始攻击:### 验证成功
当脚本终止时,通过以测试用户身份登录并验证root权限来测试利用漏洞的成功性。然后在启动程序的终端窗口中按Ctrl-C键终止攻击程序。
实验操作
提示
如果10分钟后,您的攻击仍未成功,则可以停止攻击,并检查/tmp/XYZ
文件的所有权。如果此文件的所有者成为root用户,请手动删除此文件,然后重试攻击,直到攻击成功。请在实验室报告中记录这一观察结果。在任务2.C中,我们将解释原因并提供一种改进的攻击方法。
4.3任务2.C:一种改进的攻击方法
在任务 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()
)之前立即切换上下文。删除现有符号链接并创建新符号链接的操作不是原子性的(它涉及两个单独的系统调用)。因此,如果上下文切换发生在中间(即在删除 /tmp/XYZ
之后),并且目标 Set-UID 程序有机会运行其 fopen(fn, "a+")
语句,它将创建一个以 root 为所有者的新文件。 之后,您的攻击程序将无法再更改 /tmp/XYZ
。
基本上,使用 unlink()
和 symlink()
方法,我们的攻击程序中存在竞争条件。因此,当我们试图利用目标程序中的竞争条件时,目标程序可能会意外地“利用”我们攻击程序中的竞争条件,从而击败我们的攻击。
为了解决这个问题,我们需要使 unlink()
和 symlink()
原子化。 幸运的是,有一个系统调用可以让我们实现这一点。 更准确地说,它允许我们原子地交换两个符号链接。 下面的程序首先创建两个符号链接 /tmp/XYZ
和 /tmp/ABC
,然后使用 renameat2
系统调用来原子地切换它们。 这允许我们在不引入任何竞争条件的情况下更改 /tmp/XYZ
指向的内容。
1 |
|
任务:请使用此新策略修改您的攻击程序,然后再次尝试您的攻击。 如果一切都正确完成,您的攻击应该能够成功。
实验操作
-
更改
attack_process.c
如下,编译并运行attack_process.c
-
执行脚本
target_process.sh
,开始攻击: -
攻击成功:
5 任务 3:关于对策
5.1 任务 3.A:应用最小权限原则
本实验室易受攻击程序的根本问题是违反最小权限原则。 程序员确实明白运行程序的用户可能太强大了,所以他/她引入了 access() 来限制用户的权力。 然而,这不是正确的方法。 更好的方法是应用最小权限原则; 即,如果用户不需要某些权限,则需要禁用该权限。 我们可以使用 seteuid
系统调用暂时禁用 root 权限,然后在必要时启用它。
请使用此方法修复程序中的漏洞,然后重复您的攻击。 你能成功吗? 请报告您的观察并提供解释。暂时关闭root权限
实验操作
-
更改
vulp.c
如下,重新编译,设置为root所有的setuid
程序: -
按照任务2.C的操作进行攻击:
- 无法成功
-
原因:调用open()时没有root权限打开
/tmp/X
指向的受保护的文件passwd
。
5.2 任务 3.B:使用 Ubuntu 的内置方案
Ubuntu 10.10 和更高版本带有一个内置的保护方案,可以防止竞争条件攻击。 在此任务中,您需要使用以下命令重新打开保护:
1 |
|
在保护开启后进行攻击。 请描述您的观察。 请同时解释以下内容: (1) 该保护方案是如何运作的? (2) 该方案的局限性是什么?
实验操作
-
打开保护:
-
使用任务2的
vulp.c
,重新攻击:- 攻击失败
-
原因:当设置粘滞位比特后,只有文件所有者、目录所有者或root用户才能重命名或删除粘滞目录中的文件。
/tmp
目录设置了粘滞位比特。当粘滞符号保护开启后,全局可写的粘滞目录(如tmp
)中的符号链接的所有者,与跟随者和目录所有者的其中之一相匹配时才能被跟随。本次竞态条件攻击中,漏洞程序以root权限运行,即跟随者为root,
/tmp
目录的所有者也是root,但是符号链接所有者时攻击者本身(seed)。所以系统不允许程序使用该符号链接。局限性:仅适用于
/tmp
这样的粘滞目录。
本博客所有文章除特别声明外,均为博客作者本人编写整理,转载请联系作者!