В этой статье тебя ждут: низкоуровневая эксплуатация веб-сервера со срывом стека и генерацией шелл-кода на лету с помощью древней магии pwntools; атака Padding Oracle на питоновское приложение для вскрытия шифртекста AES-CBC, а также реверс-инжиниринг исполняемого файла с атрибутом SUID для повышения привилегий в системе до локального суперпользователя.
Все это мы проделаем на пути к root-флагу виртуалки Smasher (уровень сложности Insane — 7,6 балла из 10) с CTF-площадки Hack The Box. Поскольку речь в основном пойдет о срыве стека, это будет отличным завершением для нашего цикла «В королевстве PWN».
В этом цикле статей мы изучаем разные аспекты атак типа «переполнение стека». Читай также:
Я продолжаю извращаться с методами обнаружения открытых портов, и в этот раз будем пользоваться связкой из Masscan и Nmap. Masscan, к слову, на сегодняшний день самый быстрый из асинхронных сканеров портов. Ко всему прочему он опирается на собственное видение стека TCP/IP и, по словам разработчика, может просканировать весь интернет за шесть минут с одного хоста.
root@kali:~# masscan --rate=1000 -e tun0 -p1-65535,U:1-65535 10.10.10.89 > ports
Первой командой я инициирую сканирование всего диапазона портов (в том числе UDP) IP-адреса, по которому живет Smasher, и перенаправляю результат в текстовый файл.
root@kali:~# ports=`cat ports | awk -F " " '{print $4}' | awk -F "/" '{print $1}' | sort -n | tr "n" ',' | sed 's/,$//'`
root@kali:~# nmap -n -Pn -sV -sC -oA nmap/smasher -p$ports 10.10.10.89
Далее с помощью стандартных средств текстового процессинга в Linux обрабатываю результаты скана, чтобы найденные порты хранились одной строкой через запятую, сохраняю эту строку в переменной ports
и спускаю с поводка Nmap.
По мнению Nmap, мы имеем дело с Ubuntu 16.04 (Xenial). Оно основано на информации о баннере SSH. Постучаться же можно в порты 22 и 1111. На последнем, кстати, висит некий shenfeng tiny-web-server — вот его мы и отправимся исследовать в первую очередь.
По адресу http://10.10.10.89:1111/
тебя встретит листинг корневой директории веб-сервера.
Интересно, что страница index.html
существует, но редиректа на нее нет — вместо этого открывается список файлов каталога. Запомним это.
Если мы перейдем на /index.html
вручную, то увидим заглушку для формы авторизации, с которой никак нельзя взаимодействовать (можно печатать в полях ввода, но кнопка Login не работает). Забавно, что оба поля для ввода называются input.email
.
Если поискать shenfeng tiny-web-server в Сети, по первой же ссылке в выдаче результатов можно найти репозиторий проекта на GitHub.
Сразу же бросаются в глаза предупреждения, что код небезопасен: первое в самом описании сервера (как единственная его «антифича»), второе — в открытых issues.
Если верить описанию, то tiny-web-server подвержен Path Traversal, а возможность просматривать листинги директорий как будто шепчет тебе на ухо: «Так оно и есть…»
Проверим выполнимость Path Traversal. Так как Firefox любит исправлять синтаксически некорректные конструкции в адресной строке (в частности, резать префиксы вида ../../../
), то я сделаю это с помощью nc
, как показано в issue.
Что и требовалось доказать — у нас есть возможность читать файлы на сервере!
Что дальше? Осмотримся. Если дублировать первичный слеш для доступа к каталогам, сервер подумает, что таким образом мы обращаемся к корневой директории, — и разведку можно будет провести прямо из браузера.
В /home
нам доступна всего одна директория — /www
.
Из интересного здесь — скрипт restart.sh
для перезапуска инстанса процесса сервера, а также сама директория с проектом.
#!/usr/bin/env bash ## Please don’t edit this file let others players have fun cd /home/www/tiny-web-server/ ps aux | grep tiny | awk '{print $2}' | xargs kill -9 nohup ./tiny public_html/ 1111 2>&1 > /dev/null &
Чтобы не мучиться с загрузкой каждого файла по отдельности, я клонирую директорию /home/www
целиком с помощью wget
, исключив каталог .git
, — различия в коде веб-сервера по сравнению с GitHub-версией мы узнаем чуть позже другим способом.
root@kali:~# wget --mirror -X home/www/tiny-web-server/.git http://10.10.10.89:1111//home/www/
Три файла представляют для нас интерес: Makefile
, tiny
и tiny.c
.
В Makefile
содержатся инструкции для сборки исполняемого файла.
CC = c99 CFLAGS = -Wall -O2 ## LIB = -lpthread all: tiny tiny: tiny.c $(CC) $(CFLAGS) -g -fno-stack-protector -z execstack -o tiny tiny.c $(LIB) clean: rm -f *.o tiny *~
Флаги -g -fno-stack-protector -z execstack
намекают нам на предполагаемый «по сюжету» вектор атаки — срыв стека, который, надеюсь, уже успел тебе полюбиться.
Файл tiny
— сам бинарник, который развернут на Smasher.
У нас есть исполняемый стек, сегменты с возможностью записи и исполнения произвольных данных и активный механизм FORTIFY
— последний, правда, ни на что не повлияет в нашей ситуации (подробнее о нем можно прочесть в первой части цикла, где мы разбирали вывод checksec
). Плюс нужно помнить, что на целевом хосте, скорее всего, активен механизм рандомизации адресного пространства ASLR.
Прежде чем перейти непосредственно к сплоитингу, посмотрим, изменил ли как-нибудь автор машины исходный код tiny.c
(сам файл я положу к себе на гитхаб, чтобы не загромождать тело статьи).
Если нужно построчно сравнить текстовые файлы, я предпочитаю расширение DiffTabs для Sublime Text, где — в отличие от дефолтного diff
— есть подсветка синтаксиса. Однако, если ты привык работать исключительно из командной строки, colordiff
станет удобной альтернативой.
Выдернем последнюю версию tiny.c
с гитхаба (будем звать ее tiny-github.c
) и сравним с тем исходником, который мы захватили на Smasher.
root@kali:~# wget -qO tiny1-github.c https://raw.githubusercontent.com/shenfeng/tiny-web-server/master/tiny.c
root@kali:~# colordiff tiny-github.c tiny.c
166c166 < sprintf(buf, "HTTP/1.1 200 OKrn%s%s%s%s%s", --- > sprintf(buf, "HTTP/1.1 200 OKrnServer: shenfeng tiny-web-serverrn%s%s%s%s%s", 233a234,236 > int reuse = 1; > if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0) > perror("setsockopt(SO_REUSEADDR) failed"); 234a238,239 > if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0) > perror("setsockopt(SO_REUSEPORT) failed"); 309c314 < sprintf(buf, "HTTP/1.1 %d %srn", status, msg); --- > sprintf(buf, "HTTP/1.1 %d %srnServer: shenfeng tiny-web-serverrn", status, msg); 320c325 < sprintf(buf, "HTTP/1.1 206 Partialrn"); --- > sprintf(buf, "HTTP/1.1 206 PartialrnServer: shenfeng tiny-web-serverrn"); 346c351,355 < void process(int fd, struct sockaddr_in *clientaddr){ --- > int process(int fd, struct sockaddr_in *clientaddr){ > int pid = fork(); > if(pid==0){ > if(fd < 0) > return 1; 377a387,389 > return 1; > } > return 0; 407a420 > int copy_listen_fd = listenfd; 417,420c430 < < for(int i = 0; i < 10; i++) { < int pid = fork(); < if (pid == 0) { // child --- > signal(SIGCHLD, SIG_IGN); 421a432 > 423c434,437 < process(connfd, &clientaddr); --- > if(connfd > -1) { > int res = process(connfd, &clientaddr); > if(res == 1) > exit(0); 424a439,440 > } > 426,437d441 < } else if (pid > 0) { // parent < printf("child pid is %dn", pid); < } else { < perror("fork"); < } < } < < while(1){ < connfd = accept(listenfd, (SA *)&clientaddr, &clientlen); < process(connfd, &clientaddr); < close(connfd); < } 438a443 >
Незначительные изменения:
233a234
, 234a238
);166c166
, 320c325
).Важные изменения: модифицирована логика обработки запросов клиента (все, что касается функции process
и создания форков). Если в tiny-github.c
реализована многопоточность с помощью концепции PreFork, когда мастер-процесс спавнит дочерние в цикле от 0 до 9, то в tiny.c
родитель форкается только один раз — и уже не в теле main
, а в самой функции process
. Полагаю, это было сделано, чтобы ослабить нагрузку на сервер — ведь ВМ атакует множество людей одновременно. Ну а нам это только на руку, потому что дебажить многопоточные приложения — то еще удовольствие.
На одной из моих вузовских практик преподаватель поставил такую задачу: без доступа в Сеть с точностью до строки найти в исходном коде пакета OpenSSL место, ответственное за нашумевшую уязвимость Heartbleed (CVE-2014-0160). Разумеется, в большинстве случаев нельзя однозначно обвинить во всех бедах одну-единственную строку, но всегда можно (и нужно) выделить для себя место в коде, от которого ты будешь отталкиваться при атаке.
Найдем такую строку в tiny.c
. В формате статьи трудно анализировать исходные коды без нагромождения повторяющейся информации — поэтому я представлю анализ в виде цепочки «прыжков» по функциям (начиная от main
и заканчивая уязвимостью), а ты потом сам проследишь этот путь в своем редакторе.
main() { int res = process(connfd, &clientaddr); } ==> process() { parse_request(fd, &req); } ==> parse_request() { url_decode(filename, req->filename, MAXLINE); }
Функция url_decode
принимает три аргумента: два массива строк (источник — filename
и назначение — req->filename
) и количество копируемых байтов из первого массива во второй. В нашем случае это константа MAXLINE
, равная 1024.
void url_decode(char* src, char* dest, int max) { char *p = src; char code[3] = { 0 }; while(*p && --max) { if(*p == '%') { memcpy(code, ++p, 2); *dest++ = (char)strtoul(code, NULL, 16); p += 2; } else { *dest++ = *p++; } } *dest = '