Artículos en profundidad sobre la tecnología que da forma al futuro.

La IA de seguridad destapa nuestra deuda técnica

Los escáneres de seguridad con IA convierten errores latentes en exploits críticos. Cómo priorizar el backlog de vulnerabilidades sin ahogarse en él.

Una jaula de pájaro en un túnel de mina de carbón, con grietas brillantes que se extienden por las paredes de roca detrás de ella
PHP2Shell fue el canario: los bugs de tipo crash que ignoramos durante años resultan ser grietas estructurales.

En 2024 llegó al gestor de incidencias del proyecto PHP un informe de error. Describía una condición de falta de memoria: el tipo de fallo que tumba un proceso, molesto pero, en la jerarquía informal de los bugs, básicamente un encogerse de hombros. Se quedó ahí parado. Luego, durante una auditoría independiente de PHP patrocinada por OpenAI, un investigador de la firma de seguridad Calif, usando escáneres de seguridad con IA y su propia y profunda experiencia, dedicó unas dos semanas a convertir aquel fallo molesto en ejecución remota de código. Cualquier servidor con una versión vulnerable de PHP podía ser forzado a ejecutar código del atacante o a entregar datos sensibles. Entre los sitios en el radio de impacto estaban la NASA, Wikipedia y la Casa Blanca, porque WordPress y Drupal funcionan sobre PHP, y PHP corre en una fracción sorprendente de la web.

El parche salió antes de que nadie malintencionado llegara, así que la historia terminó bien. Pero los investigadores implicados repiten lo mismo: esto es el principio, no el final. Cuatro décadas de código escrito por personas acumulan un océano de bugs que colectivamente clasificamos como 'crashes, no se arregla', y las herramientas de IA han desplomado el coste de averiguar cuáles de esos crashes son en realidad armas cargadas. He pasado las últimas semanas hablando con personas que llevan la seguridad de sistemas reales sobre lo que esto cambia en la práctica. Lo que sigue no es una encuesta: es un conjunto de errores que ya he visto cometer a equipos, y lo que hicieron las personas que lo hicieron bien.

Error número uno: catalogar los bugs de tipo crash como 'molestos pero inofensivos'

El bug de PHP en el centro de esta historia no estaba oculto. Se reportó, se reconoció y se degradó en prioridad, porque 'solo' causaba un error de falta de memoria. Este es el fallo de triaje más común en organizaciones de software maduras, y el desarrollo de exploits asistido por IA lo acaba de volver caro. El profesor de Columbia Suman Jana, que ha trabajado exactamente en esta clase de investigación, explicó la dinámica con claridad en entrevistas alrededor de la divulgación: en cualquier momento, todos los proyectos grandes tienen una pila de informes de crash que nadie se toma en serio, porque convertir un crash en exploit requiere largas cadenas de razonamiento tedioso y ultraespecífico. Los humanos evitan ese trabajo. Los modelos no se aburren.

Aquí está la versión mecánica de por qué importa la distinción. Un bug de memoria que corrompe el heap con datos no controlados normalmente solo mata el proceso: el asignador detecta algo inconsistente y aborta, o el proceso desreferencia basura y sufre un segfault. Pero ese mismo bug, alimentado con una entrada cuidadosamente dimensionada y moldeada, se convierte en una primitiva de escritura:

void handle_request(const char *input, size_t len) {
char *buf = malloc(64);
memcpy(buf, input, len);   /* len never validated */
/* len = 65:            heap metadata trashed -> likely abort, filed as 'crash' */
/* len = attacker-tuned: adjacent object's function pointer overwritten */
process(buf);             /* calls through the corrupted pointer -> their code runs */
free(buf);
}

La línea entre esos dos resultados no está en el bug, sino en la entrada. Averiguar qué bits sobrescribir, en qué orden, para conseguir ejecución en una dirección elegida es laborioso pero mecánico, y 'laborioso pero mecánico' es justo el centro de aquello en lo que los modelos de lenguaje son buenos ayudando. La solución a este error es conceptual antes que técnica: deja de usar 'solo crashea' como señal de severidad para código inseguro en memoria. Un crash en C o C++ (o en el núcleo en C de un intérprete como PHP) es un exploit que todavía no se ha desarrollado. La nueva pregunta de triaje no es '¿crashea?', sino '¿la corrupción está influida por el atacante?'. Si una entrada no confiable controla el tamaño, el desplazamiento o el contenido de una escritura incorrecta, asume que es explotable y prioriza en consecuencia, aunque nadie haya demostrado un exploit funcional. Sobre todo entonces, porque los exploits demostrados ahora tardan semanas, no años.

