Zum Inhalt springen

LDAP Injection

CWE-90OWASP A03:2021Aktualisiert 31. August 20266 Min. Lesezeit

LDAP Injection ist eine Schwachstelle, bei der ein Angreifer eigene Filterzeichen wie eine Klammer oder ein Sternchen in eine Verzeichnisabfrage einschleust, weil die Anwendung Eingabe und Filtersyntax nicht trennt. Mit einer Payload wie *)(uid=* umgeht er die Anmeldung oder liest das gesamte Benutzerverzeichnis aus. Die Lösung ist Escaping nach RFC 4515 plus ein echter LDAP-Bind.

LDAP Injection ist das Verzeichnispendant zur SQL Injection: weniger bekannt, in Unternehmensnetzen aber meist deutlich näher an den Kronjuwelen. Eine Anwendung, die ihren Suchfilter aus Textbausteinen eines Eingabefelds zusammensetzt, überlässt dem Angreifer einen Teil der Frage, die an die Benutzerverwaltung gestellt wird. Im Folgenden lesen Sie, worin die Schwachstelle besteht, wie ein Angriff abläuft und wie Sie sie dauerhaft schließen.

Was ist LDAP Injection?

LDAP Injection ist eine Schwachstelle, bei der ein Angreifer eigene Filterzeichen in eine Verzeichnisabfrage Ihrer Anwendung einschleust. LDAP steht für Lightweight Directory Access Protocol, also für das Protokoll, mit dem Anwendungen ein Verzeichnis wie Active Directory oder OpenLDAP nach Benutzern, Gruppen und Berechtigungen abfragen. Sobald eine Benutzereingabe ungeschützt in einen Suchfilter gelangt, bestimmt der Besucher mit, was tatsächlich gefragt wird.

Ein Vergleich aus dem Alltag: Sie geben der Empfangskraft einen Zettel mit der Bitte, die Personalakte des Mitarbeiters Jansen herauszusuchen. Unterwegs ergänzt jemand die Worte “oder eigentlich von allen”. Die Empfangskraft liest den Satz genau so, wie er dasteht, und rollt den ganzen Karteischrank heran. Ein Verzeichnis verhält sich identisch: Es führt den Filter aus, den es bekommt, ohne zu wissen, welcher Teil aus Ihrer Anwendung stammt und welcher von einem Besucher.

Anfällig macht LDAP dabei die Filtersyntax selbst. Ein Suchfilter besteht aus Klammern, logischen Operatoren wie &, | und !, Vergleichsoperatoren und dem Platzhalter *. Das sind durchweg gewöhnliche Satzzeichen, die jeder ungehindert in einen Namen, eine E-Mail-Adresse oder eine Abteilungsbezeichnung tippen kann. Wer sie nicht neutralisiert, lässt den Benutzer an der Logik der Abfrage mitschreiben statt nur an deren Wert.

Wie funktioniert ein LDAP-Injection-Angriff?

Das Problem entsteht auf die vertraute Weise: Der Filter wird als Zeichenkette zusammengesetzt. Nehmen wir ein Anmeldeformular, das Benutzernamen und Passwort in einen einzigen Filter packt und jeden Treffer als erfolgreiche Anmeldung wertet.

Verwundbar:

// Benutzereingaben werden direkt in den LDAP-Filter eingefügt
const username = req.body.username;
const password = req.body.password;

const filter =
  "(&(uid=" + username + ")(userPassword=" + password + "))";

const treffer = await suche(client, "ou=users,dc=beispiel,dc=de", filter);
if (treffer.length > 0) {
  // Zugriff gewährt
}

Gibt eine Mitarbeiterin ihren Namen und ihr Passwort ein, funktioniert das wie vorgesehen. Ein Angreifer tippt in das Feld für den Benutzernamen jedoch keinen Namen, sondern *)(uid=*))(|(uid=*, dazu ein beliebiges Passwort. Der Filter, den das Verzeichnis am Ende auswertet, lautet dann:

(&(uid=*)(uid=*))(|(uid=*)(userPassword=beliebig))

Der Angreifer hat Ihre Klammern vorzeitig geschlossen und eine eigene Konstruktion darum herum gebaut. Vorn steht nun (&(uid=*)(uid=*)), eine Bedingung, die für jedes Objekt mit einer uid wahr ist. Die Passwortprüfung ist in den Textteil hinter dem ersten vollständigen Filter gerutscht. Viele Verzeichnisserver werten diesen ersten Filter aus und ignorieren den Rest, andere weisen die Anfrage ab. Wo das erste Verhalten gilt, ist die Passwortprüfung schlicht verschwunden: Die Suche liefert Treffer, die Anwendung schließt daraus auf eine gültige Anmeldung, und der Angreifer ist drin, meist als das Konto, das zufällig an erster Stelle der Trefferliste steht.

Auch ohne vollständige Umgehung lohnt sich diese Lücke. Ein einzelnes Sternchen macht aus (uid=jansen) ein (uid=*) und liefert die komplette Mitarbeiterliste in einer einzigen Anfrage. Und mit einem Filter wie (&(uid=admin)(description=a*)) lassen sich Attributwerte Zeichen für Zeichen erraten, solange die Anwendung sichtbar anders auf einen Treffer reagiert als auf null Ergebnisse.

Die strukturelle Lösung besteht aus zwei Teilen. Erstens escapen Sie jeden Wert, der in einen Filter gelangt, nach RFC 4515. Dieser Standard legt fest, wie die Zeichen *, (, ), der Backslash und das Nullbyte innerhalb eines Filterwerts kodiert werden. Zweitens prüfen Sie das Passwort nicht per Suche, sondern per Bind: Sie ermitteln zuerst mit einem Dienstkonto den DN (Distinguished Name) des Benutzers und überlassen dann dem Verzeichnis die Entscheidung, ob das angegebene Passwort zu diesem DN gehört.

Sicher:

// Jeder Wert, der in einen Filter gelangt, wird nach RFC 4515 escaped
function escapeFilterValue(value) {
  return String(value)
    .replace(/\\/g, "\\5c")
    .replace(/\*/g, "\\2a")
    .replace(/\(/g, "\\28")
    .replace(/\)/g, "\\29")
    .replace(/\0/g, "\\00");
}

// 1. Suche mit einem Dienstkonto, ausschliesslich zur Ermittlung des DN
await client.bind(SERVICE_DN, SERVICE_PASSWORT);

const filter =
  "(&(objectClass=person)(uid=" + escapeFilterValue(req.body.username) + "))";

const treffer = await suche(client, "ou=users,dc=beispiel,dc=de", filter);
if (treffer.length !== 1) {
  throw new Error("Anmeldung fehlgeschlagen");
}

// 2. Das Verzeichnis selbst das Passwort prüfen lassen
const password = req.body.password;
if (password.length === 0) {
  throw new Error("Anmeldung fehlgeschlagen");
}

const benutzerClient = createClient();
await benutzerClient.bind(treffer[0].dn, password); // scheitert bei falschem Passwort

Jetzt ist *)(uid=*))(|(uid=* nur noch ein merkwürdiger Benutzername, der auf kein Konto passt: Klammern und Sternchen landen als \28, \29 und \2a im Filter und verändern dessen Struktur nicht. Zusätzlich verlässt das Passwort die Anwendung nie mehr als Bestandteil einer Suche, sodass sich an dieser Stelle nichts mehr manipulieren lässt.

Weisen Sie ein leeres Passwort ausdrücklich zurück, bevor Sie binden. Viele Verzeichnisserver behandeln einen Bind ohne Passwort als anonymen Bind und melden Erfolg, sodass sich Ihre Anmeldeprüfung mit einem leeren Feld weiterhin umgehen lässt.

Welche Auswirkungen hat LDAP Injection?

Der Schweregrad hängt davon ab, was das Dienstkonto sehen darf und was die Anwendung mit dem Ergebnis anstellt. Am unteren Ende steht der Informationsabfluss: Ein Platzhalter liefert das vollständige Mitarbeiterverzeichnis samt E-Mail-Adressen, Telefonnummern, Funktionsbezeichnungen und Organisationsstruktur. Das ist für sich genommen ein Datenschutzvorfall und liefert Angreifern zugleich hervorragendes Material für gezieltes Phishing und Social Engineering.

Am oberen Ende steht die Authentifizierungsumgehung aus dem Beispiel. Wer ohne gültige Zugangsdaten hineinkommt, erbt die Rechte des Kontos, auf das die Anwendung fällt, und das ist bei einem Filter ohne Sortierung häufig willkürlich oder ausgerechnet das erste Administratorkonto. Darüber hinaus kann ein Angreifer Filter zur Gruppenmitgliedschaft manipulieren und sich Berechtigungen verschaffen, die ihm nie zugedacht waren. Liest die Anwendung sensible Attribute aus, geraten auch diese in Reichweite, bis hin zu Passwort-Hashes, sofern das Dienstkonto sie lesen darf.

Seltener, aber keineswegs ausgeschlossen, ist eine Anwendung, die Änderungen an einem DN vornimmt, den der Benutzer beeinflussen kann. Dann verschiebt sich das Risiko vom Lesen zum Schreiben: geänderte Gruppenmitgliedschaften, zurückgesetzte Attribute, manipulierte Kontaktdaten, denen andere Systeme vertrauen. Weil die Spanne vom Auslesen eines Adressbuchs bis zur Übernahme von Konten reicht, bewegt sich der Schweregrad zwischen mittel und hoch.

Wie erkennen Sie LDAP Injection?

Der schnellste Test ist ein einzelnes Sternchen. Geben Sie * in jedes Feld ein, das am Ende ein Verzeichnis abfragt, und achten Sie darauf, ob die Trefferzahl sprunghaft steigt oder Konten auftauchen, die Sie nicht sehen dürften. Senden Sie anschließend eine einzelne öffnende Klammer. Eine Fehlermeldung über einen ungültigen Suchfilter, eine leere Seite oder ein HTTP 500 verrät, dass Ihre Eingabe die Filtersyntax erreicht und nicht als reiner Wert behandelt wird.

# Derselbe Endpunkt, zwei Suchen: ein Name und ein Platzhalter
curl -s "https://app.beispiel.de/suche?name=jansen" | wc -l
curl -s "https://app.beispiel.de/suche?name=%2A" | wc -l

Antwortet die Anwendung ohne Fehlermeldung, arbeitet ein Tester blind weiter: Er vergleicht die Antwort auf (&(uid=jansen)(description=a*)) mit der Antwort auf eine sicher falsche Bedingung und leitet aus dem Unterschied ab, ob der Filter ausgeführt wird. Automatisierte Scanner finden die auffälligen Fälle, übersehen diese Schwachstelle aber regelmäßig, weil LDAP-Fehler in der Anwendungsschicht sauber abgefangen werden und als Signal nur ein Verhaltensunterschied bleibt. Handarbeit ist hier entscheidend, und genau diese Art von Schwachstelle spürt AssistSec bei einem Penetrationstest gezielt auf und weist sie nachvollziehbar nach.

Wie verhindern Sie LDAP Injection?

  • Escapen Sie jeden Filterwert nach RFC 4515 mit der Standardfunktion Ihrer LDAP-Bibliothek und nicht mit einer selbst geschriebenen Variante.
  • Prüfen Sie Passwörter grundsätzlich per Bind, niemals durch einen Vergleich innerhalb eines Suchfilters. Ermitteln Sie den DN, binden Sie anschließend als dieser Benutzer.
  • Setzen Sie Distinguished Names nicht von Hand zusammen. Wenn es sich nicht vermeiden lässt, wenden Sie das Escaping nach RFC 4514 an, das andere Sonderzeichen kennt, darunter Komma, Pluszeichen und Gleichheitszeichen.
  • Validieren Sie Eingaben gegen eine strikte Allowlist. Ein Benutzername oder ein Abteilungskürzel hat ein vorhersagbares Format; weisen Sie alles zurück, was nicht dazu passt.
  • Statten Sie das Dienstkonto mit minimalen Rechten aus. Begrenzen Sie die Search Base, begrenzen Sie die zurückgelieferten Attribute und vergeben Sie Leserechte nur dort, wo sie wirklich nötig sind.
  • Begrenzen Sie die Zahl der Suchtreffer und protokollieren Sie Suchen, die an diese Grenze stoßen; ein Platzhalterangriff fällt dann sofort auf.
  • Geben Sie niemals rohe LDAP-Fehlermeldungen an den Benutzer aus, sondern protokollieren Sie sie intern. Fehlermeldungen verraten einem Angreifer den genauen Aufbau Ihres Filters.
  • Lassen Sie Anmelde- und Suchfunktionen regelmäßig prüfen, mit einem Penetrationstest und einem Code-Review, denn gerade diese Schwachstelle übersteht automatisierte Scans.

Quellen

Häufige Fragen

Was ist der Unterschied zwischen LDAP Injection und SQL Injection?

Die Ursache ist dieselbe: Eingaben, die als Teil eines Befehls gelesen werden statt als Wert. Das Ziel unterscheidet sich. SQL Injection trifft die Datenbank, LDAP Injection das Verzeichnis, in dem Ihre Benutzer, Gruppen und Berechtigungen liegen. Da dieses Verzeichnis meist die zentrale Identitätsquelle des Unternehmens ist, trifft eine erfolgreiche LDAP Injection unmittelbar die Authentifizierung.

Ist LDAP Injection in modernen Anwendungen noch relevant?

Ja. Intranetportale, VPN-Zugänge, Druck- und Ausweissysteme sowie viele ältere Webanwendungen fragen weiterhin direkt Active Directory oder OpenLDAP ab. Die Schwachstelle tritt vor allem in selbst gebauten Such- und Anmeldemasken auf, in denen der Filter per Stringverkettung zusammengesetzt wird.

Reicht Escaping gegen LDAP Injection aus?

Escaping nach RFC 4515 schließt den Suchfilter ab und ist die wichtigste Einzelmaßnahme. Es behebt aber weder eine Anmeldeprüfung, die das Passwort innerhalb eines Filters vergleicht, noch einen Distinguished Name, den Sie selbst zusammensetzen: dafür gelten die abweichenden Regeln von RFC 4514. Kombinieren Sie Escaping deshalb immer mit einer Passwortprüfung per Bind.

Wie teste ich meine Anwendung auf LDAP Injection?

Geben Sie in jedem Feld, das am Ende ein Verzeichnis abfragt, ein einzelnes Sternchen ein und beobachten Sie, ob die Trefferzahl sprunghaft steigt oder unerwartete Konten erscheinen. Senden Sie danach eine einzelne öffnende Klammer: eine Fehlermeldung über einen ungültigen Suchfilter, eine leere Seite oder ein HTTP 500 deutet darauf hin, dass Ihre Eingabe die Filtersyntax erreicht. Bestätigen Sie jeden Verdacht mit einer kontrollierten Payload in einer Testumgebung.

Verwandte Artikel

Zum Suchen / drücken · Esc