
Laboratório Docker de ponta a ponta reproduzindo o Apache log4j2 #4255 — bypass da allowlist do FilteredObjectInputStream via java.rmi.MarshalledObject (desserialização não filtrada → RCE) no Log4j 2.26.1 / JDK 17.
FilteredObjectInputStream via java.rmi.MarshalledObjectUm laboratório autônomo e containerizado que reproduz o problema #4255 do Apache log4j2 #4255 de ponta a ponta usando os artefatos oficiais do Log4j 2.26.1 no JDK 17, com controles positivos, um oráculo endurecido e uma mitigação validada.
⚠️ Uso responsável
Este laboratório contém um RCE funcional por desserialização contra um problema do Log4j atualmente sem correção (#4255 está ABERTO /
aguardando-mantenedorno momento da escrita; nenhum CVE atribuído). Ele roda inteiramente dentro de contêineres Docker descartáveis na sua própria máquina e não se conecta a nada externo, exceto ao Maven Central (para baixar os jars oficiais) — nenhum alvo é contatado.
- O relator original (U-Sec / Wujie Security) está retendo seu PoC até que uma correção seja publicada. Esta é uma reprodução independente construída para validação de defensores e engenharia de detecção.
- Não execute isso contra sistemas que você não possui ou para os quais não está explicitamente autorizado a testar.
O FilteredObjectInputStream (FOIS) do Log4j é uma allowlist de desserialização baseada em resolveClass.
Sua allowlist inclui java.rmi.MarshalledObject. Um MarshalledObject armazena seu payload como um
byte[] opaco, e MarshalledObject.get() desserializa esse payload em um ObjectInputStream novo e sem filtro
— portanto, a allowlist nunca inspeciona o grafo interno.
O próprio Log4j aciona isso: Log4jLogEvent$LogEventProxy (a forma serializada em fio de um LogEvent,
desde a versão 2.8) carrega o Message do evento dentro de um MarshalledObject e chama .get() automaticamente
durante a desserialização (readResolve() → message()). Qualquer aplicação que leia um LogEvent
serializado por meio do FOIS realiza, portanto, desserialização sem filtro de bytes do atacante — e
como message() engole a exceção resultante e recai para SimpleMessage, o receptor registra um evento
benigno e continua em execução. O exploit é silencioso.
rel/2.26.1)| # | Local | Defeito |
|---|---|---|
| 1 | log4j-api …/util/internal/SerializationUtil.java | REQUIRED_JAVA_CLASSES contém java.rmi.MarshalledObject |
| 2 | log4j-api …/util/FilteredObjectInputStream.java | sobrescreve apenas resolveClass(); o payload objBytes do MarshalledObject é invisível para ele |
| 3 | log4j-core …/impl/Log4jLogEvent.java | LogEventProxy.marshalledMessage é um MarshalledObject<Message> |
| 4 | log4j-core …/impl/Log4jLogEvent.java | message() chama marshalledMessage.get() (sem filtro) e engole todas as exceções |
Um substituto fiel do descontinuado ObjectInputStreamLogEventBridge do log4j-samples: um
receptor TCP não autenticado que lê um LogEvent serializado por conexão por meio do FOIS.
O atacante envia um único objeto serializado; o oráculo é um arquivo de prova gravado em um diretório
montado por bind apenas no contêiner do receptor, de modo que seu aparecimento prova que o código
foi executado dentro do receptor via desserialização.
| # | Cenário | Classpath da vítima | jdk.serialFilter | Esperado |
|---|---|---|---|---|
| S1 | gadget não permitido enviado no nível superior | + gadget | nenhum | rejeitar — o FOIS aplica sua allowlist |
| S2 | mesmo gadget envolvido em um LogEvent | + gadget | nenhum | rce — acionamento automático (precisa da classe do gadget na vítima) |
| S3 | CommonsCollections6 bruto no nível superior | log4j + cc-3.2.1 | nenhum | rejeitar — o FOIS bloqueia CC |
| S4 | CC6 inserido no MarshalledObject | log4j + cc-3.2.1 apenas | nenhum | rce — nenhuma classe do atacante na vítima |
| S5 | payload do S4 | log4j + cc-3.2.1 | !java.rmi.MarshalledObject | rejeitar — mitigação |
| S6 | payload do S4 | log4j + cc-3.2.1 | maxdepth=5;maxbytes=1000000 | silencioso — o fluxo interno herda o filtro; CC6 é profundo demais |
| S7 | payload do S4 | log4j + cc-3.2.2 | nenhum | silencioso — a 3.2.2 desativa a desserialização de functor insegura |
rce = código executado. rejeitar = o FOIS lançou exceção no fluxo externo. silencioso = objeto externo processado,
nenhum código executado (bloqueado mais profundamente, ou a versão do gadget é segura).
./run.sh # ou: make run
Requisitos: apenas Docker (um JDK é baixado como eclipse-temurin:17-jdk). O script baixa os
jars oficiais e os verifica contra o SHA-1 do Maven Central antes do uso. Fixe uma versão diferente do Log4j
na faixa vulnerável com LOG4J_VERSION=2.20.0 ./run.sh.
LogEvent serializado para um
receptor baseado em FOIS. Não é acionável ao fazer com que uma string seja registrada (diferente do Log4Shell) — é
necessário que bytes serializados brutos cheguem à ponte de soquete.LogEvent → o writeReplace()/writeObject() do Log4j envolve o Message
em um MarshalledObject → o gadget se esconde dentro de seu byte[] opaco → no receptor,
readResolve() → message() → MarshalledObject.get() abre um fluxo novo sem filtro →
gadget → Runtime.exec. src/attacker/Attacker.java (poc2) insere um grafo puro de CommonsCollections6
no objBytes do MarshalledObject, portanto nenhuma classe do atacante é necessária na vítima.-Djdk.serialFilter='!java.rmi.MarshalledObject' na JVM do receptor (S5).
Ressalva: isso também bloqueia objetos legítimos de LogEventProxy serializados (eles usam
MarshalledObject também) — é eficaz, mas não é transparente para o transporte de logs serializados.maxdepth/maxbytes. O filtro de todo o processo se propaga para
o fluxo interno do MarshalledObject e a profundidade recomeça ali, portanto maxdepth=5 bloqueia CC6 (S6),
mas um gadget raso passaria. Depende da profundidade da cadeia, não é um limite.MarshalledObject da allowlist e mover
a mensagem serializada para os writeWrappedObject/readWrappedObject filtrados do Log4j.LogEvent serializado não autenticado baseado em FOIS e tenha uma versão de gadget utilizável em
seu classpath. Implantações comuns do Log4j não executam tal receptor.net.server.TcpSocketServer
foi movido para fora do log4j-core em 2017; ausente desde a 2.9.0). Receptores modernos são
código de aplicação/exemplo, que este laboratório modela.uid=0 no laboratório é root do contêiner — não há escape do Docker; o RCE é executado como o
processo do receptor.MarshalledObject derrotando um filtro resolveClass) é arte anterior conhecida;
consulte a discussão do Apache #4168
("Endurecimento de desserialização do Log4j 2.x"). O acionamento automático específico do Log4j é a contribuição do #4255.run.sh executor portátil (com checksum fixado, oráculo endurecido)
Makefile make build | run | clean
src/victim/Receiver.java receptor de log FOIS (substituto do ObjectInputStreamLogEventBridge)
src/attacker/Attacker.java construtor de payload: controles, PoC-1, PoC-2 (CC6 + inserção de bytes)
src/attacker/EvilMessage.java gadget autônomo do PoC-1
docs/RESULTS.md matriz de evidências + análise
Vulnerabilidade relatada por U-Sec (Wujie Security) no Apache log4j2 #4255. Este repositório é um laboratório independente de reprodução/validação para pesquisa defensiva e engenharia de detecção.