GoAhead — это популярный встраиваемый веб-сервер компании Embedthis, который используется в продуктах Oracle, IBM, HP и других известных производителей. Я думаю, не нужно говорить, какие перспективы открывает возможность исполнять произвольный код на сотнях тысяч устройств, где они работают. Давай разберемся, как это делается.
Баг обнаружил австралийский исследователь Дэниел Ходсон (Daniel Hodson) из компании Elttam. Причина уязвимости — в некорректной обработке HTTP-запросов при включенной поддержке интерфейса CGI. При инициализации окружения некоторых CGI-скриптов существует возможность использования специальных переменных окружения типа LD_PRELOAD
, с помощью которых можно выполнить код.
Уязвимость получила идентификатор 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
.
Если тебе захочется вместе со мной заглянуть в исходники веб-сервера, то их ты всегда сможешь найти в официальном репозитории Embedthis.
Для начала посмотрим, как устроен бинарник goahead
. Для этого можно воспользоваться утилитой readelf
или file
.
readelf -hl 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
.
Как ты, наверное, знаешь, переменных окружения великое множество, но среди них есть специальные, которые могут изменить дефолтное поведение ступеней загрузки исполняемого файла. Подробнее о них ты можешь прочитать в мануале к файлу ld.so
.
Давай пробежимся по исходникам и посмотрим, каким образом переменные влияют на работу приложения. Для этого заглянем в библиотеку glibc, исходники которой ты можешь найти в этом репозитории.
Первая остановка — это функция dl_main
, которая выполняется сразу после запуска линкера.
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
. Да, она делает то, что и следует из ее названия, — парсит и обрабатывает переменные окружения.
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] != '