Error número dos: tratar la salida del escáner como si fuera un backlog

El segundo modo de fallo aparece en cuanto un equipo ejecuta de verdad escáneres modernos con IA sobre un conjunto de sistemas que incluye código de hace veinte años. La herramienta devuelve cientos de hallazgos, el panel se pone en rojo y el equipo hace lo que los backlogs les enseñan: ordenar por puntuación de severidad y empezar por arriba. Seis meses después han arreglado cuarenta cosas, el recuento ha subido y la moral está por los suelos. Los ingenieros con los que hablé que manejan esto bien coincidían en una versión de lo mismo: la salida de un escáner con IA no es un backlog, es inteligencia en bruto, y necesita una pasada de triaje de un analista antes de que se abra un solo ticket.

El triaje eficaz en este escenario plantea preguntas sobre la ruta de explotación, no sobre la puntuación de severidad:

  • ¿Se puede alcanzar el código vulnerable desde una entrada no confiable? Un desbordamiento de heap en una función que solo procesa un archivo de configuración local es un animal muy distinto al mismo desbordamiento en el manejo de peticiones.
  • ¿Está expuesto a internet hoy? No 'debería estarlo', sino si lo está. Revisa las configuraciones del balanceador de carga y el DNS, no el diagrama de arquitectura.
  • ¿La entrada controla la corrupción? Tamaño, desplazamiento o contenido del acceso incorrecto. Esta es la línea entre crash y exploit de la sección anterior.
  • ¿Qué le aporta un exploit al atacante? Un crash en un worker aislado que se reinicia no es lo mismo que uno en el proceso que tiene las credenciales de la base de datos.
  • ¿Hay un exploit en circulación o un análisis público? Eso adelanta cualquier hallazgo al principio de la cola, independientemente de su puntuación.

Ese último punto merece énfasis. Puntuaciones como CVSS se diseñaron para un mundo en el que los humanos encontraban bugs a ritmo humano, y sistemáticamente sobrevaloran la severidad teórica frente a la alcanzabilidad. Cuando un escáner puede generar severidad teórica a escala industrial, te ahogas. Los equipos que mejor se las arreglan han invertido el orden: primero alcanzabilidad y exposición, después severidad, y cualquier cosa inalcanzable por construcción se documenta y se cierra en lugar de arreglarse, porque un parche que publicas trae su propio riesgo de regresión, y vas a necesitar ese presupuesto de riesgo para los hallazgos que importan. Aquí es también donde las comprobaciones de comprensión de código con IA se ganan su sitio en el lado defensivo: la misma herramienta que encuentra el bug puede responder a menudo '¿muéstrame todas las rutas de llamada desde el límite de red hasta esta función?' en minutos, una pregunta que antes le costaba a un ingeniero senior un día por hallazgo.

Un trabajador clasificando fragmentos brillantes en una cinta transportadora, enviando solo los más luminosos hacia una bóveda
El triaje hace que la mayoría de los hallazgos acaben en el contenedor lateral; solo los pocos alcanzables y expuestos a internet reciben el tratamiento de bóveda.

Error número tres: asumir que los parches pueden avanzar a la velocidad de los hallazgos

Aquí hay una asimetría que debería estar impresa en un póster en cada sala de equipo de seguridad. Encontrar la ruta de explotación del bug de PHP le llevó a un investigador muy capaz, con ayuda de IA, unas dos semanas. Desarrollar el parche le llevó a cuatro ingenieros dos meses. Encontrar es un problema de búsqueda: basta un camino para entrar. Corregir es un problema de tipo demostración: hay que cerrar todos los caminos, y hay que lograr que los mil millones de instalaciones que no están siendo atacadas sigan funcionando exactamente igual. El trabajo del defensor es estrictamente más difícil que el del atacante, y la IA acorta la brecha del lado atacante mucho más rápido que del lado defensor.

