Chris Rohlf
Durante el último año, hemos publicado muchos datos que demuestran que los modelos de IA fronterizos son más que capaces de descubrir y explotar vulnerabilidades de seguridad. Sin embargo, mostrar cómo podrían explotarse vulnerabilidades previamente desconocidas es solo el comienzo de lo que los modelos de IA fronterizos pueden hacer por la ciberdefensa. En esta publicación, resumimos parte de nuestra investigación inicial sobre el análisis y la reducción automatizados de la superficie de ataque: el proceso de usar modelos de IA para mapear el código que es alcanzable y luego eliminar o restringir el acceso a todo lo que un despliegue dado no necesita exponer.
El análisis y la reducción automatizados de la superficie de ataque pueden mejorar de manera significativa la seguridad del software desplegado a corto plazo. Proteger el software únicamente encontrando y corrigiendo vulnerabilidades individuales escala mal: corregir incluso una vulnerabilidad crítica no dice nada sobre las vulnerabilidades que permanecen. Por el contrario, reducir la alcanzabilidad del código puede disminuir la gravedad de las vulnerabilidades desconocidas, incluidas las que podrían estar bajo explotación activa. Estas técnicas no eliminan las vulnerabilidades desconocidas del código, pero hacen que las vulnerabilidades sean más difíciles de alcanzar y, por lo tanto, reducen su gravedad. Minimizar la superficie de ataque de las bases de código produce una mejor seguridad sin tener que encontrar o corregir una vulnerabilidad individual.
A continuación, presentamos algunos de los datos que hemos recopilado al aplicar técnicas de reducción de superficie de ataque a grandes bases de código de código abierto usando Claude. Nuestro objetivo al hacerlo es demostrar que Claude puede mejorar de manera significativa y económica la seguridad del software mediante los métodos que describimos. Específicamente, ejecutamos Claude sobre 25 proyectos (en una variedad de lenguajes), y descubrió 493 problemas. Con estos resultados en mente, discutimos cómo los desarrolladores pueden aplicar técnicas similares a su propio código.
Métodos para reducir la superficie de ataque de las bases de código
Con el tiempo, el software tiende a volverse más complicado, con nuevas capas de abstracción, nuevas dependencias y nuevas características que se acumulan lentamente unas sobre otras. Esto a menudo hace que funcione de manera muy diferente a como lo pretendían sus desarrolladores originales. Y es una gran parte de la razón por la que los atacantes suelen tener tanto éxito: estudian exactamente cómo se comporta una pieza de código en la práctica, en lugar de cómo sus desarrolladores pretendían o diseñaron que funcionara.
Las técnicas de análisis y reducción automatizados de la superficie de ataque buscan descubrir superficies de ataque innecesarias y luego mover ese código detrás de una compuerta apropiada. Esto puede implicar mover código de pre-autenticación a post-autenticación, de 0-clic a 1-clic, de activado-por-defecto a inclusión explícita, o desde el primer mensaje de un protocolo a una posición más profunda en la máquina de estados de la sesión. Algunos código está destinado a ser alcanzable antes de estas etapas, pero mucho no lo está y, dada la complejidad que acompaña a los cambios de código a lo largo de miles o millones de líneas de código, la exposición innecesaria de funcionalidades a menudo pasa desapercibida.
Hay numerosas formas de reducir la superficie de ataque del código. En nuestros propios esfuerzos de investigación, nos centramos en tres métodos en particular:
- Configuración predeterminada. Identificamos código que era alcanzable en su configuración predeterminada de envío pero que no estaba destinado a ser usado por ninguna funcionalidad predeterminada.
- Interacción de configuraciones. Encontramos pares de opciones que parecían razonables por sí solas, pero que causaban problemas cuando se configuraban juntas (por ejemplo, al deshabilitar silenciosamente un control de seguridad, o al dejar indefinido el comportamiento de una funcionalidad mientras su código sigue siendo alcanzable).
- Alcanzabilidad temprana. Identificamos código sensible a la seguridad y complejo que se ejecuta antes de un control de seguridad apropiado (como la autenticación), sin interacción de la víctima, o antes de una transición de estado del protocolo.
Queríamos entender qué tan bien podía Claude tomar estas tres descripciones de superficie de ataque y descubrir problemas reales en bases de código grandes y complejas. Analizamos 25 proyectos de código abierto (que representan más de cinco millones de líneas de código), escritos en lenguajes que incluyen C, C++, Java, PHP y Python. Claude creó un inventario de más de 17,000 opciones de configuración en estos proyectos. De ellas, 3,842 se relacionaban con la configuración de un control o característica relevante para la seguridad.
Para los fines de nuestra investigación, dividimos estos proyectos de código abierto en tres categorías amplias:
- Público. Estos proyectos están construidos para servir a cualquier usuario que pueda alcanzarlos, o para consumir datos escritos por partes que no pueden identificar. (Ejemplos incluyen un resolvedor de DNS que analiza respuestas ascendentes, o un proxy que analiza respuestas de origen.)
- Con compuerta. Estos proyectos están destinados a servir a usuarios autenticados (como aquellos con una contraseña, poseedores de una clave o certificado, o dispositivos inscritos). El software todavía permite solicitudes arbitrarias de clientes, pero ninguna funcionalidad está disponible hasta que el usuario haya sido autenticado.
- Privado. Estos están construidos y desplegados para servir al entorno privado del propio operador. En estos casos, agregar un control como una capa de autenticación es opcional, pero queda fuera del alcance del software.
Estas categorías ayudan a contextualizar nuestros resultados. Por ejemplo, esperaríamos que un porcentaje significativo de un código base Público fuera alcanzable en la configuración predeterminada (ya que está diseñado para servir a cualquier usuario), pero que ocurriera lo contrario en los proyectos de la categoría Gated.
Los hallazgos de Claude
Claude encontró 493 problemas utilizando los tres métodos descritos anteriormente. Varios temas nos llamaron la atención de sus resultados.
Primero, Claude encontró que los once proyectos gated tenían código que era alcanzable involuntariamente (aunque este código no era un porcentaje significativo del total de líneas de código en ninguno de esos proyectos). Específicamente, Claude descubrió 93 superficies innecesarias de pre-autenticación en la configuración predeterminada del grupo gated, 37 ubicaciones donde se ejecutaba código o se registraba estado antes de la autenticación, y 61 combinaciones de configuración que debilitaban silenciosamente un control de pre-autenticación configurado por el administrador. Ninguno de estos problemas habría sido explotable de inmediato por sí solo, pero crean una superficie de ataque mucho mayor de lo necesario.
Segundo, el trabajo de Claude mitigando problemas de interacción de configuración produjo algunos de los hallazgos más interesantes e impactantes (en todos los tipos de proyectos). Claude encontró 162 combinaciones de configuración sensibles a la seguridad que eran aceptadas tanto sin un error ni una advertencia como sin ninguna documentación del comportamiento esperado de su interacción. En 94 de esos casos, la combinación desactivaba silenciosamente un control de seguridad. Los otros 68 casos eran inconsistencias en los mismos mecanismos de seguridad (por ejemplo, un listener o una ruta de inicio de sesión seguía siendo alcanzable aunque la configuración indicara que estaba deshabilitado; dos ajustes entraban en conflicto, y el que tenía prioridad dependía de su orden en el archivo de configuración, o una protección configurada por el administrador no hacía nada mientras su código seguía siendo alcanzable). Aunque algunas de estas combinaciones de configuración podrían ser no estándar o usarse con poca frecuencia, nada impediría que un administrador de estos proyectos configurara y desplegara el código de esta manera.
Claude también encontró un patrón específico que apareció de forma independiente en cuatro proyectos no relacionados. Funcionaría de la siguiente manera. Un administrador escribiría una regla de acceso para clientes remotos usando un nombre de host o un patrón de comodín, en lugar de una dirección IP. Cuando un cliente se conecta, el servidor compararía esa regla con el nombre de host del cliente. Si el servidor llegaba a conocer ese nombre de host dependería de una opción separada que controlaba las búsquedas DNS inversas (que la mayoría de los proyectos tenían desactivada). Con las búsquedas desactivadas, el servidor solo tendría la dirección IP del cliente. El servidor compararía la regla con esa dirección como texto plano. Pero un patrón de nombre de host nunca puede coincidir con una dirección IP. Esto significa que una regla de denegación nunca se activaría, y la lista fallaría abierta (fail open). Algunos de los proyectos afectados mencionan en su documentación que las reglas de nombre de host dependen de que las búsquedas DNS estén habilitadas, pero ninguno indica que una lista de denegación escrita de esta manera tendría un comportamiento fail open.
Tercero, Claude a menudo descubría que dos opciones de configuración entraban en conflicto de una manera que eliminaba silenciosamente una verificación de seguridad. En un caso, un administrador podía requerir que cada cliente que se conectara probara su identidad con un certificado. En otro, el código solo tenía la intención de retrasar esa verificación hasta que fuera necesaria, pero de hecho la deshabilitaba por completo. Por lo tanto, un servidor configurado para aceptar solo clientes de confianza aceptaría una conexión de cualquiera. Esto expuso un gran cuerpo de código de manejo de solicitudes (que se suponía que estaba detrás de la verificación del certificado) a clientes no autenticados. El software aceptaba ambas opciones de configuración juntas sin advertencia bajo la configuración predeterminada; la documentación nunca mencionaba que el requisito del certificado había sido eliminado. Este conjunto de opciones de configuración en conflicto había estado presente en el software durante 21 años.
Finalmente, Claude descubrió un problema en un proyecto que dejaba la autenticación desactivada por defecto (y esperaba que el administrador la activara). La opción que requería certificados de cliente TLS se respetaba en la mayoría de los puntos finales de red del proyecto, pero se ignoraba silenciosamente en algunos otros (aquellos que cargaban su configuración TLS a través de una ruta con un espacio de nombres diferente). Esto significaba que un cliente sin certificado podía completar el protocolo de enlace TLS y abrir una sesión en un punto final afectado. No se registraría nada sobre la configuración ignorada. Si la opción surte efecto dependería del espacio de nombres utilizado por el administrador en la configuración. Una forma del nombre de la configuración funciona en todos los puntos finales, pero la forma más corta —la que usan la mayoría de los ejemplos— se descarta en los afectados.
Estudio de caso: OpenSSH
Sorprendentemente, cuando se mide como porcentaje del total de líneas de código, el proyecto en el que Claude logró la mayor reducción de la superficie de ataque fue OpenSSH. OpenSSH es una base de código C de alta calidad que proporciona acceso remoto seguro. Claude trazó las rutas de entrada del demonio SSH en la versión 10.4 y encontró aproximadamente 4,500 líneas de código nuevo y experimental para ML-DSA-44, un algoritmo de firma post-cuántico, que un cliente no autenticado podía alcanzar en la configuración predeterminada. ML-DSA no está habilitado por defecto, pero el código era alcanzable de todos modos, incluso cuando un administrador excluía explícitamente el algoritmo. El demonio analizaba el tipo de clave que el cliente enviara (y, para certificados, también verificaba la firma incrustada de la CA). Solo entonces comprobaba si ese tipo de clave estaba permitido. No hay ninguna buena razón para que este código complejo e inseguro en memoria sea alcanzable cuando no ha sido habilitado en la configuración. Esas 4,500 líneas de código en ML-DSA-44 representan aproximadamente el 16% del propio código de preautenticación de OpenSSH en un sshd predeterminado. Lo informamos a los desarrolladores de OpenSSH junto con parches. La versión 10.5 movió la comprobación antes del análisis.
Claude luego señaló dos fallos en la comprobación de la 10.5. El primero era una discrepancia entre lo que el cliente afirmaba y lo que enviaba. La comprobación examinaba el nombre del algoritmo anunciado por el cliente, pero el analizador era elegido por una cadena de tipo incrustada dentro del blob de la clave. Un cliente podía anunciar ssh-ed25519, pasar la comprobación y aun así enviar una clave ML-DSA que sería analizada.
El segundo fallo estaba en los certificados de la comprobación de la 10.5. Los tipos de clave de certificado estaban habilitados por defecto. Dado un certificado suministrado por un atacante, sshd deserializaría la clave pública incrustada de la CA y ejecutaría una verificación de firma completa con ella, para cualquier algoritmo compatible, antes de consultar la configuración. La ruta de la firma de la clave de usuario estaba correctamente restringida por la lista de claves públicas autorizadas a conectarse al servidor, pero la ruta de la firma de la CA no. Eso hacía que cada verificador de firmas de claves públicas en el binario fuera alcanzable por cualquier cliente que pudiera completar un intercambio de claves. Construimos una prueba de concepto y trabajamos en varias iteraciones de una corrección con los mantenedores. El cambio resultante colocó las comprobaciones dentro del propio analizador. El tipo de clave codificado y el algoritmo de firma de la CA del certificado ahora se comparan con las listas de permitidos configuradas antes de la deserialización o verificación de firma correspondiente.
Esta corrección eliminó aproximadamente una quinta parte del código de OpenSSH que un cliente no autenticado previamente podía alcanzar en una compilación predeterminada. Casi todo ello era el nuevo código ML-DSA, que ahora es inalcanzable incluso para un cliente que mienta sobre su tipo de clave u oculte una clave de CA ML-DSA dentro de un certificado por lo demás aceptable. Los certificados firmados con un algoritmo de CA permitido todavía se analizan y verifican, pero la comprobación ahora se ejecuta antes del análisis. Los mantenedores publicaron los cambios descritos aquí en la versión 10.6.
OpenSSH es uno de los códigos más auditados del mundo. Si la alcanzabilidad innecesaria sobrevive allí, puede sobrevivir en cualquier parte. Esto sugiere que reducir la superficie de ataque de las bases de código probablemente generará dividendos en muchas bases de código importantes.
Conclusión
Es quizás imposible medir cuántas vulnerabilidades desconocidas hay en cualquier base de código determinada. Y hay abundantes datos que muestran que los atacantes pueden y encontrarán esas vulnerabilidades con suficiente tiempo (particularmente si están equipados con modelos de IA de frontera). Pero un defensor que reduce la superficie de ataque innecesaria en su código reduce de una vez el riesgo de todas las vulnerabilidades desconocidas en ese código, incluidas las que ya se están explotando silenciosamente. OpenSSH es parte del código C más auditado del mundo. Sin embargo, publicó alrededor de 4,500 líneas de código experimental de análisis de firmas que un atacante podía alcanzar antes de cualquier comprobación de configuración.
Hemos combinado las técnicas de reducción de superficie de ataque descritas anteriormente en una habilidad de Claude reutilizable, que estamos trabajando para integrar en OSS-Scanner. La habilidad ejecuta tres pasadas sobre una base de código. Cada vez comienza desde cero (con una ventana de contexto vacía), excepto por un breve fragmento de contexto que recibe de la pasada anterior.
En la primera pasada, la habilidad examina si existe código que la configuración predeterminada permite alcanzar pero que no es usado por ninguna funcionalidad prevista. En la segunda pasada, examina el código que se ejecuta antes de cualquier barrera estricta (como la autenticación) o antes de los puntos de entrada de la máquina de estados del protocolo, en cualquier configuración. Finalmente, en tercer lugar, la habilidad evalúa qué combinaciones de configuraciones desactivan silenciosamente un control de seguridad o degradan su eficacia. En cada pasada, Claude crea un inventario de los puntos de entrada a partir del código y confirma cada hallazgo con un rastreo dinámico del programa. Para los hallazgos en la configuración predeterminada, diseña e implementa una barrera y se asegura de que esto no cause ningún cambio en el comportamiento predeterminado del programa. Lo hace ejecutando la propia suite de pruebas del proyecto (junto con una suite de regresión de caja negra) contra la línea base; requiere que las compilaciones modificadas produzcan resultados idénticos. Cualquier barrera que cambiaría una configuración no predeterminada se aparta para que un humano decida. Para las otras dos pasadas, propone cambios como reordenar una comprobación de configuración, añadir una protección para imponer el orden de los estados o rechazar la combinación al cargar la configuración. Incluso sin usar esta habilidad específica, creemos que la metodología general aquí descrita probablemente sea eficaz.
Las técnicas descritas en esta publicación ofrecen solo una visión muy limitada de lo que es posible en la reducción de la superficie de ataque. Por ejemplo, Claude también es capaz de analizar y comparar registros de ejecución con configuraciones para garantizar que solo se haya habilitado la funcionalidad requerida: deshabilitar analizadores sin usar, protocolos heredados y paneles reduce el riesgo de seguridad a un costo mínimo.
Alentamos a las organizaciones a explorar el uso de Claude para analizar y reducir la superficie de ataque del software del que dependen. Esperamos que esta sea una de las formas más prácticas en que los desarrolladores puedan mejorar de manera medible la seguridad del código sobre el cual el mundo ya funciona.