Django Unchained | Building a chain
Вступление
Совсем недавно завершился чемпионат России по программированию систем информационной безопасности, ребята показали крутые результаты и классно выступили.
Мы же в сегодняшнем материале разберем одно из заданий, которое мне довелось подготовить. Относится оно к категории Pentest и за время соревнований поддалось лишь трем командам.
Решение
В описании к задаче дан IP и порт, сканировать тут нечего, поэтому сразу двигаем на веб
Проходим несложную регистрацию и получаем доступ к функционалу приложения – генератор паролей и случайных числ.
Негусто. Однако помимо доступов к приложению нам была присвоена сессионная кука
И кука эта оказалась защищена слабым секретом, который успешно сбрутился при помощи модуля flask_unsign. Секретом оказался manchester.
Секрет – текстовая строка, известная серверу, при помощи которой происходит подпись cookie. Именно с использованием него сервер расшифровывает и проверяет валидность куки. Утечка или слабый секрет могут привести к получению несанкционированого доступа к функционалу приложения, что и произошло в этом задании.
Кука содержит в себе юзернейм, юзер-айди и булеан параметр id_vip_user, коим мы не являемся
Однако, имея секрет, мы можем создать и подписать куку, положив туда свои данные. Делается это при помощи все того же flask_unsign:
python3 -m flask_unsign --sign --secret manchester -c <data>
Подписываем, вставляем в браузер и получаем доступ к новому функционалу приложения – to-do листу и Kittys's gallery
To-do в себе чего-то интересного не содержит
А Kitty's gallery содержит в себе фото котят. Не соврали в названии, выходит
Но интересны здесь не столько сами котята, сколько обращение к апи при подгрузке станицы:
Еще и v2. А что если понизить версию до v1? В таком случае api требует некий параметр file:
И здесь мы получаем Arbitrary File Read:
Но что делать дальше? SSH на машине не запущен, никаких паролей учетных записей мы не находили, как добраться до заветного RCE?
На хабре есть крутой материал о том, как можно раскрутить AFR до RCE используя алгоритм генерации debug pin, к этому способу мы и прибегнем. Полностью дублировать статью я не буду, ознакомиться с ней детально можно по ссылке выше, но вкратце вот, что нам нужно собрать:
- username –> /etc/passwd
- modname –> flask.app by default
- flask path –> можно найти, вызвав ошибку в приложении. Например, попытаться прочитать /etc/shadow
- MAC –> /sys/class/net/eth0/address
- machine id –> /etc/machineid + /proc/self/cgroup
Собрав все необходимые данные, клонируем себе Werkzeuger и запускаем генерацию пина на основе имеющихся данных:
В результате работы скрипта получаем несколько пинов:
Прежде чем перейти дальше, нужно разобраться с ошибкой:
Многие писали с вопросом "а работает ли приложение? там 400 ответ на /console". Приложение работает, а 400 ошибка связана с небольшой особенностью debug режима во фласке. Чтобы победить этот этап просто подменяем заголовок Host с IP на localhost:
И здесь, наконец, получаем то заветное RCE:
Пробрасываем себе шелл любым удобным рабочим способом и оказываемся в системе:
Мы находимся под пользователем app, осталось всего-то повыситься до root =)
После перечисления ресурсов системы, с поиском вектора повышения могло повезти, а могло не повезти. Гарантированный способ увидеть что-то неладное – мониторинг процессов при помощи pspy:
Раз в три минуты запускается некий скрипт /root/healthcheck.sh, похожую директорию можно было найти в /opt. А подождав еще немного, можно было увидеть, как от имени рута выполняется rm-rf по директории /opt/healthcheck/*, после чего туда записывается и выполняется какой-то скрипт, после чего снова rm -rf /opt/healthcheck/*.
Поймать тайминг и прочитать скрипт – наша первичная цель:
Скрипт безобиден и просто собирает информацию о состоянии системы, однако можно увидеть, что он импортирует 4 сторонних модуля и выполняется от имени рута. А права-то на директорию у нас rwx.
Python берет модули из определенного набора директорий, начиная с текущей, поэтому права 777 на неё – недопустимая конфигурация. Таким образом, нашла задача поймать первый rm -rf, дождаться записи скрипта и подложить к нему свой, вредоносный – к примеру, psutils.py.
Дожидаемся очередную итерацию крона и пишем скрипт:
echo "import os; os.system('cp /bin/bash /tmp/bash && chmod +s /tmp/bash')" > /opt/healthcheck/psutil.py
Скрипт healthcheck.py отработает с ошибкой, но подарит нам бинарник bash, принадлежащий руту и повесит на него SUID.
SUID-бит – конфигурация прав в *nix, позволяющая запускать файл, на котором он установлен, с правами владельца этого файла. Если владелец root – с правами root, пользователь app – с правами app.
Дожидаемся, проверяем, запускаем.
Заключение
На этом сегодняшнее чтиво с большим количеством картинок подходит к концу. Shoutout to @anodev за его First Blood на этой машинке, а так же к ребятам из ИТМО, которые сделали это ровно на две минуты позже. Ваша гонка на этой машине была незабываемой =)