Desserialização Java Básica com ObjectInputStream readObject
Tip
Aprenda e pratique AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Navegue pelo catálogo completo do HackTricks Training para as trilhas de assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord, ao grupo do telegram, siga @hacktricks_live no X/Twitter, ou confira a página do LinkedIn e o canal do YouTube.
- Compartilhe hacking tricks enviando PRs para os repositórios github HackTricks e HackTricks Cloud.
Neste POST será explicado um exemplo usando java.io.Serializable e por que sobrescrever readObject() pode ser extremamente perigoso se o stream de entrada for controlado por um atacante.
Serializable
A interface Java Serializable (java.io.Serializable) é uma marker interface que suas classes devem implementar se elas forem serializadas e desserializadas. A serialização de objetos Java (escrita) é feita com o ObjectOutputStream e a desserialização (leitura) é feita com o ObjectInputStream.
Lembrete: Quais métodos são invocados implicitamente durante a desserialização?
readObject()– lógica de leitura específica da classe (se implementado e privado).readResolve()– pode substituir o objeto desserializado por outro.validateObject()– via callbacks deObjectInputValidation.readExternal()– para classes que implementamExternalizable.- Construtores não são executados – portanto gadget chains dependem exclusivamente dos callbacks anteriores.
Qualquer método nessa cadeia que acabe invocando dados controlados por um atacante (execução de comandos, JNDI lookups, reflection, etc.) transforma a rotina de desserialização em um gadget de RCE.
Vamos ver um exemplo com uma class Person que é serializable. Esta classe sobrescreve o readObject, então quando qualquer objeto desta classe for desserializado esta função vai ser executada.
No exemplo, a função readObject da classe Person chama a função eat() do seu pet e a função eat() de um Dog (por algum motivo) executa um calc.exe. Vamos ver como serializar e desserializar um objeto Person para executar essa calculadora:
The following example is from https://medium.com/@knownsec404team/java-deserialization-tool-gadgetinspector-first-glimpse-74e99e493649
import java.io.Serializable;
import java.io.*;
public class TestDeserialization {
interface Animal {
public void eat();
}
//Class must implements Serializable to be serializable
public static class Cat implements Animal,Serializable {
@Override
public void eat() {
System.out.println("cat eat fish");
}
}
//Class must implements Serializable to be serializable
public static class Dog implements Animal,Serializable {
@Override
public void eat() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("dog eat bone");
}
}
//Class must implements Serializable to be serializable
public static class Person implements Serializable {
private Animal pet;
public Person(Animal pet){
this.pet = pet;
}
//readObject implementation, will call the readObject from ObjectInputStream and then call pet.eat()
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException {
pet = (Animal) stream.readObject();
pet.eat();
}
}
public static void GeneratePayload(Object instance, String file)
throws Exception {
//Serialize the constructed payload and write it to the file
File f = new File(file);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
out.flush();
out.close();
}
public static void payloadTest(String file) throws Exception {
//Read the written payload and deserialize it
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
Object obj = in.readObject();
System.out.println(obj);
in.close();
}
public static void main(String[] args) throws Exception {
// Example to call Person with a Dog
Animal animal = new Dog();
Person person = new Person(animal);
GeneratePayload(person,"test.ser");
payloadTest("test.ser");
// Example to call Person with a Cat
//Animal animal = new Cat();
//Person person = new Person(animal);
//GeneratePayload(person,"test.ser");
//payloadTest("test.ser");
}
}
Conclusão (cenário clássico)
Como você pode ver neste exemplo bem básico, a “vulnerabilidade” aqui ocorre porque o método readObject() está chamando outro código controlado pelo atacante. Em gadget chains do mundo real, milhares de classes contidas em bibliotecas externas (Commons-Collections, Spring, Groovy, Rome, SnakeYAML, etc.) podem ser abusadas – o atacante precisa de apenas um gadget alcançável para obter execução de código.
2023-2025: O que mudou em bugs reais de deserialização Java?
Casos recentes lembram que bugs em ObjectInputStream não são mais apenas “fazer upload de um arquivo .ser para um endpoint HTTP legado”:
- Brokers / consumidores de filas: Spring-Kafka (
CVE-2023-34040) mostrou que deserializar headers de exceção de tópicos controlados pelo atacante é suficiente se o consumer habilita as incomuns flagscheckDeserExWhen*. - Confiança do lado do cliente em servidores remotos: o cliente Java Aerospike (
CVE-2023-36480) deserializou objetos recebidos do servidor. A resposta do fornecedor foi notável: clientes mais novos removeram o suporte à serialização/deserialização nativa do Java em vez de tentar preservá-lo atrás de um filtro fraco. - Streams “restritos” ainda costumam ser amplos demais:
pac4j-core(CVE-2023-25581) tentou proteger a deserialização comRestrictedObjectInputStream, mas o conjunto de classes aceitas ainda era grande o bastante para possibilitar abuso de gadget.
A lição ofensiva é que o boundary de confiança perigoso muitas vezes não é “usuário faz upload de um blob”, mas “algum componente que o desenvolvedor considerou confiável pode injetar bytes em um stream que eventualmente chega em readObject()”.
Se você precisa de checks de reachability de baixo ruído antes de gastar tempo com pesquisa completa de gadgets, use as páginas Java dedicadas para:
Java DNS Deserialization, GadgetProbe and Java Deserialization Scanner
Anti-padrões em readObject() que ainda criam pontos de entrada para gadgets
Mesmo que sua própria classe não seja um gadget RCE óbvio, os seguintes padrões são suficientes para torná-la explorável quando objetos controlados pelo atacante estão embutidos no grafo:
- Chamar métodos sobrescritíveis ou métodos de interface a partir de
readObject()(pet.eat()no PoC acima é o exemplo clássico). - Realizar lookups, reflection, carregamento de classes, avaliação de expressões ou operações JNDI durante a deserialização.
- Iterar sobre collections ou maps controlados pelo atacante, o que pode disparar
hashCode(),equals(), comparators ou transformers como efeitos colaterais. - Registrar callbacks
ObjectInputValidationque realizam pós-processamento perigoso. - Assumir que “readObject() privado” é proteção suficiente. Isso apenas controla a semântica de dispatch; não torna a deserialização segura.
Mitigações modernas que você deve implantar
- JEP 290 / Serialization Filtering (Java 9+) Use uma allow-list e limites explícitos do grafo:
-Djdk.serialFilter="com.example.dto.*;java.base/*;maxdepth=5;maxrefs=1000;maxbytes=16384;!*"
- Aplique um filter em todo stream não confiável, não apenas globalmente:
try (var ois = new ObjectInputStream(input)) {
var filter = ObjectInputFilter.Config.createFilter(
"com.example.dto.*;java.base/*;maxdepth=5;maxrefs=1000;!*"
);
ois.setObjectInputFilter(filter);
return (Message) ois.readObject();
}
- JEP 415 (Java 17+) Context-Specific Filter Factories Prefira isso quando a mesma JVM tiver múltiplos contextos de deserialização (RMI, cache replication, message consumers, admin-only imports) e cada um precisar de uma allow-list diferente.
- Mantenha
readObject()monótono Chame apenasdefaultReadObject()/ leituras explícitas de campos, então execute checagens estritas de invariantes. Não faça I/O, logging que desreferencie objetos controlados pelo atacante, lookups dinâmicos ou chamadas de método em sub-objetos deserializados. - Se possível, remova a serialização nativa do Java do design
O fix do Aerospike é um bom modelo: quando o recurso não é essencial, deletar o uso de
readObject()/writeObject()costuma ser mais seguro do que tentar manter filtros perfeitos para sempre.
Fluxo de trabalho para detecção e pesquisa
ysoserialcontinua sendo a base para validação de gadgets e probes rápidos de RCE/URLDNS.marshalsecainda é útil quando o sink pivota para território JNDI/LDAP/RMI.GadgetInspectoré útil quando você tem os jars do alvo e precisa procurar por gadget chains específicos da aplicação.- Java 17 adicionou o evento
jdk.Deserializationno Flight Recorder, que é útil para ver ondeObjectInputStreamé realmente usado e se filtros estão sendo aplicados.
Checklist rápido para implementações seguras de readObject()
- Torne o método
privatee anote hooks de serialização com@Serialpara que compiladores possam detectar assinaturas declaradas incorretamente. - Chame
defaultReadObject()primeiro, a menos que você tenha uma razão forte para ler manualmente todo o object graph. - Trate todo objeto aninhado como controlado pelo atacante até ser validado.
- Nunca invoque métodos em colaboradores deserializados de dentro de
readObject(). - Pareie a revisão de código com uma revisão de
ObjectInputFilter; “códigoreadObject()que parece seguro” não é suficiente se o stream ainda aceita classes arbitrárias.
Referências
- OpenJDK JEP 415: Context-Specific Deserialization Filters
- GitHub Security Lab: GHSL-2022-085 / CVE-2023-25581 (
pac4j-coredeserialization leading to RCE)
Tip
Aprenda e pratique AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Navegue pelo catálogo completo do HackTricks Training para as trilhas de assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord, ao grupo do telegram, siga @hacktricks_live no X/Twitter, ou confira a página do LinkedIn e o canal do YouTube.
- Compartilhe hacking tricks enviando PRs para os repositórios github HackTricks e HackTricks Cloud.


