[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fByCYBxAEKPMPvCmL6Rnt5PwdwTLe8JMPfYcTjgM1WuI":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-39474","post-duplicator-authenticated-contributor-php-object-injection","Post Duplicator \u003C= 3.0.10 - Authenticated (Contributor+) PHP Object Injection","The Post Duplicator plugin for WordPress is vulnerable to PHP Object Injection in versions up to, and including, 3.0.10 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.","post-duplicator",null,"\u003C=3.0.10","3.0.11","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-13 00:00:00","2026-04-21 15:04:30",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fa10dc207-eb41-407e-a85c-ae5ea4c5d972?source=api-prod",9,[22,23,24,25,26,27],"assets\u002Fbuild\u002FpostDuplicator-rtl.css","assets\u002Fbuild\u002FpostDuplicator.asset.php","assets\u002Fbuild\u002FpostDuplicator.css","includes\u002Fapi.php","m4c-postduplicator.php","readme.txt","researched",false,3,"# Exploit Research Plan - CVE-2026-39474\n\n## 1. Vulnerability Summary\nThe **Post Duplicator** plugin (\u003C= 3.0.10) for WordPress is vulnerable to **PHP Object Injection** due to the unsafe use of `maybe_unserialize()` on post metadata within its REST API endpoints. \n\nThe vulnerability exists in `includes\u002Fapi.php` within the `get_post_data()` function. This function iterates through all custom fields (metadata) associated with a post and calls `maybe_unserialize()` on any value that satisfies `is_serialized()`. An authenticated user with **Contributor** level access can create a post, attach a malicious serialized PHP object as metadata, and then trigger the deserialization by accessing the plugin's REST API endpoint for that post.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `GET \u002Fwp-json\u002Fpost-duplicator\u002Fv1\u002Fpost-data\u002F(?P\u003Cid>\\d+)`\n- **Vulnerable Parameter:** The values of post metadata (retrieved via `get_post_custom()`) for the post specified by the `id` parameter.\n- **Authentication:** Authenticated (Contributor+). The user must have permission to duplicate the post, which typically includes any post they have authored.\n- **Preconditions:** \n    1. The attacker must be able to add or edit post metadata (standard for Contributors on their own posts).\n    2. The attacker must obtain a valid `wp_rest` nonce to interact with the WordPress REST API.\n\n## 3. Code Flow\n1. **Entry Point:** The REST route `post-duplicator\u002Fv1\u002Fpost-data\u002F(?P\u003Cid>\\d+)` is registered in `includes\u002Fapi.php` (line 12) with the callback `Mtphr\\PostDuplicator\\get_post_data`.\n2. **Permission Check:** `get_post_data_permissions` (line 80) is called. It verifies the post exists and checks `user_can_duplicate($post)`.\n3. **Data Retrieval:** `get_post_data` (line 104) is executed. It calls `get_post_custom($post_id)` (line 143) to retrieve all metadata for the post.\n4. **Iteration:** The code loops through each meta key and value (lines 146-156).\n5. **Vulnerable Logic:**\n   - At line 163, it checks: `if ( is_serialized( $value ) )`.\n   - If true, at line 165, it calls: `$unserialized = maybe_unserialize( $value );`.\n6. **Sink:** `maybe_unserialize()` (a wrapper for `unserialize()`) processes the untrusted string from the database, triggering the PHP Object Injection.\n7. **Response:** The code then checks `is_object($unserialized)` (line 169) and, if true, sets the `type` to `'object'` and JSON-encodes the object for the response (line 171).\n\n## 4. Nonce Acquisition Strategy\nThe REST API endpoint requires a `wp_rest` nonce for authenticated requests. \n1. **Identify Script Loading:** The plugin enqueues its duplication interface scripts on the post list page (`edit.php`) for supported post types.\n2. **Access Admin Area:** Log in as the Contributor and navigate to `\u002Fwp-admin\u002Fedit.php`.\n3. **Extract Nonce:** Use `browser_eval` to extract the WordPress standard REST nonce from the `wpApiSettings` object:\n   - **JS Variable:** `window.wpApiSettings.nonce`\n   - **Alternative:** If the plugin localizes its own nonce, it would likely be found in a variable like `window.postDuplicatorData.nonce` (though standard `wp_rest` is used by the `register_rest_route` default).\n\n## 5. Exploitation Strategy\n1. **Setup Post:** Create a post as the Contributor and add a meta field containing a serialized object.\n2. **Obtain Nonce:** Extract the `wp_rest` nonce from the WordPress dashboard.\n3. **Trigger Exploit:** Send a GET request to the vulnerable REST endpoint.\n\n### Step-by-Step Execution:\n1. **Login:** Log in as a Contributor user.\n2. **Inject Metadata:** Use WP-CLI to add a serialized object to a post owned by the Contributor:\n   ```bash\n   wp post meta add \u003CPOST_ID> \"exploit_field\" 'O:8:\"stdClass\":0:{}'\n   ```\n3. **Capture Nonce:**\n   - Navigate to `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fedit.php`.\n   - Run `browser_eval(\"window.wpApiSettings.nonce\")`.\n4. **Send Request:**\n   - Use `http_request` to call the endpoint:\n     - **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-json\u002Fpost-duplicator\u002Fv1\u002Fpost-data\u002F\u003CPOST_ID>`\n     - **Headers:** `X-WP-Nonce: \u003CNONCE_VALUE>`\n     - **Method:** `GET`\n5. **Analyze Response:** Observe the `customMeta` array in the JSON response.\n\n## 6. Test Data Setup\n- **User:** Role `contributor`, username `exploit_user`.\n- **Post:** A standard `post` created by `exploit_user`.\n- **Metadata:** \n  - Key: `test_object_injection`\n  - Value: `O:8:\"stdClass\":0:{}` (A simple standard class object).\n\n## 7. Expected Results\n- **Success Indicator:** The REST API response (200 OK) will contain a JSON object. Inside the `customMeta` array, an entry for `test_object_injection` will have:\n  - `\"type\": \"object\"`\n  - `\"isSerialized\": true`\n  - `\"value\": \"{}\"`\n- **Verification:** The fact that the `type` returned is `\"object\"` confirms that `maybe_unserialize()` successfully transformed the string `'O:8:\"stdClass\":0:{}'` into a PHP object. If it had failed or not run, the type would be `\"string\"`.\n\n## 8. Verification Steps\n1. **Check Logs:** Check the PHP error logs for any notices or errors if using a more complex (non-existent) class (e.g., `O:15:\"NonExistentClass\":0:{}`).\n2. **Response Inspection:** Verify the JSON response structure:\n   ```json\n   {\n     \"taxonomies\": [...],\n     \"customMeta\": [\n       {\n         \"key\": \"test_object_injection\",\n         \"value\": \"{}\",\n         \"type\": \"object\",\n         \"isSerialized\": true,\n         \"originalValue\": \"O:8:\\\"stdClass\\\":0:{}\"\n       }\n     ]\n   }\n   ```\n3. **Database Confirmation:** Use `wp post meta get \u003CPOST_ID> test_object_injection` to confirm the raw string in the database matches the injected payload.\n\n## 9. Alternative Approaches\nIf the `post-data` endpoint is inaccessible, the `post-full-data` endpoint (also in `api.php`) follows a similar logic and can be used as a fallback:\n- **Endpoint:** `GET \u002Fwp-json\u002Fpost-duplicator\u002Fv1\u002Fpost-full-data\u002F(?P\u003Cid>\\d+)`\n- **Logic:** Calls `get_post_full_data`, which likely also processes custom meta to provide a full preview of the post being duplicated.","The Post Duplicator plugin for WordPress is vulnerable to PHP Object Injection in versions up to 3.0.10. This occurs because the plugin's REST API endpoints unsafeley call `maybe_unserialize()` on post metadata, allowing authenticated attackers with Contributor-level access to trigger deserialization of malicious payloads stored in custom fields.","\u002F\u002F includes\u002Fapi.php lines 143-176\n$custom_fields = get_post_custom( $post_id );\n$excluded_meta_keys = get_excluded_meta_keys();\n\nforeach ( $custom_fields as $key => $values ) {\n  \u002F\u002F ...\n  foreach ( $values as $value ) {\n    \u002F\u002F Detect data type\n    $type = 'string';\n    $is_serialized = false;\n    $original_value = $value;\n    \n    \u002F\u002F Check if serialized\n    if ( is_serialized( $value ) ) {\n      $is_serialized = true;\n      $unserialized = maybe_unserialize( $value );\n      if ( is_array( $unserialized ) ) {\n        $type = 'array';\n        $value = wp_json_encode( $unserialized, JSON_PRETTY_PRINT );\n      } elseif ( is_object( $unserialized ) ) {\n        $type = 'object';\n        $value = wp_json_encode( $unserialized, JSON_PRETTY_PRINT );\n      } else {\n        $type = 'string';\n      }\n    }\n\u002F\u002F ...","--- includes\u002Fapi.php\n+++ includes\u002Fapi.php\n@@ -162,15 +162,7 @@\n       \u002F\u002F Check if serialized\n       if ( is_serialized( $value ) ) {\n         $is_serialized = true;\n-        $unserialized = maybe_unserialize( $value );\n-        if ( is_array( $unserialized ) ) {\n-          $type = 'array';\n-          $value = wp_json_encode( $unserialized, JSON_PRETTY_PRINT );\n-        } elseif ( is_object( $unserialized ) ) {\n-          $type = 'object';\n-          $value = wp_json_encode( $unserialized, JSON_PRETTY_PRINT );\n-        } else {\n-          $type = 'string';\n-        }\n+        $type = 'serialized';\n+        $value = $value;\n       } elseif ( is_numeric( $value ) ) {","1. An attacker logs into WordPress with at least Contributor-level permissions.\n2. The attacker creates a new post or selects one they already own.\n3. Using the standard WordPress interface or WP-CLI, the attacker adds a custom meta field (metadata) to the post containing a malicious serialized PHP object.\n4. The attacker obtains a valid REST API nonce (standard `wp_rest` nonce) from the WordPress admin dashboard (e.g., from the `wpApiSettings` JavaScript object).\n5. The attacker sends a GET request to the REST API endpoint `\u002Fwp-json\u002Fpost-duplicator\u002Fv1\u002Fpost-data\u002F\u003CPOST_ID>` (or `\u002Fwp-json\u002Fpost-duplicator\u002Fv1\u002Fpost-full-data\u002F\u003CPOST_ID>`), passing the nonce in the `X-WP-Nonce` header.\n6. The plugin's `get_post_data` function retrieves the post's metadata and calls `maybe_unserialize()` on the malicious payload, triggering the PHP Object Injection.","gemini-3-flash-preview","2026-04-27 14:53:23","2026-04-27 14:54:19",{"type":40,"vulnerable_version":41,"fixed_version":11,"vulnerable_browse":42,"vulnerable_zip":43,"fixed_browse":44,"fixed_zip":45,"all_tags":46},"plugin","3.0.10","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fpost-duplicator\u002Ftags\u002F3.0.10","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fpost-duplicator.3.0.10.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fpost-duplicator\u002Ftags\u002F3.0.11","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fpost-duplicator.3.0.11.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fpost-duplicator\u002Ftags"]