Следующая новость
Предыдущая новость

Эксплуатируй, GoAhead! Выполняем произвольный код в веб-сервере GoAhead

08.02.2018 13:06
Эксплуатируй, GoAhead! Выполняем произвольный код в веб-сервере GoAhead

Содержание статьи

  • Готовим стенд
  • Особенности компиляции и линкования
  • Атакуем CGI
  • Трюки для удаленной эксплуатации
  • Атакуем роутер
  • Демонстрация уязвимости (видео)
  • Заключение

GoAhead — это популярный встраиваемый веб-сервер компании Embedthis, который используется в продуктах Oracle, IBM, HP и других известных производителей. Я думаю, не нужно говорить, какие перспективы открывает возможность исполнять произвольный код на сотнях тысяч устройств, где они работают. Давай разберемся, как это делается.

Баг обнаружил австралийский исследователь Дэниел Ходсон (Daniel Hodson) из компании Elttam. Причина уязвимости — в некорректной обработке HTTP-запросов при включенной поддержке интерфейса CGI. При инициализации окружения некоторых CGI-скриптов существует возможность использования специальных переменных окружения типа LD_PRELOAD, с помощью которых можно выполнить код.

INFO

Уязвимость получила идентификатор CVE-2017-17562.

Готовим стенд

Для начала определимся с версией самого веб-сервера. Уязвимы все версии приложения ниже 3.6.5. Так что будем использовать самую последнюю до патча — 3.6.4. В качестве системы я буду использовать Debian 9 в докер-контейнере.

docker run --rm -ti --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --name=goahead --hostname=goahead -p80:80 debian /bin/bash apt-get update && apt-get install -y build-essential git file 

Теперь ставим веб-сервер. Будем собирать из исходников, взятых в гит-репозитории.

git clone --branch=v3.6.4 --depth=1 https://github.com/embedthis/goahead.git cd goahead DEBUG="debug" make 

Теперь скомпилим тестовое CGI-приложение.

cd test gcc cgitest.c -o cgi-bin/cgitest 

Запустить сервер можно из папки test, там лежат дефолтные конфиги для работы GoAhead.

../build/linux-x64-default/bin/goahead 

Скомпилированное приложение будет доступно на запущенном сервере по ссылке /cgi-bin/cgitest.

Эксплуатируй, GoAhead! Выполняем произвольный код в веб-сервере GoAhead
Тестовое CGI-приложение на сервере GoAhead 6.3.4

Если тебе захочется вместе со мной заглянуть в исходники веб-сервера, то их ты всегда сможешь найти в официальном репозитории Embedthis.

Особенности компиляции и линкования

Для начала посмотрим, как устроен бинарник goahead. Для этого можно воспользоваться утилитой readelf или file.

readelf -hl goahead 
Эксплуатируй, GoAhead! Выполняем произвольный код в веб-сервере GoAhead
Детальная информация о бинарнике goahead

Обрати внимание на секцию INTERP. Она говорит нам о том, что ELF-файл был скомпилирован с динамической линковкой библиотек, а в качестве интерпретатора для ее выполнения используется библиотека ld-linux-x86-64.so.2.

root@goahead:~/goahead/build/linux-x64-default/bin# file goahead goahead: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=c29d5dc68c84bafc997dd15d4e22c3dab634afae, not stripped 

Эта библиотека — динамический компоновщик, который выполняется в первую очередь при запуске исполняемого файла и отвечает за линковку и загрузку разделяемых библиотек (so) и символов. Чтобы узнать, какие библиотеки подгружаются при старте веб-сервера, можно воспользоваться утилитой ldd или запустить goahead, установив переменную окружения LD_TRACE_LOADED_OBJECTS в 1.

Эксплуатируй, GoAhead! Выполняем произвольный код в веб-сервере GoAhead
Информация о подгружаемых библиотеках при запуске веб-сервера GoAhead

Как ты, наверное, знаешь, переменных окружения великое множество, но среди них есть специальные, которые могут изменить дефолтное поведение ступеней загрузки исполняемого файла. Подробнее о них ты можешь прочитать в мануале к файлу ld.so.

