防御措施

原子操作

文件操作原子化一般通过上锁来实现。但操作系统如果强制给文件上锁,那么恶意用户可以用它锁上很多文件,导致其他用户无法正常使用。
一般操作系统使用软锁,给愿意遵守规则的进程使用。

可行的原子化

把检查和使用放在一个系统调用中,可以利用内核的上锁机制实现原子化。
比如open()系统调用中提供了一个O_EXCL选项,当和O_CREAT结合使用,当文件已经存在情况下,不会打开指定文件。
并且这期间符号链接的操作也会失败。故如下代码是安全的:
f = open(file, O_CREAT | O_EXCL);

重复检查和使用

竟态条件漏洞依赖攻击者在检查和使用之间的时间窗口内赢得竞争,如果能让竞争变得很难,即使不能消除竟态条件, 程序仍然是安全的。
重复检查和使用就是用这个思路,在程序中多次打开这个文件,并检查打开的是否是同一个文件。

粘滞符号链接保护

就是上一节提到的禁用符号链接

粘滞目录

在linux系统中,文件目录有一个特殊的比特位叫做粘滞比特位。当设置了这个比特位时,只有文件所有者、 目录所有者或者root用户才能重命名或删除这个目录中的文件。/tmp目录设置了粘滞比特位。

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

int main()
{
    char *fn = "/tmp/XYZ";
    FILE *fp;

    fp = fopen(fn, "r");
    if (fp == NULL)
    {
        printf("fopen() call failed \n");
        printf("Reason: %s\n", strerror(errno));
    }else{
        printf("fopen() call succeeded \n");
    }
    fclose(fp);
    return 0;
}

Warning

上面程序中/tmp/XYZ是一个符号链接,不是文件

跟随者是进程的有效用户id
目录所有者是目录的拥有者
符号链接所有者是创立该符号链接的用户
创建符号链接命令:

ln -s /home/vagrant/works/seven/testlink.c /tmp/XYZ 

跟随者、目录所有者、符号链接所有者都是vagrant,打开成功

首先是开启粘滞符号链接保护 三者一致打开成功

跟随者root,目录所有者、符号链接所有者都是vagrant,打开成功

成功情况2

跟随者root,目录所有者vagrant,符号链接所有者root,打开成功

成功情况3

跟随者vagrant,目录所有者vagrant,符号链接所有者root,失败

失败情况2

Warning

结论: 符号链接所有者要么跟跟随者相同,要么跟目录所有者相同,其他情况一律失败

最小权限原则

上述代码的一个根本问题是程序拥有过大的权限,违反了最小权限原则,解决这个问题可以加入seteuid(getuid()) 设置有效id为真实用户id,关闭root权限。

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

int main()
{   
    seteuid(getuid());
    int f = open("/tmp/X", O_RDWR | O_APPEND);
    printf("f is %d\n", f);
    if (f != -1)
    {
        printf("open() call succeeded \n");
    }else{
        printf("open() call failed \n");
        printf("Reason: %s\n", strerror(errno));
    }
    close(f);
    return 0;
}

权限降级实验

Note

以上实验做之前建一个root用户的/tmp/X,把程序变成setuid程序,不加seteuid语句时, 可以正常打开。加了seteuid后,权限降级,无法打开

Warning

不能打开链接文件,书上例子会无法成功,以上例子可以成功。