Skip to main content

SnakeYaml 2.0: como resolver a vulnerabilidade de desserialização insegura

Escrito por
blog feature supply chain sbom

21 de junho de 2023

0 minutos de leitura

Em dezembro do ano passado, informamos a você sobre a CVE-2022-1471. Esse problema de desserialização insegura poderia levar facilmente à execução de código arbitrário nas circunstâncias certas.

No artigo de aprofundamento “Vulnerabilidade de desserialização insegura no SnakeYaml (CVE-2022-1471)”, expliquei os problemas dessa biblioteca e como ela poderia ser explorada. Em resumo, por padrão, o SnakeYaml analisava o YAML recebido como um objeto genérico. Isso criava a oportunidade de desserializar outras classes disponíveis no classpath. Independentemente da ClassCastException lançada, se o objeto já tiver sido carregado, o dano está feito.

O impacto depende muito de como você usa a biblioteca. Por exemplo, muitos desenvolvedores usam YAML apenas para fornecer configurações aos aplicativos. Essa vulnerabilidade só pode ser explorada se você aceitar YAML de fontes desconhecidas, como usuários finais. Ainda assim, não podemos prever como as pessoas vão usar uma biblioteca como essa, e a configuração padrão deve ser segura.

Atualize o SnakeYaml com o Snyk Open Source

Vamos usar o Snyk Open Source para encontrar uma substituição para a antiga biblioteca SnakeYaml. Se eu executar o snyk test localmente com a CLI do Snyk, vejo que há uma substituição disponível.

Saída do terminal alertando sobre a execução de código arbitrário em org.yaml:snakeyaml 1.33 e informando que o problema foi corrigido na versão 2.0

A interface web também informa que uma versão do SnykYaml 2.0 está disponível para resolver o problema. O único problema é que o Spring Boot 3 ainda inclui a versão 1.x, então preciso substituir a versão manualmente no arquivo de manifesto do Maven ou do Gradle.

Detalhes da vulnerabilidade da Snyk para org.yaml:snakeyaml: execução arbitrária de código, CVE-2022-1471, severidade média e pontuação 437.

Atenção: alterar manualmente a biblioteca para uma nova versão principal pode causar problemas. Não faça essas alterações sem avaliar o impacto potencial no funcionamento interno do seu aplicativo.

Como mitigar a desserialização com o SnakeYaml 2.0

O SnakeYaml 2.0 foi lançado no início de 2023 para mitigar o comportamento padrão que poderia levar à execução de código arbitrário. Nessa versão, o construtor usado por todo novo yaml() agora estende SafeConstructor. Com isso, só podemos analisar um conjunto limitado de tipos. O SafeConstrutor do SnakeYaml pode construir classes Java padrão, como tipos primitivos e classes básicas, como string e map.

Agora, tipos específicos não podem mais ser analisados por padrão. Portanto, o arquivo YAML abaixo vai gerar uma exceção:

!!Gadget ["env"]
Exception in thread "main" Global tag is not allowed: tag:yaml.org,2002:Gadget
 in 'reader', line 1, column 1:
    !!Gadget ["env"]
    ^

Agora, a implementação padrão não é mais vulnerável. No entanto, essa é uma alteração incompatível, então é provável que seu código atual deixe de funcionar.

Como corrigir a lógica de análise de YAML ao usar o SnakeYaml 2.x

Antes de tudo, precisamos garantir que não usamos mais o SnakeYaml 1.x. Nem mesmo a versão mais recente do Spring Boot, a 3.1, inclui atualmente o SnakeYaml 2.x. Isso significa que precisamos atualizá-lo por conta própria no arquivo de manifesto. Em projetos Maven, podemos usar a seção <dependencyManagement> do arquivo pom entre outras opções. No Gradle, também é possível atualizar dependências transitivas usando restrições de dependência.

Observe que o SnakeYaml 2.x tem alterações incompatíveis na API em relação às versões anteriores. Isso significa que precisamos reescrever a implementação de análise de YAML para usar os novos padrões seguros e voltar a fazê-la funcionar.

Vamos considerar um domínio bem simples, com duas classes:

  • Pessoa

  • Comentário

Person.java:

public class Person {
    private String name;
    private int age;

    private Comment comment;

    public Person() {
    }

    public Person(String name, int age, Comment class2) {
        this.name = name;
        this.age = age;
        this.comment = class2;
    }
    //getters and setters
}

Comment.java:

public class Comment {

    private String text;
    private String dateTime;

    public Comment() {}

    public Comment(String text) {
        this.text = text;
        this.dateTime = LocalDateTime.now().toString();
    }
    //getters and setters
}

Se você quiser criar um arquivo YAML a partir de uma entidade como a acima, provavelmente escreveu algo parecido com o código a seguir ao usar o SnakeYaml 1.x:

