使用 C 来利用备份程序
我正在做一个安全课程的作业,要求我找到备份程序(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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我不是安全专家,但这里的评论
很能说明问题:-) 所以正如您所猜测的,这里可能存在缓冲区溢出。该缓冲区实际上用于读取输入文件的内容。因此请尝试使用长度超过 3K 的文件。
现在,由于 buffer 是本地的,因此它是在堆栈上分配的。因此,通过溢出,您可以覆盖堆栈的内容,包括调用者堆栈帧内的返回地址和局部变量。据我所知,这是理论,但我无法向您提供更多实际细节。
I am not a security expert, but the comment here
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.格式漏洞存在于
usage()
中 -sprintf()
和printf()
获取从sprintf()
生成的格式字符串code>argv[0],攻击者可以操纵它来包含他们想要的任何内容。主要的缓冲区溢出是由 Péter Török 强调的;当扫描代码中是否存在安全漏洞时,任何未经检查的缓冲区填充类似这样的明目张胆的注释都是自找麻烦的路标。
使用了环境变量 USER - 它可能会被不道德的人操纵,但它是否真的会给你带来任何东西是有争议的。您可以将其设置为“root”,并且尝试的“chown”命令将使用被告知使用的名称。
chown 命令和 chmod() 系统调用之间存在某种竞争。目前还不清楚如何与其他问题分开利用它 - 但它可能会给您一些可以利用的东西。
包含两次
是多余的,但在其他方面是无害的。对于 POSIX 2008,大多数地方甚至根本不需要它。The format vulnerability is in
usage()
- with thesprintf()
andprintf()
taking format strings that are generated fromargv[0]
, which an attacker can manipulate to contain whatever they want.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.
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.
There's a race of sorts between the
chown
command and thechmod()
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.您不能再在 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.
受 Jonathan Leffler 回答中的漏洞 4 的启发,这里是
realpath()
检查和之间的 TOCTOU(从文件检查时间到更新时间间隔中的竞争条件)的利用>fopen()
无论如何,将您的
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 andfopen()
Anyway, setting your
UMASK
to000
should allow similar exploitation for thechmod()
issue.还要考虑字符串比较是否足以锁定禁止的目录。答:不,我能想到的至少有两种方式。
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.