
Laboratorio Docker end-to-end che riproduce Apache log4j2 #4255 — bypass dell'allowlist di FilteredObjectInputStream tramite java.rmi.MarshalledObject (deserializzazione non filtrata → RCE) su Log4j 2.26.1 / JDK 17.
FilteredObjectInputStream tramite java.rmi.MarshalledObjectUn laboratorio autonomo e containerizzato che riproduce end-to-end l'issue Apache log4j2 #4255 utilizzando gli artefatti ufficiali Log4j 2.26.1 su JDK 17, con controlli positivi, un oracle indurito e una mitigazione validata.
⚠️ Uso responsabile
Questo laboratorio contiene una RCE funzionante tramite deserializzazione contro un'issue Log4j attualmente non corretta (#4255 è APERTA /
waiting-for-maintaineral momento della scrittura; nessun CVE assegnato). Viene eseguito interamente all'interno di container Docker usa-e-getta sulla tua macchina e non si connette a nulla di esterno tranne Maven Central (per scaricare i jar ufficiali) — nessun target viene contattato.
- Il reporter originale (U-Sec / Wujie Security) sta trattenendo il proprio PoC in attesa di una correzione. Questa è una riproduzione indipendente costruita per la validazione dei difensori e l'ingegneria del rilevamento.
FilteredObjectInputStream (FOIS) di Log4j è una allowlist di deserializzazione basata su resolveClass.
La sua allowlist include java.rmi.MarshalledObject. Un MarshalledObject memorizza il proprio payload come
byte[] opaco, e MarshalledObject.get() deserializza quel payload su un ObjectInputStream nuovo e non filtrato
— quindi la allowlist non ispeziona mai il grafo interno.
Log4j lo innesca da solo: Log4jLogEvent$LogEventProxy (la forma serializzata su filo di un LogEvent,
dal 2.8) trasporta il Message dell'evento all'interno di un MarshalledObject e chiama .get() automaticamente
durante la deserializzazione (readResolve() → message()). Qualsiasi applicazione che legge un LogEvent
serializzato tramite FOIS esegue quindi una deserializzazione non filtrata di byte dell'attaccante — e
poiché message() inghiotte l'eccezione risultante e ripiega su SimpleMessage, il ricevente registra
un evento benigno e continua a funzionare. Lo sfruttamento è silenzioso.
rel/2.26.1)| # | Posizione | Difetto |
|---|---|---|
| 1 | log4j-api …/util/internal/SerializationUtil.java | REQUIRED_JAVA_CLASSES contiene java.rmi.MarshalledObject |
| 2 | log4j-api …/util/FilteredObjectInputStream.java | sovrascrive solo resolveClass(); il payload objBytes del MarshalledObject è invisibile ad esso |
| 3 | log4j-core …/impl/Log4jLogEvent.java | LogEventProxy.marshalledMessage è un MarshalledObject<Message> |
| 4 | log4j-core …/impl/Log4jLogEvent.java | message() chiama marshalledMessage.get() (senza filtro) e inghiotte tutte le eccezioni |
Un sostituto fedele del deprecato ObjectInputStreamLogEventBridge di log4j-samples: un
ricevitore TCP non autenticato che legge un LogEvent serializzato per connessione tramite FOIS.
L'attaccante invia un singolo oggetto serializzato; l'oracle è un file di prova scritto in una directory
montata in bind solo nel container del ricevitore, quindi la sua comparsa dimostra che il codice è stato eseguito
all'interno del ricevitore tramite deserializzazione.
| # | Scenario | Classpath vittima | jdk.serialFilter | Atteso |
|---|---|---|---|---|
| S1 | gadget non consentito inviato a livello top | + gadget | nessuno | rifiuto — FOIS applica la sua allowlist |
| S2 | stesso gadget avvolto in un LogEvent | + gadget | nessuno | rce — auto-innesco (richiede la classe gadget sulla vittima) |
| S3 | CommonsCollections6 grezzo a livello top | log4j + cc-3.2.1 | nessuno | rifiuto — FOIS blocca CC |
| S4 | CC6 inserito nel MarshalledObject | log4j + cc-3.2.1 solo | nessuno | rce — nessuna classe dell'attaccante sulla vittima |
| S5 | payload S4 | log4j + cc-3.2.1 | !java.rmi.MarshalledObject | rifiuto — mitigazione |
| S6 | payload S4 | log4j + cc-3.2.1 | maxdepth=5;maxbytes=1000000 | silenzioso — lo stream interno eredita il filtro; CC6 troppo profondo |
| S7 | payload S4 | log4j + cc-3.2.2 | nessuno | silenzioso — 3.2.2 disabilita la deserializzazione di functor non sicuri |
rce = codice eseguito. rifiuto = FOIS ha lanciato un'eccezione sullo stream esterno. silenzioso = oggetto esterno elaborato,
nessun codice eseguito (bloccato più in profondità, o la versione del gadget è sicura).
./run.sh # oppure: make run
Requisiti: solo Docker (un JDK viene scaricato come eclipse-temurin:17-jdk). Lo script scarica i
jar ufficiali e li verifica contro lo SHA-1 di Maven Central prima dell'uso. Fissa una versione Log4j
diversa nell'intervallo vulnerabile con LOG4J_VERSION=2.20.0 ./run.sh.
LogEvent serializzato verso un
ricevitore basato su FOIS. Non è attivabile facendo registrare una stringa nei log (a differenza di Log4Shell) —
richiede byte serializzati grezzi che raggiungono il bridge socket.LogEvent → writeReplace()/writeObject() di Log4j avvolge il Message
in un MarshalledObject → il gadget si nasconde nel suo byte[] opaco → sul ricevitore,
readResolve() → message() → MarshalledObject.get() apre uno stream nuovo non filtrato →
gadget → Runtime.exec. src/attacker/Attacker.java (poc2) inserisce un grafo puro CommonsCollections6
nei objBytes del MarshalledObject, quindi nessuna classe dell'attaccante è necessaria sulla vittima.-Djdk.serialFilter='!java.rmi.MarshalledObject' sulla JVM del ricevitore (S5).
Avvertenza: questo blocca anche gli oggetti LogEventProxy serializzati legittimi (anche loro usano
MarshalledObject) — è efficace ma non trasparente per il trasporto di log serializzati.maxdepth/maxbytes. Il filtro a livello di processo si propaga
nello stream interno del MarshalledObject e la profondità ricomincia lì, quindi maxdepth=5 blocca CC6 (S6)
ma un gadget poco profondo passerebbe. Dipende dalla profondità della catena, non è un confine.MarshalledObject dalla allowlist e sposta
il messaggio marshallato nei metodi filtrati writeWrappedObject/readWrappedObject di Log4j.LogEvent serializzato non autenticato basato su FOIS e ha una versione di gadget utilizzabile
nel proprio classpath. Le distribuzioni Log4j ordinarie non eseguono un tale ricevitore.net.server.TcpSocketServer
è stato spostato fuori da log4j-core nel 2017; assente dalla 2.9.0 in poi). I ricevitori moderni sono
codice applicativo/di esempio, che questo laboratorio modella.uid=0 nel laboratorio è root del container — non c'è fuga da Docker; la RCE viene eseguita come
processo del ricevitore.MarshalledObject che sconfigge un filtro resolveClass) è arte nota precedente;
vedi la discussione Apache #4168
("Log4j 2.x deserialization hardening"). L'auto-innesco specifico di Log4j è il contributo di #4255.run.sh esecutore portabile (checksum bloccato, oracle indurito)
Makefile make build | run | clean
src/victim/Receiver.java ricevitore log FOIS (sostituto di ObjectInputStreamLogEventBridge)
src/attacker/Attacker.java costruttore di payload: controlli, PoC-1, PoC-2 (CC6 + byte-splice)
src/attacker/EvilMessage.java gadget autonomo PoC-1
docs/RESULTS.md matrice di evidenze + analisi
Vulnerabilità segnalata da U-Sec (Wujie Security) nell'issue Apache log4j2 #4255. Questo repository è un laboratorio indipendente di riproduzione/validazione per ricerca difensiva e ingegneria del rilevamento.