~/blog/wordpress-rest-api-user-enumerationPOSTED
von Shady Nathan Tawfik · 9. Mai 2026 · 4 min

WordPress-Benutzer-Enumeration ist nicht der Befund, sie ist der Multiplikator

Eine Steuerberatungskanzlei legte jeden Konto-Slug über ihre öffentliche REST-API offen, lieferte keinerlei Security-Header und liess das Login ohne Rate-Limit laufen.

Interaktiv[blog-2.0]

TL;DR: Die REST-API einer WordPress-Landschaft listete jeden Konto-Slug ohne Authentifizierung auf, und das Login hatte kein Rate-Limit. Ich demonstriere die Enumeration, zeige, warum der Login-Fehlertext sie verschärft, und wie ich den Endpunkt gehärtet habe.

Ausgangslage

Ich habe das Ziel als Black-Box geprüft: keine Zugangsdaten, nur die öffentlich sichtbare Fläche. Das ist, was ich sah, bevor ich etwas angefasst habe.

Eine mittelständische Steuerberatungskanzlei mit einer Standard-WordPress-Installation, aktivem Theme und breitem Plugin-Set. Der Auftrag umfasste Enumerationspfade, Login-Missbrauchsresistenz, Header-Status, Mail-Authentifizierung und Datei-Freigaben.

Ich habe alle identifizierenden Informationen entfernt. Das Ziel wird ausschließlich nach Branche und Technologieklasse beschrieben, damit das Muster übertragbar bleibt.

Das Muster

Das Muster, das ich erkannt habe — und das ich in ähnlichen Landschaften immer wieder finde:

Enumeration wird meist als niedriger informativer Befund geführt, weil sie für sich genommen nichts ausser einem Benutzernamen preisgibt. Diese Einordnung ist falsch. Ein Benutzername ist nicht die Schwachstelle, sondern die Eingabe, die die erste Hürde eines Credential-Angriffs beseitigt.

Die WordPress-REST-API macht das billig, und zwar mit Absicht. Ein einzelnes unauthentifiziertes GET auf die Users-Sammlung liefert eine paginierte Liste mit Slugs, Anzeigenamen, Avataren und bei neueren Versionen auch Rollen-Metadaten. Kein Rate-Limit, keine Authentifizierung, kein Lockout beteiligt.

Der Multiplikator ist der nächste Schritt. Enumeration plus unbeschränktes Login ergibt Credential Stuffing, weil Angreifer bereits Breach-Korpora zu denselben Adressen halten. Enumeration plus fehlende Header bedeutet, dass die Folgeanfragen keinen Browser-Fingerprint tragen.

TL;DR: Eine Steuerberatungskanzlei legte jeden Konto-Slug über ihre öffentliche REST-API offen, lieferte keinerlei Security-Header und liess das Login ohne Rate-Limit laufen.

Befunde

SchweregradBefundNachweis
HOCHKonto-Enumeration über die öffentliche REST-APINeun Konten mit Slugs, Namen und Avatar-Hashes
HOCHKeine Security-Header site-weitKeine Transport-Policy, kein Frameschutz, keine Content-Type-Policy
HOCHLogin-Formular ohne Rate-Limit oder LockoutUnbegrenzte Versuche, keine progressive Verzögerung
MITTELPlugin- und Core-Versionen preisgegebenGenerator-Meta und Asset-Query-Strings
MITTELMail-Policy auf neutral gesetztJeder darf im Namen der Domain senden
NIEDRIGBackup- und Debug-Artefakte vorhersagbarGemeinsame Pfade liefern unterscheidbare Antworten

Der Angriffspfad

Breach corpus of email and password pairsRestrict to discovered usernamesStuff the login form with no lockoutSession cookie issuedEnumerate admin-capable accountsPassword reset abuse

Reproduktion im Labor

Jeder Befehl unten zielt auf einen Lab-Container, den ich selbst kontrolliere. Nichts davon ist auf ein laufendes System gerichtet.

Die öffentliche Users-Sammlung einer Lab-Instanz auslesen

~/code/bash bash
# Ziel: ein Labor-WordPress, das Sie kontrollieren
$ curl -s http://localhost:8080/wp-json/wp/v2/users?per_page=100 \
  | python3 -c 'import json,sys; [print(u["id"], u["slug"]) for u in json.load(sys.stdin)]'

# die Author-Route leakt dieselbe Information
$ curl -so /dev/null -w '%{http_code} %{redirect_url}\n' 'http://localhost:8080/?author=1'

# und das Login-Formular unterscheidet unbekannten Benutzer von falschem Passwort
$ curl -s -d 'log=nosuchuser&pwd=x' http://localhost:8080/wp-login.php | grep -io 'invalid username\|unknown user'

Erkennung in eigener Infrastruktur

Enumeration und Header-Prüfung in einem Durchgang

~/code/bash bash
# read-only header probe against a host you own or are authorised to test
$ curl -sI https://example.org/ | grep -iE \
  'strict-transport|content-security-policy|x-frame-options|x-content-type|referrer-policy|permissions-policy'

# presence check, exit code 0 when every header is present
$ for h in strict-transport-security content-security-policy x-frame-options \
           x-content-type-options referrer-policy; do
    curl -sI https://example.org/ | grep -qi "^$h" && echo "ok   $h" || echo "MISS $h"
  done

Die Mail-Policy prüfen, die Follow-up-Phishing billig macht

~/code/bash bash
# SPF, DKIM and DMARC for a domain you own
$ dig +short TXT example.org | grep -i spf
$ dig +short TXT _dmarc.example.org

# a soft fail (-all is the goal, ~all means anyone can forge)
$ dig +short TXT example.org | grep -oE '[-+~?]all'

Behebung

Anonymen Zugriff auf die Users-Sammlung am Edge blockieren

~/code/nginx nginx
# anonymisierte Enumeration blockieren, den Rest der API offen lassen
location ~* ^/wp-json/wp/v2/users {
    if ($remote_user = "") { return 403; }
    proxy_pass http://backend;
}

# die Author-Weiterleitung gar nicht beantworten
location ~* ^/\?author=[0-9]+ { return 404; }

Login-Fehlerdiffferenz beseitigen und Versuche begrenzen

~/code/ini ini
# wp-config.php: keine Auskunft darüber, ob das Konto existiert
define( 'LOGGED_IN_KEY', 'generate-with-openssl-rand-base64-48' );

# fail2ban-Regel, vom Host durchgesetzt, nicht von der Anwendung
[Definition]
failregex = ^<HOST> .*POST /wp-login\.php.* 401
maxretry = 5
findtime = 600
bantime = 3600

Sobald ein Angreifer Zugriff hat, kann er folgende Aktionen durchführen:

Konto-Enumeration über die öffentliche REST-API
Keine Security-Header site-weit
Login-Formular ohne Rate-Limit oder Lockout
Plugin- und Core-Versionen preisgegeben
Mail-Policy auf neutral gesetzt

Fazit — was ich daraus mitnehme

  • Enumeration danach bewerten, was sie ermöglicht, nicht was sie zurückgibt.
  • Alle drei Routen blockieren: REST-Sammlung, Login-Fehlertext und Author-Weiterleitung.
  • Enumeration ist nur die Hälfte, das Rate-Limit auf dem Login-Formular die andere.
  • Fehlende Mail-Authentifizierung macht jeden erfolgreichen Login zu einer überzeugenden Phishing-Kampagne.

Nur zu Bildungszwecken. Alle reproduzierbaren Befehle zielen auf meine eigene Lab-Umgebung (localhost), nie auf ein laufendes System. © 2026