FlawOpen

flawopen.com/Ruby YAML/Ruby YAML

YAML.load é seguro para entradas não confiáveis?

CWE-502: Deserialization of Untrusted DataPágina de referência
Resposta curta

Confira a versão do Psych realmente carregada pela aplicação: Psych 4, incluído no Ruby 3.1, mudou YAML.load para usar desserialização segura por padrão. Versões anteriores do Psych construíam objetos sem essa restrição; o Psych também pode ser atualizado independentemente do Ruby. YAML.unsafe_load e Marshal.load desserializam objetos Ruby e nunca devem receber entrada não confiável.

VULNERABLE
# Psych < 4: YAML.load used unsafe
# object construction by default.
YAML.load(params[:config])

# Every Psych version: explicitly unsafe.
YAML.unsafe_load(user_input)
Psych.unsafe_load(user_input)

# Marshal has no restricted safe-load API.
Marshal.load(cookie_value)
FIXED
# Explicit restricted types; keep this list minimal.
require "yaml"
require "date"

data = YAML.safe_load(
  input,
  permitted_classes: [Date],
  permitted_symbols: [],
  aliases: false
)

# For ordinary external data, prefer JSON and
# validate its shape, values, and input size.
data = JSON.parse(input)

Por que YAML pode ser perigoso

YAML tem tags que o Psych pode mapear para objetos Ruby. Com carregamento inseguro, um documento com tags pode solicitar a criação de uma classe e fornecer valores para seu estado serializado; o impacto depende das classes disponíveis e do comportamento delas. Esse é o princípio de cadeias gadget de desserialização. Use carregamento seguro para YAML não confiável e valide os dados resultantes separadamente. O risco amplo também existe em Python pickle, PHP unserialize e Marshal.load no Ruby.

Aplicações Rails e outras podem carregar muitas dependências, aumentando o conjunto de classes e comportamentos disponíveis para uma cadeia de desserialização insegura. Isso depende da implantação; não é uma propriedade de todo app Rails nem de qualquer documento YAML.

Por que a mudança de versão gerou confusão

Psych 4 mudou YAML.load para usar carregamento seguro por padrão e introduziu unsafe_load para o comportamento anterior, que construía objetos. O Ruby 3.1 incluiu Psych 4, mas aplicações podem selecionar outra versão da gem Psych; audite a dependência resolvida e o runtime, não apenas a versão do Ruby. Como o carregamento seguro pode rejeitar classes específicas da aplicação ou aliases, migre com uma lista de permissões explícita e mínima, em vez de trocar para unsafe_load.

Se uma atualização levar à introdução de unsafe_load, trate a chamada como um achado quando a entrada puder ser influenciada fora da fronteira de implantação confiável. Prefira safe_load com a menor lista necessária de permitted_classes; não a amplie apenas para ignorar erros de compatibilidade.

FAQ

Perguntas frequentes

YAML.safe_load é completamente seguro?

Psych.safe_load restringe as classes desserializadas às permitidas (por padrão, tipos básicos escalares e coleções) e rejeita aliases. Isso não valida formato, tamanho ou regras de negócio da aplicação. Permita apenas as classes realmente necessárias, limite o tamanho e valide os dados retornados. Algumas implementações e fluxos YAML tiveram DoS por expansão de aliases; o CVE-2024-35221 documenta um caso na leitura de metadados do RubyGems. Isso não prova que toda chamada Psych.safe_load seja vulnerável.

E se eu carregar meus próprios arquivos de configuração?

Um arquivo incluído em um artefato confiável e protegido por integridade tem origem diferente de um upload ou arquivo controlado por um tenant. Mesmo para configuração interna, prefira safe_load e permita apenas as classes necessárias. Use unsafe_load somente para dados fixos e comprovadamente confiáveis; nunca para conteúdo influenciado por usuários ou requisições.

Marshal pode ser aceitável?

Outro risco no Ruby: Marshal.load

Separado do YAML, a documentação do Ruby informa que Marshal.load pode reconstruir quase qualquer classe carregada no processo e levar à execução remota de código quando os dados não são confiáveis. Mantenha os dados Marshal dentro de uma fronteira confiável; nunca desserialize bytes fornecidos pelo usuário ou influenciados por um atacante.

Referências

Veja também Desserialização insegura: riscos e correção .NET BinaryFormatter: riscos e migração ObjectInputStream Java: desserialização segura Newtonsoft.Json TypeNameHandling: configuração segura? PHP unserialize() e entrada não confiável Segurança do pickle.load() em Python: riscos de dados não confiáveis