CodeGym /Cursos /JAVA 25 SELF /Problemas da serialização binária: segurança, compatibili...

Problemas da serialização binária: segurança, compatibilidade

JAVA 25 SELF
Nível 45 , Lição 0
Disponível

1. Segurança da serialização binária

Serialização em Java — não é apenas salvar os campos de um objeto. É a possibilidade de “reconstruir” qualquer objeto com qualquer conteúdo, desde que ele implemente a interface Serializable. Parece conveniente! Mas, se seu aplicativo desserializa dados obtidos de uma fonte não confiável (por exemplo, da rede ou de um arquivo que pode ter sido substituído por um invasor), ele corre o risco de sofrer ataques.

Como isso funciona?

Durante a desserialização, o Java cria objetos a partir de um fluxo de bytes, sem chamar os construtores das classes. Se na classe existirem métodos especiais como readObject, eles serão chamados automaticamente. Um invasor pode “construir” o fluxo de bytes de forma que, ao desserializar, execute código vulnerável.

Exemplo: ataque “gadget chain”

Imagine que você tem uma classe que, ao desserializar, executa um comando externo (lê um arquivo ou chama o shell). Se o invasor souber que o aplicativo desserializa objetos de certos tipos, ele pode injetar um fluxo de bytes especial que levará à execução de código malicioso. Esses ataques são construídos como uma “gadget chain” — uma sequência de chamadas que termina em uma operação perigosa.

Por que isso é tão crítico?

Desserialização é um processo no qual objetos reais são restaurados a partir de um fluxo de dados, e nesse momento código arbitrário pode ser executado. Se os dados vêm de uma fonte não confiável, isso abre a porta para execução remota de comandos (RCE). Grandes empresas publicaram recomendações para abandonar a desserialização insegura; em projetos corporativos modernos, a serialização binária é frequentemente proibida por políticas de segurança.

Como se proteger?

  • Nunca desserialize objetos de fontes não confiáveis.
  • Use lista de permissões (whitelisting) — uma lista explícita de tipos permitidos para desserialização.
  • Prefira formatos de texto para integrações externas: JSON, XML.
  • Se a serialização for inevitável, use bibliotecas com configurações de desserialização segura (por exemplo, Jackson com restrição de tipos).
  • Restrinja o uso de métodos de serialização não padrão (readObject, readResolve etc.) se não tiver certeza de sua segurança.

2. Compatibilidade entre versões de classes

A serialização binária em Java é rigidamente vinculada à estrutura da classe. Você serializou um objeto na versão 1.0, depois atualizou a classe (adicionou/removeu um campo) — a tentativa de desserializar o objeto “antigo” na nova versão pode levar a erro ou perda de dados.

Como o Java determina a compatibilidade?

Para isso é usado um campo especial — serialVersionUID. É o identificador de versão da classe. Se o objeto serializado tem um serialVersionUID e a classe atual tem outro, será lançado InvalidClassException, e a desserialização não acontecerá.

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L; // versão definida explicitamente

    private String name;
    private int age;
}

Se você alterar a estrutura da classe (por exemplo, adicionar o campo email) e não alterar o serialVersionUID, o Java considerará que a classe é compatível e tentará desserializar o objeto antigo. Se você não definiu o serialVersionUID explicitamente, a JVM o gerará automaticamente com base na estrutura, e qualquer mudança levará à incompatibilidade.

O que acontece quando as versões não coincidem/coincidem?

Se os identificadores não coincidirem — a desserialização não ocorrerá: InvalidClassException. Se coincidirem — os campos são mapeados por nome e tipo: novos campos receberão valores padrão (null, 0), os removidos — são ignorados. Ao mudar o tipo ou o nome de um campo, podem ocorrer erros e interpretação incorreta dos dados.

Conselho prático. Em classes serializáveis, sempre defina explicitamente o serialVersionUID. Altere-o apenas em mudanças incompatíveis (remoção/mudança de tipo de um campo importante). Ao adicionar novos campos, o identificador pode permanecer o mesmo — a JVM processará corretamente os objetos antigos.

Tabela: O que acontece ao alterar a classe

