Ejecuta comandos enviando JSON: Conoce cómo funcionan las vulnerabilidades de deserialización insegura en proyectos Ruby

Publicado el 05 agosto 2024 por Lauratuero @incubaweb

Los atacantes pueden ejecutar comandos arbitrarios en un servidor remoto simplemente enviando JSON si el código en ejecución tiene vulnerabilidades de deserialización insegura. Pero, ¿cómo es posible?

En esta publicación, describiremos cómo funcionan las vulnerabilidades de deserialización insegura y cómo detectar estas fallas en proyectos Ruby. Usaremos la biblioteca de serialización JSON para Ruby llamada Oj para nuestros ejemplos, aunque estas vulnerabilidades no se limitan a esta biblioteca. Al final, proporcionaremos un enlace a un repositorio con exploits de muestra que funcionan para Oj (JSON), Ox (XML), Psych (YAML), y Marshal (formato binario personalizado), y explicaremos cómo CodeQL puede detectar tales vulnerabilidades. Comprender cómo funciona la deserialización insegura puede ayudarte a evitar esta clase de errores sin tener que enfocarte en métodos concretos.

Construcción de una cadena de detección para Oj

Muchos tienen una noción de cómo podría explotarse una vulnerabilidad de deserialización, pero profundizaremos en los detalles. Mostraremos cómo construir un gadget de detección de deserialización insegura para Oj, una biblioteca Ruby de deserialización JSON, que llama a una URL externa. Este gadget se basa en el trabajo de William Bowling con gadgets de deserialización universal adaptados para Oj y Ruby 3.3.

  1. Empieza con una clase

La mayoría de las veces, las vulnerabilidades de deserialización insegura surgen debido a la capacidad de la biblioteca de deserialización para soportar polimorfismo, lo que permite instanciar clases especificadas en los datos serializados. El atacante puede encadenar estas clases para ejecutar código en el sistema. Estas clases útiles se denominan gadgets, y las cadenas que se forman a partir de ellas se denominan cadenas de gadgets. Tradicionalmente, la serialización-deserialización de constructos arbitrarios se consideraba una característica poderosa, pero en 2015 cambió la percepción pública con el descubrimiento de vulnerabilidades de deserialización Java.

Un proyecto en Ruby que use la biblioteca Oj es vulnerable con un constructo como:

data = Oj.load(untrusted_json)

Oj, por defecto, soporta la instanciación de clases especificadas en JSON, aunque se puede deshabilitar esta función. Para instanciar una clase, como MyClass con un campo member, el JSON sería:

{
    "^o": "MyClass",
    "member": "value"
}
  1. Mapas, listas, getters, setters, y más

La vulnerabilidad puede explotar diferentes métodos según el lenguaje. Por ejemplo, en Ruby, el método hash puede ser usado en lugar del método constructor initialize. Ejemplos para diferentes librerías en Ruby incluyen:

  • Marshal: _load
  • Oj: hash
  • Ox: hash
  • Psych: hash, init_with
  • JSON: json_create
  1. Construcción de una carga útil con gadgets

En nuestro proyecto objetivo, usamos clases disponibles del propio proyecto o de Ruby. Al deserializar en Oj, podemos iniciar la cadena de gadgets con el método hash.

Imaginemos una clase simplificada como esta:

class SimpleClass
  def initialize(cmd)
    @cmd = cmd
  end

  def hash
    system(@cmd)
  end
end

Usamos hash para ejecutar el comando open -a calculator. El payload JSON sería:

{
    "^o": "SimpleClass",
    "cmd": "open -a calculator"
}

Al cargar este JSON con Oj.load(json_payload), se ejecutará el comando y abrirá el calculador.

Extensión a una cadena RCE universal

Para convertir una detección en ejecución remota de código, necesitamos manipular miembros de clases existentes como Gem::Source::Git que puede ejecutar comandos. Una solución involucra el uso del binario zip con basada en GTFOBins, permitiendo ejecución de comandos mediante zip y sus argumentos -TmTT.

Finalmente, ejecutamos un comando como:

{
    "^o": "Gem::Resolver::SpecSpecification",
    "spec": {
        "^o": "Gem::Resolver::GitSpecification",
        "source": {
            "^o": "Gem::Source::Git",
            "git": "zip",
            "reference": "-TmTT="$(id>/tmp/anyexec)"",
            "root_dir": "/tmp",
            "repository": "anyrepo",
            "name": "anyname"
        },
        "spec": {
            "^o": "Gem::Resolver::Specification",
            "name": "name",
            "dependencies": []
        }
    }
}

Detección usando CodeQL

Si tienes acceso al código fuente, CodeQL puede detectar vulnerabilidades de deserialización insegura con su consulta de deserialización de datos controlados por el usuario. Esta herramienta mostrará todas las ubicaciones donde datos no confiables fluyen hacia deserializadores inseguros.

Resumen y recomendaciones

Este artículo mostró cómo detectar y explotar vulnerabilidades de deserialización insegura en Ruby. Usar GitHub CodeQL es la forma más eficaz de identificar estas vulnerabilidades en el código fuente. También se proporcionaron gadgets para detectar y explotar vulnerabilidades sin acceso al código fuente, destacando la importancia de usar deserializadores seguros en los proyectos.