Los equipos que ignoran esta asimetría hacen una de dos malas apuestas. O se apresuran a parchear para igualar la presión de la divulgación y envían regresiones (el parche de PHP tocó los internos del intérprete que usa aproximadamente tres cuartas partes de los sitios web que publican su stack, así que el radio de impacto de un mal arreglo es de escala civilizatoria), o se desesperan y no hacen nada mientras esperan un arreglo perfecto. Las personas que gestionaron bien esa ventana hicieron algo menos heroico y más eficaz: separaron 'mitigar ahora' de 'arreglar bien'. Las mitigaciones son baratas, rápidas y reversibles: restringir el endpoint afectado en el proxy inverso, desplegar una regla de WAF que bloquee la forma de entrada del exploit, desactivar la ruta de código vulnerable tras un flag, o reducir quién puede alcanzar el servicio mientras madura el parche definitivo. He escrito antes sobre cómo la decisión entre parchear o mitigar es un auténtico compromiso y no un fallo moral, y este incidente es esa lección a escala planetaria.

El corolario que nadie quiere oír: tu tiempo medio de mitigación es ahora la métrica que importa, porque la ventana entre 'bug encontrado por sombreros blancos' y 'bug encontrado por alguien peor' se está reduciendo. En el caso de PHP, la ventana solo se mantuvo abierta porque los investigadores fueron responsables y la fundación actuó rápido. La biblioteca de compresión Brotli contó una historia menos favorable en el mismo periodo: un bug comparable estuvo en el código durante ocho años, se arregló en git pero no se publicó durante ocho meses, y al menos cuatro investigadores lo redescubrieron de forma independiente. Cuando el descubrimiento es barato y paralelo, 'alguien lo encontrará tarde o temprano' deja de ser un pensamiento tranquilizador y se convierte en una garantía de planificación.

Error número cuatro: confiar en el arreglo porque lo escribió un LLM

El contraargumento optimista a todo esto dice: vale, la IA encuentra bugs más rápido de lo que podemos arreglarlos hoy, pero la IA también los arreglará. Anthropic publicó en octubre herramientas en exactamente esta línea: un LLM genera un parche, luego un segundo modelo genera una prueba verificable por máquina de que el parche preserva el comportamiento previsto, y un verificador de pruebas pequeño, tonto y confiable valida la prueba. El verificador es el único componente que tiene que ser correcto, y puede serlo, porque hace casi nada. Jana se muestra genuinamente entusiasta con esta dirección, y entiendo por qué: es el primer camino creíble para que arreglar sea casi tan automatizable como encontrar.

Pero he visto a dos equipos adoptar flujos de 'parche de seguridad generado por IA' y ambos cometieron el mismo error: trataron el parche del modelo como una solución en lugar de como un candidato. En un caso, el parche cerró el desbordamiento de heap reportado añadiendo una comprobación de longitud, y trasladó el desbordamiento a una segunda ruta de asignación a seis líneas de distancia que el modelo no había modelado. El arreglo pasó la revisión porque parecía obviamente correcto, y ahí está precisamente el modo de fallo: los parches de LLM son fluidos de una forma en que los parches humanos no lo son, y la fluidez suprime el escrutinio. Los equipos que sacaron valor de los parches generados añadieron tres filtros, en este orden:

  1. Una suite de regresión que incluya la entrada del exploit. La prueba de concepto que disparó el hallazgo se convierte en un test permanente. Si el escáner te dio una entrada que provoca el crash, ese es tu primer caso de prueba nuevo, sin coste extra.
  2. Una caza de variantes. Antes de fusionar, alguien (o algo) responde: ¿dónde más aparece este mismo patrón? Arreglar una instancia de un patrón sistémico es la forma de acabar con secuelas de CVE.
  3. Verificación independiente del invariante, no del diff. Declara qué debe ser cierto ahora ('ningún camino desde el manejo de peticiones llega a memcpy con una longitud sin validar') y comprueba eso, con pruebas, análisis estático o herramientas formales. Revisar solo el diff es releer los deberes del modelo.

La verificación formal ayudará aquí a medida que madure, pero fíjate en la división del trabajo que la hace confiable: las partes creativas pueden alucinar, porque el árbitro final es mecánico y pequeño. Ese es el principio de diseño que conviene copiar, con independencia de tus herramientas. Cualquier cosa que produzca un LLM (hallazgo, parche o prueba) debe terminar en algo que no comparta sus modos de fallo. Si tu paso de verificación es otro prompt, no has verificado nada.

Un sello mecánico y una lupa de latón junto a planos enrollados, contrastando creación y verificación
Deja que los modelos generen; deja que algo pequeño, tonto y confiable haga la comprobación final.