Давай пробежимся по исходникам и посмотрим, каким образом переменные влияют на работу приложения. Для этого заглянем в библиотеку glibc, исходники которой ты можешь найти в этом репозитории.

Первая остановка — это функция dl_main, которая выполняется сразу после запуска линкера.

/glibc/master/elf/rtld.c
733: static void 734: dl_main (const ElfW(Phdr) *phdr, 735:     ElfW(Word) phnum, 736:     ElfW(Addr) *user_entry, 737:     ElfW(auxv_t) *auxv) 738: { ... 772:   /* Process the environment variable which control the behaviour.  */ 773:   process_envvars (&mode); 

Здесь вызывается функция process_envvars. Да, она делает то, что и следует из ее названия, — парсит и обрабатывает переменные окружения.

/glibc/master/elf/rtld.c
2341: static void 2342: process_envvars (enum mode *modep) 2343: { 2344:   char **runp = _environ; 2345:   char *envline; 2346:   enum mode mode = normal; 2347:   char *debug_output = NULL; ... 2353:   while ((envline = _dl_next_ld_env_entry (&runp)) != NULL) 2354:     { 2355:       size_t len = 0; 2356:  2357:       while (envline[len] != '' && envline[len] != '=') 2358:   ++len; 

Перебирается массив, состоящий из переменных окружения. Для каждого элемента вычисляется его размер, а дальше функция switch, в зависимости от длины параметра, передает управление на различные ветки кода, где выясняется, не передана ли одна из специальных переменных. Нас больше всего интересует длина 7, где расположилась проверка на LD_PRELOAD в переданной командной строке.

/glibc/master/elf/rtld.c
2366:       switch (len) 2367:   { ... 2385:   case 7: ... 2393:     /* List of objects to be preloaded.  */ 2394:     if (memcmp (envline, "PRELOAD", 7) == 0) 2395:       { 2396:         preloadlist = &envline[8]; 2397:         break; 2398:       } 

Если эта переменная окружения передана, то инициализируется preloadlist. По сути, в ней хранится список библиотек, которые нужно загрузить перед тем, как передавать управление главному коду из бинарника. Поэтому вскоре после того, как отработала process_envvars, выполнение переходит к той части кода, что начинает обработку preloadlist.

/glibc/master/elf/rtld.c
1478:   assert (*first_preload == NULL); 1479:   struct link_map **preloads = NULL; 1480:   unsigned int npreloads = 0; 1481:  1482:   if (__glibc_unlikely (preloadlist != NULL)) 1483:     { 

Если список непустой, то функция do_preload выполняет загрузку каждой переданной библиотеки.

/glibc/master/elf/rtld.c
1495:       while ((p = (strsep) (&list, " :")) != NULL) 1496:   if (p[0] != '' 1497:       && (__builtin_expect (! __libc_enable_secure, 1) 1498:       || strchr (p, '/') == NULL)) 1499:     npreloads += do_preload (p, main_map, "LD_PRELOAD"); 

Теперь мы знаем, что, если указать в переменной LD_PRELOAD путь до библиотеки, линкер выполнит ее загрузку перед исполнением самого бинарника. Однако нам этого недостаточно, ведь задача не в том, чтобы просто подцепить либу, требуется еще и выполнить нужный нам код. В этом нам помогут секции .init и .fini. Если функция помещена в секцию .init, то система выполняет ее до главной (main), а если в .fini, то после того, как main отработает и вернет результат. Своеобразная имплементация конструкторов и деструкторов классов, только глобальная.

Теперь воспользуемся атрибутами функций. Нужный нам называется constructor.

Продолжение статьи доступно только подписчикам

Cтатьи из последних выпусков журнала можно покупать отдельно только через два месяца после публикации. Чтобы читать эту статью, необходимо купить подписку.

Подпишись на журнал «Хакер» по выгодной цене!

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта, включая эту статью. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

1 год

5990 р.

Экономия 1400 рублей!

1 месяц

720 р.

25-30 статей в месяц

Уже подписан?

Источник

Последние новости