Explorando desserialização insegura no Jackson databind

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

Java JSON deserialization problems with the Jackson ObjectMapper | Snyk
Learn how Jackson ObjectMapper deserialization vulnerabilities work and how to make sure you are not affected by them.
GitHub - Ingenuity-Fainting-Goats/CVE-2017-7525-Jackson-Deserialization-Lab: Insecure Java Deserialization Lab
Insecure Java Deserialization Lab. Contribute to Ingenuity-Fainting-Goats/CVE-2017-7525-Jackson-Deserialization-Lab development by creating an account on GitHub.

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!