I’m doing an assignment of a security course that asks me to find 4 vulnerabilities of a backup program (setuid) and use each of them to gain root access (on a virtual linux machine with old version of gcc etc.).
There should be one of buffer overflow and one of format string.
Could anyone help me to point out where the 4 vulnerabilities are?
I think the buffer overflow can happened in copyFile().
The following is the code for backup.c: (which can be invoked in “backup backup foo” 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);
The format vulnerability is in
usage()– with thesprintf()andprintf()taking format strings that are generated fromargv[0], which an attacker can manipulate to contain whatever they want.The main buffer overflow is the one highlighted by Péter Török; when scanning code for security vulnerabilities, any unchecked buffer filling with blatant comments like that is a signpost asking for trouble.
The environment variable USER is used – it could be manipulated by the unscrupulous, but it is debatable whether it would really buy you anything. You could set it to say ‘root’, and the attempted ‘chown’ command would user the name it was told to use.
There’s a race of sorts between the
chowncommand and thechmod()system call. It isn’t immediately clear how you’d exploit that separately from the other issues – but it might give you something to leverage.Including
<sys/types.h>twice is redundant but otherwise harmless. With POSIX 2008, it isn’t even needed in most places at all.