Alteração na classe O que acontece na desserialização?
Campo novo adicionado Recebe o valor padrão (0, null)
Campo removido Ignorado ao ler dados antigos
Tipo de campo alterado Exceção ou dados incorretos
Nome do campo alterado O campo antigo é ignorado; o novo — recebe o padrão
serialVersionUID alterado Exceção InvalidClassException

3. Limitações da serialização padrão

Nem todos os objetos podem ser serializados

Campos com os modificadores transient e static não são serializados. static — porque pertence à classe, não ao objeto; transient — porque você explicitamente proibiu a serialização do campo.

Alguns objetos, por definição, não são serializáveis: Thread, conexões com BD, sockets, Scanner etc. Se sua classe tem um campo desse tipo e ele não é transient, você receberá NotSerializableException.

import java.io.Serializable;
import java.util.Scanner;

public class Session implements Serializable {
    private transient Scanner scanner; // não é serializado!
    private String login;
}

Problemas de desempenho e extensibilidade

Serializar grandes grafos de objetos pode ser lento e exigir muita memória.

O formato binário é pouco adequado para integrações com outras plataformas e linguagens — só o Java “entende”.

É difícil controlar o que exatamente é serializado, especialmente em hierarquias profundas e com referências cíclicas.

Problemas ao manter dados antigos

Armazenar dumps binários por longo prazo é um risco. Em um ou dois anos, a estrutura das classes muda e os arquivos antigos deixam de carregar.

História real: “Salvamos um cache de usuários serializado três anos atrás, atualizamos o aplicativo, e agora não conseguimos carregá-lo. Adeus, dados perdidos!”

4. Boas práticas: como evitar armadilhas

  • Use a serialização binária apenas para tarefas internas, onde você controla ambos os lados do processo.
  • Não use serialização binária para integrações externas e para armazenamento de longo prazo de dados importantes.
  • Sempre defina explicitamente o serialVersionUID nas classes serializáveis.
  • Marque com o modificador transient os campos que não devem ser serializados.
  • Para integração com sistemas externos — formatos de texto e bibliotecas modernas: JSON, XML, Jackson, Gson, JAXB.
  • Para compatibilidade, use versionamento: armazene a versão do objeto na própria classe e adapte o processamento durante a desserialização.
  • Se a serialização for necessária apenas para cache — não mantenha compatibilidade a qualquer custo: é mais fácil recalcular o cache.
  • Não armazene, em objetos serializáveis, dados sensíveis (senhas, chaves) — a serialização não criptografa os dados.

5. Erros comuns ao trabalhar com serialização binária

Erro nº 1: Desserializar dados de fonte não confiável. O erro mais perigoso é aceitar e desserializar objetos que vieram “da rua” (da rede, do usuário, de um arquivo adulterado). É um caminho direto para vulnerabilidades, inclusive RCE.

Erro nº 2: Alterar implicitamente a estrutura da classe sem atualizar o serialVersionUID. Se você não definir o identificador explicitamente, a JVM o gerará automaticamente. Qualquer mudança na estrutura (até a ordem dos campos) levará à incompatibilidade e à impossibilidade de carregar objetos antigos.

Erro nº 3: Tentar serializar objetos com campos não serializáveis. Se a classe tiver um campo que não implementa Serializable e ele não for transient, a serialização terminará com uma exceção.

Erro nº 4: Armazenar, em objetos serializáveis, dados temporários ou sensíveis. Tokens, senhas, descritores temporários de recursos — tudo isso pode acabar no arquivo sem querer.

Erro nº 5: Usar serialização binária para armazenamento de longo prazo e troca entre versões. Após a primeira atualização das classes, há um alto risco de dados “corrompidos” e problemas de compatibilidade.

Erro nº 6: Esperar que campos static e transient “se recuperem” após a desserialização. Esses campos não são serializados; após o carregamento, eles terão valores padrão.

1
Tarefa
JAVA 25 SELF, nível 45, lição 0
Bloqueado
Campo não serializável para sessão de jogo
Campo não serializável para sessão de jogo
1
Tarefa
JAVA 25 SELF, nível 45, lição 0
Bloqueado
Expansão da biblioteca digital e verificação de compatibilidade
Expansão da biblioteca digital e verificação de compatibilidade
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION