December 9

Django Unchained | Building a chain

@fefuctf @collapsz


Вступление

Всем привет!

Совсем недавно завершился чемпионат России по программированию систем информационной безопасности, ребята показали крутые результаты и классно выступили.

Мы же в сегодняшнем материале разберем одно из заданий, которое мне довелось подготовить. Относится оно к категории Pentest и за время соревнований поддалось лишь трем командам.

Помчали.


Решение

В описании к задаче дан IP и порт, сканировать тут нечего, поэтому сразу двигаем на веб

screenshot 1

Написано оно на пайтоне

screenshot 2

Проходим несложную регистрацию и получаем доступ к функционалу приложения – генератор паролей и случайных числ.

screenshot 3
Random number
password

Негусто. Однако помимо доступов к приложению нам была присвоена сессионная кука

Screnshot 6

И кука эта оказалась защищена слабым секретом, который успешно сбрутился при помощи модуля flask_unsign. Секретом оказался manchester.

Секрет – текстовая строка, известная серверу, при помощи которой происходит подпись cookie. Именно с использованием него сервер расшифровывает и проверяет валидность куки. Утечка или слабый секрет могут привести к получению несанкционированого доступа к функционалу приложения, что и произошло в этом задании.

Кука содержит в себе юзернейм, юзер-айди и булеан параметр id_vip_user, коим мы не являемся

Screenshot 7

Однако, имея секрет, мы можем создать и подписать куку, положив туда свои данные. Делается это при помощи все того же flask_unsign:

python3 -m flask_unsign --sign --secret manchester -c <data> 

Подписываем, вставляем в браузер и получаем доступ к новому функционалу приложения – to-do листу и Kittys's gallery

Screenshot 8

To-do в себе чего-то интересного не содержит

Screenshot 8

А Kitty's gallery содержит в себе фото котят. Не соврали в названии, выходит

Screenshot 9

Но интересны здесь не столько сами котята, сколько обращение к апи при подгрузке станицы:

Screenshot 10
Screenshot 11
Screenshot 12

Еще и v2. А что если понизить версию до v1? В таком случае api требует некий параметр file:

Screenshot 13

И здесь мы получаем Arbitrary File Read:

Screenshot 14

Но что делать дальше? 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 и запускаем генерацию пина на основе имеющихся данных:

Screenshot 15

В результате работы скрипта получаем несколько пинов:

Screenshot 16

Прежде чем перейти дальше, нужно разобраться с ошибкой:

Screenshot 17

Многие писали с вопросом "а работает ли приложение? там 400 ответ на /console". Приложение работает, а 400 ошибка связана с небольшой особенностью debug режима во фласке. Чтобы победить этот этап просто подменяем заголовок Host с IP на localhost:

Screenshot 18

И здесь, наконец, получаем то заветное RCE:

Screenshot 19

Пробрасываем себе шелл любым удобным рабочим способом и оказываемся в системе:

Screenshot 20

Мы находимся под пользователем app, осталось всего-то повыситься до root =)

После перечисления ресурсов системы, с поиском вектора повышения могло повезти, а могло не повезти. Гарантированный способ увидеть что-то неладное – мониторинг процессов при помощи pspy:

Screenshot 20

Раз в три минуты запускается некий скрипт /root/healthcheck.sh, похожую директорию можно было найти в /opt. А подождав еще немного, можно было увидеть, как от имени рута выполняется rm-rf по директории /opt/healthcheck/*, после чего туда записывается и выполняется какой-то скрипт, после чего снова rm -rf /opt/healthcheck/*.

Поймать тайминг и прочитать скрипт – наша первичная цель:

Screenshot 21

Скрипт безобиден и просто собирает информацию о состоянии системы, однако можно увидеть, что он импортирует 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.

Дожидаемся, проверяем, запускаем.

Screenshot 22

Заключение

На этом сегодняшнее чтиво с большим количеством картинок подходит к концу. Shoutout to @anodev за его First Blood на этой машинке, а так же к ребятам из ИТМО, которые сделали это ровно на две минуты позже. Ваша гонка на этой машине была незабываемой =)