title: Exploit a backup program using C
body: <p>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. </p>
  
  <p>Could anyone help me to point out where the 4 vulnerabilities are?
  I think the buffer overflow can happened in <code>copyFile()</code>.</p>
  
  <p>The following is the code for backup.c: (which can be invoked in "backup backup foo" or "backup restore foo")</p>
  
  <pre><code>#include &lt;stdio.h&gt;
  #include &lt;stdlib.h&gt;
  #include &lt;sys/types.h&gt;
  #include &lt;unistd.h&gt;
  #include &lt;sys/wait.h&gt;
  #include &lt;string.h&gt;
  #include &lt;sys/types.h&gt;
  #include &lt;sys/stat.h&gt;
  
  #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 &lt; 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 &lt; 0) {
      fprintf(stderr, "Fork failed\n");
      return -1;
    }
  
    // parent
    if (pid &gt; 0) {
      waitpid(pid, &amp;status, 0);
      if (WIFEXITED(status) == 0 || WEXITSTATUS(status) &lt; 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 &amp;&amp; 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, &amp;buf) == 0 &amp;&amp; buf.st_uid != getuid()) {
        fprintf(stderr, "Not your file: %s\n", dst);
        return 1;
      }
    }
  
    // perform actual backup/restore operation
    if (copyFile(src, dst) &lt; 0)
      return 1;
  
    // grant user access to restored file
    if (cmd == CMD_RESTORE) {
      if (restorePermissions(path) &lt; 0)
        return 1;
    }
  
    return 0;
  }
  </code></pre>
  
  <p>And something useful:</p>
  
  <pre><code>  // 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) &lt; 0)
      fprintf(stderr, "execve failed.\n");
    exit(0);
  </code></pre>