Error número cinco: asumir que alguien más auditó el código

Bajo cada lección técnica de aquí hay una lección económica. PHP es infraestructura a la escala de un servicio público: impulsa WordPress, Drupal y una larga cola de la web que incluye agencias espaciales y sitios de gobiernos, y su seguridad descansa en la PHP Foundation, un organismo pequeño y financiado, más voluntarios. El mito cómodo del software libre siempre fue 'con suficientes ojos, todos los bugs son superficiales'. Lo que realmente pasó durante treinta años es que todos asumieron que esos ojos pertenecían a otro. La IA ahora suministra esos ojos, sin discriminar, tanto a atacantes como a defensores, lo que significa que el mito por fin se está haciendo realidad, solo que de forma adversarial y con fecha límite.

La divulgación de PHP2Shell funcionó gracias a un acuerdo poco habitual: OpenAI patrocinó una auditoría externa, una firma bien dotada hizo el trabajo y Wiz ayudó a confirmar y ampliar los hallazgos. Eso es caridad, no un sistema, y no escala a los cientos de proyectos críticos pero sin financiación de tu árbol de dependencias. Si la producción de tu organización depende de un intérprete, framework o biblioteca de código abierto, y así es, las soluciones que importan son aburridas: financia las fundaciones, patrocina auditorías, aporta horas de ingeniería y, cuando ejecutes tus propios escáneres y encuentres algo en una dependencia, repórtalo aguas arriba en lugar de parchear tu fork y seguir adelante. La alternativa es el mundo que predice Matthew Green, de Johns Hopkins: no cambiará nada estructural hasta que un incidente lo bastante grande como para alterar a la gente normal fuerce el cambio. Su consejo mientras tanto es poco glamuroso y correcto: cuando aparezca el aviso de actualización, instálala ahora, no la semana que viene. Aprendimos la misma lección por las malas en el último gran ciclo de divulgaciones de PHP, cuando desconectar fue mejor que los arreglos ingeniosos para los equipos que no podían parchear con suficiente rapidez.

Los ojos llegaron. Simplemente no son exigentes con el bando en el que están.

El manual del lunes por la mañana para vulnerabilidades reveladas por IA

Todo lo anterior se resume en una lista corta de cosas que haría de verdad este trimestre si dirigiera una organización de ingeniería con código heredado en producción, y si estás leyendo esto, tienes código heredado en producción:

  • Parchea con agresividad y mide cuánto tarda. Tu latencia de actualización es ahora un control de seguridad con un umbral seguro cada vez más pequeño. Conoce tu número antes de necesitarlo.
  • Vuelve a hacer el triaje de tu backlog actual de bugs tipo crash con la pregunta de explotabilidad, no con la puntuación de severidad: ¿la entrada no confiable controla la corrupción? Todo lo que sea inseguro en memoria y alcanzable se promociona.
  • Ejecuta tú mismo escaneos asistidos por IA, empezando por tu código más antiguo. El coste marginal de redescubrir algo es ahora casi cero, lo que significa que debes asumir que cualquier cosa encontrable en tu superficie de ataque acabará encontrada. Encuéntrala tú primero.
  • Separa la mitigación de la corrección en tu proceso de incidentes. Prepara el manual de antemano: reglas de proxy, feature flags, restricción de acceso. Comprar tiempo es una estrategia legítima; fingir que no necesitas tiempo, no lo es.
  • Condiciona los parches generados a pruebas con la entrada del exploit y a cazas de variantes, nunca a lo limpio que se vea el diff.
  • Invierte dinero o horas en los proyectos upstream de los que dependes. Las auditorías patrocinadas encontraron este. No es una coincidencia; es el modelo funcionando, y debe dejar de ser excepcional.

Hay un debate razonable sobre si esta era trata de 'pagar la deuda técnica' o si nos apunta a una cinta de correr de trabajo de correcciones sin valor funcional para siempre. Probablemente ambas cosas: cada parche no trivial conlleva el riesgo del siguiente bug sutil, y ningún sistema se termina nunca. Pero prefiero vivir en un mundo en el que la deuda la reclaman auditores y sombreros blancos, que en uno en el que seguimos fingiendo que las arenas movedizas son roca firme porque todavía no nos ha tragado nada. PHP2Shell terminó con un parche y un suspiro de alivio. El siguiente ya está en la cola de crashes de alguien, archivado como inofensivo. Ve a mirar la tuya.