
Relatório de investigação sobre as vulnerabilidades S2-045, S2-055 do Struts2 e as vulnerabilidades CVE-2017-7525, CVE-2017-15095 do Jackson
Publicamos um resumo simplificado e de fácil leitura. Recomendado para quem deseja uma visão geral ou tem pouco tempo.
Em 1º de dezembro de 2017, foi publicada uma atualização de segurança do Struts2. Antes da publicação, já circulava na lista de discussão a informação de que as vulnerabilidades do Jackson (biblioteca JSON popular em Java) estavam relacionadas. O autor, que utiliza Jackson em sistemas e ferramentas internas, também estava preocupado com o conteúdo específico.
Quanto ao conteúdo realmente divulgado, os dois problemas de segurança a seguir foram corrigidos. A vulnerabilidade do jackson-databind, componente do Jackson, afeta apenas o S2-055.
No plugin REST, parece que anteriormente já estavam integrados handlers que usavam JSON-lib e handlers que usavam Jackson, permitindo que o usuário escolhesse. Acredita-se que a correção completa desta vez consiste em: no S2-054, alterar o handler padrão para Jackson e, no S2-055, atualizar a versão antiga do Jackson para a mais recente.
Afinal, que tipo de vulnerabilidade é o CVE-2017-7525? Como o autor costuma usar Jackson ao processar JSON em Java, este artigo é o resultado de uma investigação sobre o problema durante o fim de semana de 2 e 3 de dezembro.
Ambiente do autor utilizado para verificação do código de amostra:
No blog de Adam Caudill, foi publicada uma explicação sobre o CVE-2017-7525.
Em resumo, com minhas próprias palavras: o jackson-databind fornece a funcionalidade de mapear JSON para objetos Java (classe ObjectMapper).
Ao chamar ObjectMapper.enableDefaultTyping(), é possível mapear usando um nome de classe inserido de forma personalizada no JSON.
Quem pensou "quando é possível especificar o nome da classe a partir do JSON de entrada, já dá um mau pressentimento" acertou em cheio: esse mau pressentimento se concretizou no CVE-2017-7525.
Antes de entrar na explicação da vulnerabilidade, explico por que essa funcionalidade foi implementada.
Consulte o código de amostra a seguir para o uso básico da desserialização com jackson-databind. (Neste artigo, usamos Groovy para os códigos de amostra do Jackson. É conveniente poder alternar facilmente a versão do jackson-databind com @Grab.)
No código de amostra acima, a chave "animal" pode ser mapeada diretamente para a classe Animal. E nos casos a seguir, como fica?```java class Zoo { Animal animal; }
abstract class Animal { String name; protected Animal() { } }
class Dog extends Animal { double barkVolume; Dog() { } }
class Cat extends Animal { boolean likesCream; int lives; Cat() { } }
Nesta configuração, surgem dois tipos de conteúdo para a chave "animal": um apontando para a classe Dog e outro para a classe Cat. Portanto, são necessárias informações adicionais para saber qual classe deve ser usada no mapeamento.
Para resolver isso, o jackson-databind incorporou um processamento próprio que permite embutir o nome da classe a ser mapeada no JSON.
Por exemplo, como mostrado a seguir, o conteúdo da chave "animal" é transformado em um array, e o primeiro elemento especifica o nome da classe.```
{"animal":["Dog",{"name":"dog1","barkVolume":1.2}]}
Isso permite que ObjectMapper.readValue() reconheça o conteúdo da chave "animal" como da classe Dog e realize o mapeamento.
Naturalmente, não é possível distinguir, apenas com isso, se o conteúdo da chave "animal" era originalmente um array ou se continha informações de nome de classe específicas do jackson-databind.
Quem faz essa alternância é o método ObjectMapper.enableDefaultTyping().
Também existe a possibilidade de definir a anotação @JsonTypeInfo na classe. Para mais detalhes, consulte a documentação do Jackson abaixo:
A seguir, apresentamos um código de exemplo que realmente utiliza o método ObjectMapper.enableDefaultTyping():
Como vimos acima, ao fornecer o nome da classe e suas propriedades em JSON, é possível, embora com algumas limitações, instanciar classes arbitrárias com propriedades arbitrárias.
A vulnerabilidade CVE-2017-7525 explora exatamente isso, e acredita-se que o gatilho tenha sido o seguinte relatório:
Bibliotecas de serialização/desserialização comuns em Java, como Jackson, foram relatadas como perigosas, pois manipulações de nomes de classe podem levar à execução de código arbitrário. Além disso, foram listados nomes de classes concretas que representam risco.
Não se sabe se foi em resposta a isso, mas cronologicamente, logo após o primeiro commit do repositório acima, foi aberta a seguinte Issue no jackson-databind e iniciou-se o tratamento:
Como seriam, na prática, os dados JSON e o código Java que exploram essa vulnerabilidade?
O código de teste do jackson-databind 2.8.9, que tratou essa Issue, fornece uma dica:
Com base nesse código de teste, apresentamos a seguir um exemplo de código ajustado para permitir verificação de funcionamento:
Se você executar especificando a versão 2.8.9 com @Grab, será exibido your jackson version IS SAFE to CVE-2017-7525. Isso ocorre porque a correção da versão 2.8.9 adicionou uma verificação de lista negra para nomes de classes que são instanciadas.