Blog ·
Por qué un escáner de vulnerabilidades debe enviar cero bytes
La frontera entre una comprobación y un ataque son los bytes que salen del socket. Cómo convertir esa promesa en un número que una prueba pueda suspender.
Por Provibr
Quien gestiona infraestructura para otras personas acaba queriendo saber si el puerto 3306 de la máquina de alguno de sus clientes está respondiendo a todo internet. Es una pregunta razonable, el cliente querría la respuesta, y en el momento de ir a mirar uno se ha convertido en una parte que envía tráfico no solicitado a una máquina que está bajo el contrato de otra persona.
Lo que separa eso de una intrusión no es la intención, y no es una casilla en las condiciones del servicio. Es lo que salió realmente del socket. Todo lo demás es un relato sobre lo que salió del socket.
Hace poco construimos una comprobación de exposición dentro de nuestro agente, con una regla que empezó siendo una frase en un documento de diseño: no enviar ninguna consulta que modifique, ningún intento de inicio de sesión, ninguna credencial por defecto, ninguna carga de explotación, ninguna recolección amplia de banners. Así es como esa frase se convirtió en algo que una prueba puede suspender, y esto es lo que cuestan en bytes las sondas resultantes.
Una promesa en un comentario es una promesa con fecha de caducidad
El problema de la frase anterior no es que sea difícil de cumplir. Es que es fácil de cumplir durante unos seis meses.
Alguien añade un EHLO a la sonda de SMTP para poder detectar si se ofrece STARTTLS, que es información genuinamente útil. Las pruebas existentes siguen pasando, porque afirman sobre lo que la sonda encontró y no sobre lo que dijo. Desde ese día el software envía comandos de protocolo a las máquinas de los clientes, y el documento de diseño sigue diciendo que no lo hace. Nada se puso en rojo. Nada lo hará nunca.
Así que lo convertimos en un número donde un número es posible. Cada socket que abre la comprobación va envuelto en un flujo que cuenta los bytes escritos, y el total viaja de vuelta con el resultado. Cuatro de los seis tipos de sonda llevan una cifra exacta afirmada en las pruebas: cero para las dos que no envían nada, treinta y dos para SSH, ocho para PostgreSQL. Las otras dos no pueden, porque un ClientHello y un negotiate de SMB varían de longitud según lo que se ofrezca dentro de ellos; su límite es en cambio una forma: un solo mensaje y nada después. Añadir un EHLO a una sonda afirmada en cero es ahora una prueba en rojo con un número dentro.
Dos detalles de ese contador resultaron importar.
Cuenta escrituras y no lecturas. Lo que la otra parte ofrece por su cuenta no es algo que hayamos causado; lo que enviamos, sí. Y es un contador compartido en lugar de un campo en nuestro propio tipo de flujo, porque la sonda de TLS entrega su socket a una biblioteca que luego escribe sobre él sus propios registros de handshake. Sin un contador compartido, la única sonda que usa código ajeno sería la única sonda sin contar, que es justo donde más cuesta razonar sobre un presupuesto de bytes.
Lo que cuesta cada sonda
| Sonda | Qué enviamos | Qué se lee |
|---|---|---|
| Solo abrir | nada, afirmado en 0 bytes | nada |
| Banner: FTP, Telnet, SMTP, MySQL | nada, afirmado en 0 bytes | un saludo acotado |
| SSH | una línea de identificación, afirmada en 32 bytes | la línea de identificación del servidor y, si la ofrece, su lista de algoritmos |
| TLS | un ClientHello, de longitud variable | el hello del servidor y el certificado |
| SMB | un negotiate, de longitud variable | la respuesta al negotiate |
| PostgreSQL | la petición de SSL, afirmada en 8 bytes | un byte |
Nada sigue a la apertura. Ni un segundo mensaje, ni un keepalive, ni la terminación de un handshake. En cuanto la otra parte ha hablado, el socket se cierra.
SSH: treinta y dos bytes, y por qué no puede ir más lejos
El protocolo SSH hace que ambos extremos envíen primero una línea de identificación. La nuestra es una línea de treinta y dos bytes, y es lo único que la sonda escribe. Después lee: la línea de identificación del servidor y, luego, si el servidor lo envía sin que se le pida, su mensaje de intercambio de claves con los algoritmos ofrecidos dentro.
La parte importante es lo que falta. La sonda no envía nunca su propio mensaje de intercambio de claves. Eso no es una política, es un límite estructural: sin él el intercambio de claves no puede avanzar, no se establece nunca ninguna sesión, y por tanto no existe ningún estado del protocolo en el que se pudiera enviar una petición de autenticación. «Ningún intento de inicio de sesión» deja de ser una regla que alguien tiene que recordar.
La línea de identificación nombra el producto en lugar de imitar a un cliente. Un administrador que lea su registro de autenticación debería poder ver quién se conectó. Una cadena neutra o falsificada haría que una comprobación legítima pareciera un reconocimiento previo, que es un mal cambio por los dos segundos de oscuridad que compra.
Hay un coste, y merece la pena nombrarlo porque aparece como un problema de calidad de los datos y no como un error. Algunos servidores esperan al mensaje de intercambio de claves del cliente antes de enviar el suyo, y el protocolo lo permite en cualquiera de los dos órdenes. Cuando eso ocurre, la sonda obtiene una cadena de versión y ninguna lista de algoritmos. La cobertura de ese puerto es entonces parcial, y hay que registrarla como parcial. Si se registrara como completa con una lista vacía, el motor de reglas concluiría que no se ofrece ningún algoritmo obsoleto, que es el peor resultado posible: un «seguro» dicho con confianza y derivado de una ausencia de datos.
Los protocolos que hablan primero, y la tentación de contestar
FTP, Telnet, SMTP y MySQL saludan todos. La sonda para esos no envía absolutamente nada: conecta, lee un número acotado de bytes y cierra. Eso no es una recolección de banners, porque no hay forma de abrir un puerto así sin recibir el saludo, y no se pidió nada.
La tentación del EHLO es real y decidimos que no. Saber si un servidor de correo ofrece STARTTLS es genuinamente valioso. También exige enviar un comando de protocolo, y la regla iba de hechos que la otra parte suelta sin que se le pidan. La consecuencia es que un hallazgo de SMTP aterriza con una gravedad menor y una confianza menor de las que tendría en otro caso. Ese es el precio honesto y debería verse en el hallazgo en lugar de discutirse hasta hacerlo desaparecer.
PostgreSQL es el caso intermedio interesante. Ocho bytes son el paquete entero de petición de SSL: una longitud y un número mágico, que es lo primero que envía cualquier cliente y que no es una consulta, ni una credencial, ni un inicio de sesión. El servidor responde con un único byte que dice si va a hablar TLS. Un byte de respuesta basta para distinguir una base de datos que exige cifrado de una que no, y no existe ninguna versión de esta comprobación que envíe menos.
Un puerto abierto no es una vulnerabilidad
Que el puerto 445 responda no es que SMB1 esté habilitado. Son dos hechos separados y necesitan dos campos separados.
Nuestra sonda registra un estado para el puerto, que es si hay alguien ahí, y como mucho una pieza de evidencia, que es lo que dijo. Un negotiate que vuelve sin un dialecto antiguo deja un puerto abierto y nada más, y eso es un resultado completo y no uno parcial. La tentación de juntar los dos en una sola gravedad es fuerte porque produce un panel mucho más emocionante.
La higiene de la evidencia va en el mismo párrafo, porque un saludo no es una cadena inofensiva. Los banners de FTP y de SMTP llevan en la práctica el nombre de host del cliente, a veces el nombre de host de su cliente, y de vez en cuando una ruta en disco. Guardarlos en crudo significa que la tabla de hallazgos se convierte en un sitio donde se acumula información personal y estructural sobre las redes de otras personas, conservada durante todo el tiempo que se conserven los hallazgos, visible para cualquiera que pueda leer un hallazgo.
Así que lo que sale de la sonda es un identificador de protocolo normalizado, un fragmento de versión recortado por una regla documentada, el número de bytes leídos y un hash de los bytes en crudo. El hash hace comparables dos escaneos sin que nadie conserve el texto. Esa última propiedad es la que se echa de menos si se omite: sin ella, «¿ha cambiado esto desde el mes pasado?» exige justo aquello que se decidió no guardar.
El sensor no tiene opiniones
Una separación más, y es la que hace mantenible todo lo demás.
El agente mide. No resuelve ningún nombre de host, no guarda ninguna lista de puertos propia y no elige ningún objetivo: la dirección y los puertos los escribe la plataforma, que comprobó que la dirección está asignada en ese momento a esa organización y a ese servidor antes de enviarlos. Sin objetivos, el agente no mide nada.
El juicio ocurre en la plataforma, a partir de un conjunto de reglas que se puede leer y versionar. Esto importa porque un hallazgo sobrevive al código que lo produjo. Dentro de seis meses alguien preguntará por qué un escaneo de septiembre se calificó como alto, y la única manera de responder es tener las reglas y las mediciones como cosas separadas y recuperables por separado. Si la gravedad estuviera cocida en el sensor, la respuesta sería «porque esa versión del agente lo creía así», que no es una respuesta.
El mismo razonamiento explica por qué el agente conecta hacia fuera y nunca está a la escucha, y por qué las credenciales se quedan en el host en lugar de viajar hasta nosotros. Un componente que solo informa de lo que midió es un componente cuyo radio de daño se puede describir en una frase.
Lo que dejaríamos por escrito
- Convertir la promesa en un número. Si la regla habla de lo que se envía, hay que contar lo que se envía y afirmar sobre el recuento. Un comentario no puede fallar.
- Preferir los límites estructurales. No enviar un mensaje de intercambio de claves significa que la autenticación es inalcanzable, lo cual es más fuerte que una regla que dice que no hay que autenticarse.
- Dejar que una sonda sea parcial. Los datos que faltan tienen que verse como faltantes, porque la alternativa es un negativo dicho con confianza que nadie puede auditar.
- Mantener «abierto» y «vulnerable» en campos distintos, y hacer que el motor de reglas los combine en un sitio que se pueda leer.
- No guardar banners en crudo. Normalizar, aplicar un hash y conservar el hash para comparar.
- Poner la opinión en la plataforma, para que un juicio del trimestre pasado siga siendo explicable este trimestre.
Para que quede claro sobre nuestro propio producto: esto es un detector, está detrás de un interruptor a nivel de organización que ahora mismo está apagado, y no es algo que se pueda comprar hoy. Las mediciones de arriba salen de la implementación y de sus pruebas, no de una página de marketing.
Construido para Provibr, donde el agente se ejecuta dentro de la red del propio cliente. El lado de la verificación del mismo diseño, demostrar que un nombre de host pertenece a quien lo pide, está en cómo demostrar la propiedad de un nombre de host.