[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fddXmyJCL811L01ikADejAFkmSZ7ZkZem8PvsS4-PKKU":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":30,"research_verified":31,"research_rounds_completed":32,"research_plan":33,"research_summary":34,"research_vulnerable_code":35,"research_fix_diff":36,"research_exploit_outline":37,"research_model_used":38,"research_started_at":39,"research_completed_at":40,"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":31,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":31,"source_links":41},"CVE-2026-39532","events-calendar-for-geodirectory-authenticated-contributor-php-object-injection-2","Events Calendar for GeoDirectory \u003C= 2.3.25 - Authenticated (Contributor+) PHP Object Injection","The Events Calendar for GeoDirectory plugin for WordPress is vulnerable to PHP Object Injection in versions up to, and including, 2.3.25 via deserialization of untrusted input. This makes it possible for authenticated attackers, with contributor-level access and above, to inject a PHP Object. No known POP chain is present in the vulnerable software. If a POP chain is present via an additional plugin or theme installed on the target system, it could allow the attacker to delete arbitrary files, retrieve sensitive data, or execute code.","events-for-geodirectory",null,"\u003C=2.3.25","2.3.26","high",7.5,"CVSS:3.1\u002FAV:N\u002FAC:H\u002FPR:L\u002FUI:N\u002FS:U\u002FC:H\u002FI:H\u002FA:H","Deserialization of Untrusted Data","2026-04-16 00:00:00","2026-04-21 15:00:32",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F5fc9ecf7-2802-433e-832b-731505c5cb9b?source=api-prod",6,[22,23,24,25,26,27,28,29],"events-for-geodirectory.php","includes\u002Fadmin\u002Fclass-geodir-event-admin-import-export.php","includes\u002Fclass-geodir-event-api.php","includes\u002Fclass-geodir-event-ayi.php","includes\u002Fclass-geodir-event-fields.php","includes\u002Fcore-functions.php","languages\u002Fgeodirevents-en_US.po","languages\u002Fgeodirevents-en_US.pot","researched",false,3,"This plan focuses on a **PHP Object Injection** vulnerability in the **Events Calendar for GeoDirectory** plugin. The vulnerability stems from the use of a dangerous parsing function, `GeoDir_Event_Fields::parse_array()`, which calls `unserialize()` on strings starting with `a:`. This function is used to process various event-related fields when a post is saved or imported.\n\n### 1. Vulnerability Summary\n*   **ID**: CVE-2026-39532\n*   **Type**: PHP Object Injection (Deserialization of Untrusted Data)\n*   **Location**: `includes\u002Fclass-geodir-event-fields.php` (inferred sink) and `includes\u002Fadmin\u002Fclass-geodir-event-admin-import-export.php` (confirmed usage).\n*   **Sink**: `unserialize()` inside `GeoDir_Event_Fields::parse_array()`.\n*   **Condition**: A string passed to `parse_array()` must start with `a:`.\n*   **Access**: Authenticated (Contributor+). Contributors can create and edit their own \"Events\" (or post types that support events), allowing them to trigger the metadata sanitization logic.\n\n### 2. Attack Vector Analysis\n*   **Endpoint**: `wp-admin\u002Fpost.php` (via the standard post save\u002Fedit process).\n*   **Action**: `editpost`.\n*   **Parameter**: Sub-fields of the event data, specifically `recurring_dates`, `start_times`, or `end_times`.\n*   **Authentication**: Required (Contributor, Author, Editor, or Admin).\n*   **Precondition**: The target WordPress site must have a GeoDirectory Custom Post Type (CPT) configured to support \"Events\". This is the default behavior for the plugin's \"Events\" CPT.\n\n### 3. Code Flow\n1.  A Contributor-level user edits or creates a post of a type that supports Events (e.g., the default `gd_event`).\n2.  Upon submission, WordPress triggers the saving process.\n3.  The plugin has registered a filter: `add_filter( 'geodir_custom_field_value_event', array( 'GeoDir_Event_Fields', 'sanitize_event_data' ), 10, 6 );` (see `includes\u002Fclass-geodir-event-fields.php`).\n4.  The `sanitize_event_data` function (inferred) processes the `event_dates` field data.\n5.  Based on the logic seen in `includes\u002Fadmin\u002Fclass-geodir-event-admin-import-export.php`, the plugin utilizes `GeoDir_Event_Fields::parse_array()` to handle inputs that might be arrays or serialized strings.\n6.  `GeoDir_Event_Fields::parse_array( $input )` checks if `$input` is a string and if `strpos( $input, 'a:' ) === 0`.\n7.  If the condition is met, it calls `unserialize( $input )`, triggering the injection.\n\n### 4. Nonce Acquisition Strategy\nThis exploit targets the standard WordPress post-editing flow. \n1.  **Login**: Authenticate as a Contributor.\n2.  **Navigate**: Use `browser_navigate` to go to `wp-admin\u002Fpost-new.php?post_type=gd_event` (replace `gd_event` with the appropriate CPT slug if different).\n3.  **Extract**: Use `browser_eval` to extract the `_wpnonce` from the form and the `post_ID` (if one is already assigned in the hidden inputs).\n    *   `_wpnonce`: `document.querySelector('#_wpnonce').value`\n    *   `post_ID`: `document.querySelector('#post_ID').value`\n\n### 5. Exploitation Strategy\n\n#### Step 1: Identify the CPT and Event Support\nVerify which post types support events using WP-CLI:\n`wp eval \"print_r(GeoDir_Event_Post_Type::get_event_post_types());\"`\n\n#### Step 2: Observe Normal Request\nAs a Contributor, submit a valid \"Event\" and observe the POST structure to `post.php`. Look for how the `event_dates` field is structured. It is likely either top-level parameters or nested within an `event_dates` array.\n\n#### Step 3: Send Injection Payload\nConstruct a POST request to `wp-admin\u002Fpost.php` using the `http_request` tool.\n\n**Example Request (assuming top-level params based on Import mapper):**\n*   **Method**: `POST`\n*   **URL**: `http:\u002F\u002Fvulnerable-site.com\u002Fwp-admin\u002Fpost.php`\n*   **Headers**: `Content-Type: application\u002Fx-www-form-urlencoded`\n*   **Body Parameters**:\n    *   `action`: `editpost`\n    *   `post_ID`: `[EXTRACTED_ID]`\n    *   `_wpnonce`: `[EXTRACTED_NONCE]`\n    *   `recurring`: `1`\n    *   `repeat_type`: `custom`\n    *   `recurring_dates`: `a:1:{i:0;O:8:\"stdClass\":0:{}}` (Payload)\n    *   `start_date`: `2025-01-01`\n    *   `end_date`: `2025-01-01`\n\n**Alternative Payload (if nested):**\nIf Step 2 shows nesting, use `event_dates[recurring_dates]=a:1:{i:0;O:8:\"stdClass\":0:{}}`.\n\n### 6. Test Data Setup\n1.  Ensure \"Events for GeoDirectory\" is active.\n2.  Ensure a Post Type supports events. If not, use WP-CLI:\n    `wp option update geodir_event_post_types '[\"gd_place\"]'` (or similar).\n3.  Create a Contributor user:\n    `wp user create attacker attacker@example.com --role=contributor --user_pass=password`\n\n### 7. Expected Results\n*   The `unserialize()` call will be executed.\n*   If using a simple `stdClass` payload, no visible error may occur, but the application will process the object.\n*   To confirm execution without a POP chain, one can attempt to inject an object of a class that exists but will cause a noticeable error when treated as a string (e.g., `O:20:\"WP_Block_List_Custom\":0:{}` which may cause a fatal error if the plugin expects a date string).\n*   If a POP chain is available in the environment (e.g., via another plugin), it will trigger its magic methods (`__destruct`, `__wakeup`).\n\n### 8. Verification Steps\n1.  Check the `wp-content\u002Fdebug.log` (if `WP_DEBUG` is on) for deserialization errors:\n    `grep \"unserialize\" \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fdebug.log`\n2.  Verify if the metadata was saved (though the goal is the injection during the *process* of saving):\n    `wp post get [ID] --field=event_dates`\n\n### 9. Alternative Approaches\n*   **Import Vector**: If the Contributor has access to the GeoDirectory Import tool (unlikely, usually Admin), use the CSV import feature. Place the serialized payload in the `recurring_custom_dates` column.\n*   **REST API**: The plugin registers REST routes (see `includes\u002Fclass-geodir-event-api.php`). If the REST API update handlers for `gd_event` categories or tags also pass data through the same event field sanitization logic, they could be used as an alternative endpoint via `PATCH \u002Fwp\u002Fv2\u002Fgd_event\u002F[id]`.","The Events Calendar for GeoDirectory plugin is vulnerable to PHP Object Injection due to the insecure use of the maybe_unserialize() function on user-supplied event metadata. Authenticated attackers with contributor-level permissions can exploit this by submitting crafted serialized strings in fields like recurring dates, potentially leading to remote code execution if a suitable POP chain is available on the system.","\u002F\u002F includes\u002Fclass-geodir-event-fields.php:1152\n$event_data = maybe_unserialize( $value );\n$event_data = maybe_unserialize( $event_data ); \u002F\u002F includes\\post_functions.php#296\n\n---\n\n\u002F\u002F includes\u002Fadmin\u002Fclass-geodir-event-admin-import-export.php:236\n$event_data = ! empty( $row['event_dates'] ) ? maybe_unserialize( $row['event_dates'] ) : array();","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fevents-for-geodirectory\u002F2.3.25\u002Fincludes\u002Fadmin\u002Fclass-geodir-event-admin-import-export.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fevents-for-geodirectory\u002F2.3.26\u002Fincludes\u002Fadmin\u002Fclass-geodir-event-admin-import-export.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fevents-for-geodirectory\u002F2.3.25\u002Fincludes\u002Fadmin\u002Fclass-geodir-event-admin-import-export.php\t2026-02-05 16:13:44.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fevents-for-geodirectory\u002F2.3.26\u002Fincludes\u002Fadmin\u002Fclass-geodir-event-admin-import-export.php\t2026-03-11 14:10:50.000000000 +0000\n@@ -233,7 +233,7 @@\n \t\t\t$week_day_nos \t\t\t= self::week_days();\n \n \t\t\tforeach ( $results as $key => $row ) {\n-\t\t\t\t$event_data = ! empty( $row['event_dates'] ) ? maybe_unserialize( $row['event_dates'] ) : array();\n+\t\t\t\t$event_data = ! empty( $row['event_dates'] ) ? geodir_event_maybe_unserialize( $row['event_dates'] ) : array();\n \n \t\t\t\tif ( ! is_array( $event_data ) ) {\n \t\t\t\t\t$event_data = array();\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fevents-for-geodirectory\u002F2.3.25\u002Fincludes\u002Fclass-geodir-event-fields.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fevents-for-geodirectory\u002F2.3.26\u002Fincludes\u002Fclass-geodir-event-fields.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fevents-for-geodirectory\u002F2.3.25\u002Fincludes\u002Fclass-geodir-event-fields.php\t2026-02-05 16:13:44.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fevents-for-geodirectory\u002F2.3.26\u002Fincludes\u002Fclass-geodir-event-fields.php\t2026-03-11 14:10:50.000000000 +0000\n@@ -1123,6 +1123,11 @@\n \t\t\t\t$value = maybe_serialize( $event_data );\n \t\t\t} else if ( is_object( $value ) ) {\n \t\t\t\t$value = '';\n+\t\t\t} else if ( is_serialized( $value ) ) {\n+\t\t\t\t\u002F\u002F Checks if a string contains PHP object.\n+\t\t\t\tif ( geodir_event_is_serialized_object( $value ) ) {\n+\t\t\t\t\t$value = '';\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n@@ -1149,8 +1154,8 @@\n \t\t\treturn $value;\n \t\t}\n \n-\t\t$event_data = maybe_unserialize( $value );\n-\t\t$event_data = maybe_unserialize( $event_data ); \u002F\u002F includes\\post_functions.php#296\n+\t\t$event_data = geodir_event_maybe_unserialize( $value );\n+\t\t$event_data = geodir_event_maybe_unserialize( $event_data ); \u002F\u002F includes\\post_functions.php#296\n \n \t\tif ( isset( $gd_post->recurring ) ) {\n \t\t\t$recurring = ! empty( $gd_post->recurring ) ? true : false;\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fevents-for-geodirectory\u002F2.3.25\u002Fincludes\u002Fcore-functions.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fevents-for-geodirectory\u002F2.3.26\u002Fincludes\u002Fcore-functions.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fevents-for-geodirectory\u002F2.3.25\u002Fincludes\u002Fcore-functions.php\t2023-08-07 14:24:04.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fevents-for-geodirectory\u002F2.3.26\u002Fincludes\u002Fcore-functions.php\t2026-03-11 14:10:50.000000000 +0000\n@@ -733,4 +733,42 @@\n \t}\n \n \treturn $time_format;\n-}\n\\ No newline at end of file\n+}\n+\n+\u002F**\n+ * Checks if a string contains a serialized PHP object.\n+ *\n+ * @since 2.3.26\n+ *\n+ * @param string $data The string to inspect.\n+ * @return bool        True if an object pattern is found.\n+ *\u002F\n+function geodir_event_is_serialized_object( $data ) {\n+\tif ( ! is_string( $data ) || empty( $data ) ) {\n+\t\treturn false;\n+\t}\n+\n+\t$pattern = '\u002F[OC]:[0-9]+:(\\\\\\\"|\")[^\"]+(\\\\\\\"|\"):[0-9]+:[\\{|:]\u002F';\n+\n+\treturn (bool) preg_match( $pattern, $data );\n+}\n+\n+\u002F**\n+ * Unserializes data only if it was serialized.\n+ *\n+ * @since 2.3.26\n+ *\n+ * @param string $data Data that might be unserialized.\n+ * @return mixed Unserialized data can be any type.\n+ *\u002F\n+function geodir_event_maybe_unserialize( $data, $allowed_classes = false ) {\n+\tif ( is_serialized( $data ) ) { \u002F\u002F Don't attempt to unserialize data that wasn't serialized going in.\n+\t\tif ( $allowed_classes !== null ) {\n+\t\t\treturn @unserialize( trim( $data ), array( 'allowed_classes' => $allowed_classes ) );\n+\t\t} else {\n+\t\t\treturn @unserialize( trim( $data ) );\n+\t\t}\n+\t}\n+\n+\treturn $data;\n+}","To exploit this vulnerability, an attacker must have Contributor-level access or higher. The attacker navigates to the post-editing interface (wp-admin\u002Fpost.php) for a post type that supports events (like 'gd_event'). By crafting a POST request to update the event, the attacker can supply a serialized PHP object payload to metadata fields such as 'recurring_dates', 'start_times', or 'end_times'. When the plugin saves the post, it passes these values through the sanitize_event_data function, which triggers maybe_unserialize() on the malicious string. This results in the instantiation of the injected object, allowing the attacker to leverage any available POP chains in the WordPress environment for further exploitation.","gemini-3-flash-preview","2026-04-27 14:27:15","2026-04-27 14:28:11",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","2.3.25","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fevents-for-geodirectory\u002Ftags\u002F2.3.25","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fevents-for-geodirectory.2.3.25.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fevents-for-geodirectory\u002Ftags\u002F2.3.26","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fevents-for-geodirectory.2.3.26.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fevents-for-geodirectory\u002Ftags"]