Автор: miserylord
Эксклюзивно для форума: xss.pro
Laravel - довольно популярный PHP-фреймворк для создания веб-приложений. Он сам служит основой для построения других CMS, а под капотом использует компоненты из Symfony. По одной из статистик, в сети около полумиллиона хостов используют Laravel.
В этой статье я рассмотрю тему тестирования на проникновение инструмента, сфокусировавшись только на важных деталях и актуальных техниках.
Паттерном обнаружения служит кука laravel_session.
Файл конфигурации Laravel может быть раскрыт по маршруту /.env. Представляет собой файл с различными переменными:
Помимо прочего, он может содержать разного рода API-ключи, для тестирования которых требуется понимание специфики сервиса, для которого они предназначены. К примеру, для ключа OPENAI_API_KEY документация расположена на OpenAI Platform:
Как можно заметить, для работы требуется передача параметра model. В .env файле могут быть обнаружены оба этих параметра, а если отсутствует model, то, возможно, ключ не валиден, либо стоит перебрать все модели.
Конфигурация базы данных встречается практически в каждом .env файле.
Если DB_CONNECTION=mysql, а DB_HOST не равен 127.0.0.1 или localhost (или не из диапазонов 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), то вполне можно подключиться к базе через терминал.
Утилита mysql, аргументы: -u DB_USERNAME, -p DB_PASSWORD, -h DB_HOST.
Команда SHOW DATABASES; покажет базы данных. Выбрать базу можно с помощью USE database_name;, а показать таблицы - через SHOW TABLES;.
Оставим пока что этот способ и подумаем: а что, если DB_HOST всё же равен локальному хосту?
Попробуем /phpmyadmin - и внезапно:
Админ-панель для управления базой данных MySQL.
Возникает идея проверить cpanel, но данных для входа может не быть. Можно попробовать подбор, это уже зависит от конфигурации.
Рассмотрим получение шелла.
Проверить версию phpMyAdmin можно на главной странице, но публичные эксплойты используют довольно старые версии, так что рассматривать это подробнее не будем.
Далее, получить шелл в phpMyAdmin по сути - то же, что и в MySQL. Во вкладке SQL вводятся SQL-команды.
Теоретически шелл выглядит следующим образом, где после выполнения команды он доступен по адресу site.com/php.php?cmd=id:
Во-первых, что тут происходит? MySQL записывает строку в файл за пределами базы данных - это PHP-файл, и он исполняется сервером. Но что не так? Куда пишется файл? Как именно определяется маршрут для OUTFILE? Файл записывается в корневую директорию PHP - document root.
Её необходимо точно определить, поскольку шанс угадать - примерно 15–20%, возможно 30%, если у вас много вариантов, и 50%, если вы видели все комбинации мира (Laravel раскрывает document root, но об этом позже).
Директория document root может быть раскрыта в файле phpinfo, по адресу /info.php.
А кто мы на phpMyAdmin и можем ли вообще выполнять подобные действия? Часто можем - имеем все права или являемся root, или можем сменить/установить пароль root. В phpadmin есть вкладка которая это показывает.
Главной проблемой является secure_file_priv. Это переменная только для чтения. Она показывает папку, куда MySQL может писать. Команда SHOW VARIABLES LIKE "secure_file_priv" её раскроет - это часто /var/lib/mysql-files/. Если корневая директория отличается - файл не будет прочтён. Если в ответе NULL, значит вообще никуда нельзя писать.
Итого, для заливки шелла должны совпасть условия: права на запись в файлы, пустой secure_file_priv, раскрытие document root.
В иных случаях можно поэкспериментировать с UDF - пользовательскими функциями. Пользователь может самостоятельно дописать функции к MySQL. В репозитории MySQL-UDF-Exploitation расписано, как это сделать: сначала определяется версия и архитектура ОС, а также директория плагинов. Это нужно для написания кода и создания бинарного файла. Пример билда: gist
Однако, как следует из репозитория, файл передаётся из локального сервера в MySQL, а не через SQL-запрос.
Теоретически бинарник можно загрузить по частям, но на практике - я пробовал привести его к base64 и воспользоваться функцией FROM_BASE64, но техника не сработала.
Находим таблицу с аккаунтом администратора - это может быть users, либо admin, либо что-то похожее в зависимости от контекста. Пароли зашифрованы bcrypt, и расшифровывать их мы не будем.
Сохраняем текущий хеш пароля. Генерируем новый хеш - есть сервис LaravelHashGenerator. Количество раундов может быть также указано в .env, по умолчанию вроде как 1 (хотя где-то пишут, что 10 - так что пробуйте разные варианты; 1 - это для LaravelHashGenerator).
Берём хеш пароля, меняем его в таблице users (но не забываем сохранить тот, что был!).
Далее ищем админ-панель - а вот это может быть сложнее, чем нахождение таблицы. Их число конечно, но только бесплатных более 200 на GitHub: Laravel Dashboards. Это может быть admin, dashboard, nova, :8080 или что угодно. Файл /composer.json содержит установленные пакеты - по их названиям можно определить URL.
Берём логин из таблицы и пароль в чистом виде - он верно сверяется с хешем, и мы в админке.
Дальше всё сильно зависит от админ-панели. Можно попробовать получить шелл, залив файл. Лучше понять, с чем мы имеем дело - найти репозиторий панели на GitHub и изучить код.
После - возвращаем хеш пароля на место. Можно также добавить нового пользователя - администратора.
Во время проверки версий в файле composer.json я заметил маршрут database/database.sqlite, и внезапно он отдавал SQLite-файл.
Проверять можно, если в .env указано DB_CONNECTION=sqlite. Впрочем, сервер может отдавать SQLite и не раскрывать .env.
Ответ содержит Accept-Ranges: bytes.
Админ-панель может находиться по маршруту /admirer, но SQLite в Laravel встречается нечасто.
Помимо маршрута /.env, конфигурация может быть раскрыта через index.php. Если включён режим отладки (debug), то в ответе мы получим всё ту же env-конфигурацию, которая, помимо прочего, раскроет ещё больше информации, включая DOCUMENT_ROOT, что будет полезно при работе с phpMyAdmin.
В файле конфигурации время от времени встречается JWT_SECRET.
Казалось бы, дальше всё просто - получаем JWT-токен от сервера, расшифровываем его, меняем поля, подписываем ключом и отправляем серверу.
Но сервер не даёт никаких JWT-токенов. Изучив composer.json, складывается впечатление, что это просто некая технология, генерирующая токен при старте проекта, но далее он не используется. Библиотеки для jwt со словом auth, так что мало вероятно что это подпись для чего-то другого.
Проверяется это визуально - если видно, что это не JWT, значит, это не JWT. Я предполагаю, что должен быть параметр вроде SESSION_DRIVER=token.
В каждом .env раскрывается APP_KEY. Казалось бы, зная ключ, можно подписать куки и с помощью phpggc получить шелл. Но этому мешает один параметр - SESSION_DRIVER должен быть не file, а cookie. Это встречается крайне редко - один раз на несколько сотен файлов.
Далее нужно установить версию Laravel, можно попробовать через composer.json.
Выбираем нужный payload, исходя из версии, в утилите phpggc GitHub.
Команда - ./phpggc -f -b Laravel/RCE22 system id
Флаг -b - для base64, если это используется в APP_KEY.
Затем с помощью скрипта laravel_cookie_killer подписываем полученный payload, указывая его аргументом -v, а ключ - аргументом -k. Эту куку передаём Laravel.
Встретить SESSION_DRIVER=cookie - уже большая редкость, так что этот способ не подходит для большинства хостов.
Я не рассматривал CVE, поиск раскрытия конфигурационных файлов исходя из установленных пакетов и другие техники, например, те же phpinfo, которые тоже могут быть раскрыты в Laravel.
Могут быть и другие техники и способы!
Эксклюзивно для форума: xss.pro
Laravel - довольно популярный PHP-фреймворк для создания веб-приложений. Он сам служит основой для построения других CMS, а под капотом использует компоненты из Symfony. По одной из статистик, в сети около полумиллиона хостов используют Laravel.
В этой статье я рассмотрю тему тестирования на проникновение инструмента, сфокусировавшись только на важных деталях и актуальных техниках.
Обнаружение
Паттерном обнаружения служит кука laravel_session.
Конфигурация Laravel
Файл конфигурации Laravel может быть раскрыт по маршруту /.env. Представляет собой файл с различными переменными:
API ключи
Помимо прочего, он может содержать разного рода API-ключи, для тестирования которых требуется понимание специфики сервиса, для которого они предназначены. К примеру, для ключа OPENAI_API_KEY документация расположена на OpenAI Platform:
Как можно заметить, для работы требуется передача параметра model. В .env файле могут быть обнаружены оба этих параметра, а если отсутствует model, то, возможно, ключ не валиден, либо стоит перебрать все модели.
База данных - MySQL
Конфигурация базы данных встречается практически в каждом .env файле.
Если DB_CONNECTION=mysql, а DB_HOST не равен 127.0.0.1 или localhost (или не из диапазонов 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), то вполне можно подключиться к базе через терминал.
Утилита mysql, аргументы: -u DB_USERNAME, -p DB_PASSWORD, -h DB_HOST.
Команда SHOW DATABASES; покажет базы данных. Выбрать базу можно с помощью USE database_name;, а показать таблицы - через SHOW TABLES;.
Оставим пока что этот способ и подумаем: а что, если DB_HOST всё же равен локальному хосту?
Админ-панель к базе данных
Попробуем /phpmyadmin - и внезапно:
Админ-панель для управления базой данных MySQL.
Возникает идея проверить cpanel, но данных для входа может не быть. Можно попробовать подбор, это уже зависит от конфигурации.
Получение шелла через phpMyAdmin
Рассмотрим получение шелла.
Проверить версию phpMyAdmin можно на главной странице, но публичные эксплойты используют довольно старые версии, так что рассматривать это подробнее не будем.
Далее, получить шелл в phpMyAdmin по сути - то же, что и в MySQL. Во вкладке SQL вводятся SQL-команды.
Теоретически шелл выглядит следующим образом, где после выполнения команды он доступен по адресу site.com/php.php?cmd=id:
Во-первых, что тут происходит? MySQL записывает строку в файл за пределами базы данных - это PHP-файл, и он исполняется сервером. Но что не так? Куда пишется файл? Как именно определяется маршрут для OUTFILE? Файл записывается в корневую директорию PHP - document root.
Её необходимо точно определить, поскольку шанс угадать - примерно 15–20%, возможно 30%, если у вас много вариантов, и 50%, если вы видели все комбинации мира (Laravel раскрывает document root, но об этом позже).
Директория document root может быть раскрыта в файле phpinfo, по адресу /info.php.
А кто мы на phpMyAdmin и можем ли вообще выполнять подобные действия? Часто можем - имеем все права или являемся root, или можем сменить/установить пароль root. В phpadmin есть вкладка которая это показывает.
Главной проблемой является secure_file_priv. Это переменная только для чтения. Она показывает папку, куда MySQL может писать. Команда SHOW VARIABLES LIKE "secure_file_priv" её раскроет - это часто /var/lib/mysql-files/. Если корневая директория отличается - файл не будет прочтён. Если в ответе NULL, значит вообще никуда нельзя писать.
Итого, для заливки шелла должны совпасть условия: права на запись в файлы, пустой secure_file_priv, раскрытие document root.
В иных случаях можно поэкспериментировать с UDF - пользовательскими функциями. Пользователь может самостоятельно дописать функции к MySQL. В репозитории MySQL-UDF-Exploitation расписано, как это сделать: сначала определяется версия и архитектура ОС, а также директория плагинов. Это нужно для написания кода и создания бинарного файла. Пример билда: gist
Однако, как следует из репозитория, файл передаётся из локального сервера в MySQL, а не через SQL-запрос.
Теоретически бинарник можно загрузить по частям, но на практике - я пробовал привести его к base64 и воспользоваться функцией FROM_BASE64, но техника не сработала.
Получение аккаунта администратора
Находим таблицу с аккаунтом администратора - это может быть users, либо admin, либо что-то похожее в зависимости от контекста. Пароли зашифрованы bcrypt, и расшифровывать их мы не будем.
Сохраняем текущий хеш пароля. Генерируем новый хеш - есть сервис LaravelHashGenerator. Количество раундов может быть также указано в .env, по умолчанию вроде как 1 (хотя где-то пишут, что 10 - так что пробуйте разные варианты; 1 - это для LaravelHashGenerator).
Берём хеш пароля, меняем его в таблице users (но не забываем сохранить тот, что был!).
Далее ищем админ-панель - а вот это может быть сложнее, чем нахождение таблицы. Их число конечно, но только бесплатных более 200 на GitHub: Laravel Dashboards. Это может быть admin, dashboard, nova, :8080 или что угодно. Файл /composer.json содержит установленные пакеты - по их названиям можно определить URL.
Берём логин из таблицы и пароль в чистом виде - он верно сверяется с хешем, и мы в админке.
Дальше всё сильно зависит от админ-панели. Можно попробовать получить шелл, залив файл. Лучше понять, с чем мы имеем дело - найти репозиторий панели на GitHub и изучить код.
После - возвращаем хеш пароля на место. Можно также добавить нового пользователя - администратора.
composer.json и SQLite
Во время проверки версий в файле composer.json я заметил маршрут database/database.sqlite, и внезапно он отдавал SQLite-файл.
Проверять можно, если в .env указано DB_CONNECTION=sqlite. Впрочем, сервер может отдавать SQLite и не раскрывать .env.
Ответ содержит Accept-Ranges: bytes.
Админ-панель может находиться по маршруту /admirer, но SQLite в Laravel встречается нечасто.
Другой способ получить конфигурацию
Помимо маршрута /.env, конфигурация может быть раскрыта через index.php. Если включён режим отладки (debug), то в ответе мы получим всё ту же env-конфигурацию, которая, помимо прочего, раскроет ещё больше информации, включая DOCUMENT_ROOT, что будет полезно при работе с phpMyAdmin.
JWT токены
В файле конфигурации время от времени встречается JWT_SECRET.
Казалось бы, дальше всё просто - получаем JWT-токен от сервера, расшифровываем его, меняем поля, подписываем ключом и отправляем серверу.
Но сервер не даёт никаких JWT-токенов. Изучив composer.json, складывается впечатление, что это просто некая технология, генерирующая токен при старте проекта, но далее он не используется. Библиотеки для jwt со словом auth, так что мало вероятно что это подпись для чего-то другого.
Проверяется это визуально - если видно, что это не JWT, значит, это не JWT. Я предполагаю, что должен быть параметр вроде SESSION_DRIVER=token.
APP_KEY
В каждом .env раскрывается APP_KEY. Казалось бы, зная ключ, можно подписать куки и с помощью phpggc получить шелл. Но этому мешает один параметр - SESSION_DRIVER должен быть не file, а cookie. Это встречается крайне редко - один раз на несколько сотен файлов.
Далее нужно установить версию Laravel, можно попробовать через composer.json.
Выбираем нужный payload, исходя из версии, в утилите phpggc GitHub.
Команда - ./phpggc -f -b Laravel/RCE22 system id
Флаг -b - для base64, если это используется в APP_KEY.
Затем с помощью скрипта laravel_cookie_killer подписываем полученный payload, указывая его аргументом -v, а ключ - аргументом -k. Эту куку передаём Laravel.
Встретить SESSION_DRIVER=cookie - уже большая редкость, так что этот способ не подходит для большинства хостов.
Я не рассматривал CVE, поиск раскрытия конфигурационных файлов исходя из установленных пакетов и другие техники, например, те же phpinfo, которые тоже могут быть раскрыты в Laravel.
Могут быть и другие техники и способы!