Marshal.load = ejecución remota de código. ¿Por qué la comunidad de Ruby hizo la vista gorda ante el problema durante años?

Marshal.load = ejecución remota de código. ¿Por qué la comunidad de Ruby hizo la vista gorda ante el problema durante años?

Cada parche generaba nuevas formas de ataque, y la industria seguía ignorando las fallas sistémicas.

image

Desde que en el lenguaje de programación Ruby se introdujo la serialización mediante el módulo Marshal, desarrolladores y especialistas en seguridad se vieron arrastrados a un prolongado juego de «parchear y eludir». La historia de estas vulnerabilidades es un ejemplo claro de cómo las medidas cosméticas contra errores concretos no solucionan el problema de raíz si la arquitectura subyacente permanece igual. La evolución de los exploits en Marshal es una crónica de una década de impotencia ante soluciones temporales frente a una vulnerabilidad sistémica.

La primera advertencia documentada sobre los riesgos de usar Marshal.load apareció en enero de 2013, cuando Charlie Somerville abrió un ticket en el rastreador de errores de Ruby. Desde ese momento comenzó una cadena de investigaciones, publicaciones y CVE que revelaron cada vez más vías para la ejecución remota de código mediante la deserialización de objetos Marshal.

Uno de los puntos de inflexión ocurrió en diciembre de 2024. En la versión Ruby 3.4.0-rc1 había código que no se había tocado en los últimos 16 años, y en él se ocultaba una brecha. Por la misma época, el investigador Luke Yanke publicó un nuevo método de explotación de Marshal. Aunque el fallo se corrigió antes del lanzamiento de la versión final, la situación subrayó que incluso fragmentos de código antiguos y sin cambios pueden convertirse en puertas de ataque.

Un ejemplo clásico de código vulnerable es un controlador en Ruby on Rails que carga un parámetro entrante usando Marshal.load. Ese enfoque se considera equivalente a la ejecución remota de código si los datos de entrada no pasan por una filtración estricta. El envío de un objeto binario correctamente formado podría provocar la ejecución de comandos en el servidor, incluyendo la lectura de archivos o la ejecución de scripts de shell.

Con el aumento del interés en el tema surgieron métodos más sofisticados para buscar y construir gadgets: elementos de las librerías Ruby que se pueden usar dentro de un objeto deserializable para crear cadenas de ejecución. En 2018 Luke Yanke publicó un gadget RCE universal para Ruby 2.x. Tras él aparecieron artículos y reportes de otros investigadores, entre ellos Etienne Stalmans, William Bowling y el proyecto Zero Day Initiative.

En 2021–2022 surgieron exploits actualizados dirigidos ya a Ruby 3.x. Al mismo tiempo salió una nueva versión de la librería YAML Psych, donde la carga segura de objetos se convirtió en el comportamiento por defecto. Parecía que el progreso era evidente. Pero la verdadera automatización de la búsqueda de exploits comenzó más adelante.

Desde 2024 comenzó la «era moderna» de la deserialización en Ruby. Investigadores como Alex Leahu (Include Security) y Peter Stöckli (GitHub Security Lab) aplicaron análisis de programas y consultas CodeQL interprocedimentales para identificar vulnerabilidades. Las investigaciones fueron más allá de Marshal, abarcando XML, JSON y YAML. Surgieron PoC públicas, guías de explotación, herramientas de análisis y recomendaciones de protección. El uso de Marshal en sí mismo pasó a considerarse una práctica inaceptable.

Al momento de la publicación de este artículo, pese a todas las advertencias y medidas, en la popular infraestructura RubyGems.org todavía quedan fragmentos de código con riesgo de deserialización mediante Marshal. Esos casos están clasificados como vulnerabilidades «informativas», pero los ejemplos de anteriores elusiones muestran que incluso una brecha apenas perceptible puede convertirse en un incidente crítico.

Para mejorar la situación se proponen pasos tanto para desarrolladores individuales como para la comunidad Ruby en su conjunto. A corto plazo: auditar el código en busca del uso de Marshal, aplicar reglas de Semgrep y migrar a formatos más seguros (por ejemplo, JSON con reconstrucción manual de objetos o MessagePack). A largo plazo: la eliminación gradual del propio Marshal del lenguaje.

También se propone introducir Marshal.safe_load con restricciones sobre las clases permitidas, añadir advertencias al invocar el Marshal.load habitual y luego cambiar su comportamiento por uno seguro por defecto. Solo tras varias versiones debería abandonarse por completo la carga insegura de objetos.

La historia de Ruby y Marshal demuestra: la comodidad suele ser enemiga de la seguridad. Si una herramienta permite generar un RCE con mínimos esfuerzos, se explotará una y otra vez. Hasta que no se elimine por completo del ecosistema.

Las huellas digitales son tu debilidad, y los hackers lo saben

¡Suscríbete y descubre cómo borrarlas!