var john = new Person("John", 31, new Comment("This is a comment"));
dumpYaml(john, "file.yaml");

public static void dumpYaml(Person pojo, String filename) throws IOException {
    Yaml yaml = new Yaml();

    try (FileWriter writer = new FileWriter(filename)) {
        yaml.dump(pojo, writer);
    }
}

O resultado é o seguinte arquivo YAML:

!!mypackage.Person
age: 31
comment: {dateTime: '2023-06-15T16:53:23.175989', text: This is a comment}
name: John

Com o SnakeYaml 2.x, !!mypackage.Person não é mais aceito. Agora podemos eliminar a referência ao objeto ao analisá-lo para um arquivo YAML. No entanto, ainda há um problema com arquivos YAML exportados antes dessa migração.

Felizmente, ainda podemos resolver esse problema! Ao analisar um objeto específico, você pode definir qual construtor o analisador deve usar. Além disso, podemos adicionar um TagInspector específico às LoaderOptions para permitir a tag do nosso pacote. Assim, você permite apenas arquivos YAML compatíveis com o seu objeto e mantém a compatibilidade com versões anteriores dos arquivos YAML criados com as versões 1.x.

public static Person parseYaml(String filename) throws IOException {
        var loaderoptions = new LoaderOptions();
        TagInspector taginspector = 
                tag -> tag.getClassName().equals(Person.class.getName());
        loaderoptions.setTagInspector(taginspector);

        Yaml yaml = new Yaml(new Constructor(Person.class, loaderoptions));

        try (InputStream in = new FileInputStream(filename)) {
            // Parse the YAML file into a mypackage.MyYamlClass object
            Person obj = yaml.load(in);
            return obj;
        }
    }

Além disso, seria melhor remover totalmente do arquivo YAML a referência ou a tag do objeto em si. Isso já era possível nas versões anteriores do SnakeYaml: bastava adicionar um representer ao objeto YAML para mapear a tag do objeto de nível superior para um mapa. Abaixo, você encontra um exemplo compatível com o SnakeYaml 2.x:

    public static void dumpYaml(Person pojo, String filename) throws IOException {
        Representer customRepresenter = new Representer(new DumperOptions());
        customRepresenter.addClassTag(Person.class, Tag.MAP);

        Yaml yaml = new Yaml(new Constructor(Person.class, new LoaderOptions()), 
                customRepresenter);

        try (FileWriter writer = new FileWriter(filename)) {
            yaml.dump(pojo, writer);
        }
    }

O arquivo YAML não começará mais com !!mypackage.Person (ou algo parecido). Se todos os seus arquivos YAML estiverem limpos, você poderá remover o TagInspector do analisador.

Fique por dentro das novidades da Snyk

Manter todas as versões das suas bibliotecas atualizadas é essencial para a segurança do código aberto. Usar a versão 1.x do SnakeYaml pode levar a problemas de segurança desnecessários se você aceitar direta ou indiretamente arquivos YAML de fontes externas.

Snyk Open Source ajuda você a encontrar e corrigir esses problemas ou indica uma versão alternativa, se necessário — como no exemplo a seguir, que mostra que atualizar o SnakeYaml para a versão 2.0 ou posterior elimina a vulnerabilidade de execução de código arbitrário.

Detalhes da vulnerabilidade do Snyk para org.yaml:snakeyaml, mostrando execução arbitrária de código, CVE-2022-1471, gravidade média e versões afetadas.

Além disso, se você conectar seu repositório Git ao Snyk, podemos abrir pull requests para manter suas dependências atualizadas, mesmo quando não houver nenhum problema crítico de segurança. Prevenir é melhor do que remediar: manter suas bibliotecas atualizadas ajuda a reduzir o risco de vulnerabilidades e o trabalho extra que surge quando é preciso atualizar tudo por causa de um problema de segurança.

Comentário de pull request do bot da Snyk recomendando a atualização da versão 1.7.0 para a 1.8.1 para manter as dependências atualizadas.

Comece a resolver desafios de capture the flag

Aprenda a resolver desafios de capture the flag assistindo sob demanda ao nosso workshop virtual introdutório.

Leia mais

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.

feature insights context
Blog

A prevenção é essencialmente um problema resolvido?

A prevenção em código gerado por agentes está resolvida do ponto de vista arquitetural — mas escolher controles que protejam a segurança sem desacelerar o desenvolvimento continua sendo um desafio.

Live Stream

Agentes de Remediação, Desmistificados: Por Que Corrigir é Melhor do que Encontrar

Veja como o Remediation Agent da Snyk usa inteligência de segurança, análise de explorabilidade e validação para transformar vulnerabilidades em pull requests prontos para merge.