[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fhAQEBYZ_L1TSiSVG1KVJwkFyXRUthk_mzpDJCK5hyqc":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-25008","ninja-tables-easy-data-table-builder-authenticated-contributor-information-exposure","Ninja Tables – Easy Data Table Builder \u003C= 5.2.5 - Authenticated (Contributor+) Information Exposure","The Ninja Tables – Easy Data Table Builder plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 5.2.5. This makes it possible for authenticated attackers, with Contributor-level access and above, to extract sensitive user or configuration data.","ninja-tables",null,"\u003C=5.2.5","5.2.6","medium",4.3,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:L\u002FI:N\u002FA:N","Exposure of Sensitive Information to an Unauthorized Actor","2026-01-18 00:00:00","2026-05-04 15:24:37",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fbaee1bba-c531-4a6a-8e4d-5c44e3d7e84f?source=api-prod",107,[22,23,24,25,26,27,28,29],"app\u002FHooks\u002FHandlers\u002FAdminMenuHandler.php","app\u002FHooks\u002FHandlers\u002FPublicDataHandler.php","app\u002FHttp\u002FControllers\u002FTablesController.php","app\u002FHttp\u002FRoutes\u002Froutes.php","app\u002FModels\u002FPost.php","app\u002FModules\u002FDataProviders\u002FFluentFormProvider.php","app\u002FModules\u002FDataProviders\u002FNinjaFooTable.php","app\u002FModules\u002FFluentCart\u002FTraits\u002FFluentCartTrait.php","researched",false,3,"This research plan targets **CVE-2026-25008**, an information exposure vulnerability in **Ninja Tables**. The vulnerability exists because the plugin's custom REST API router (defined in `app\u002FHttp\u002FRoutes\u002Froutes.php`) uses a `UserPolicy` that likely only verifies a user is authenticated, failing to enforce high-level administrative capabilities (like `manage_options`) for sensitive data retrieval. This allows **Contributor-level** users to access table configurations and, more critically, the actual data rows stored within those tables.\n\n### 1. Vulnerability Summary\n*   **Vulnerability:** Authenticated (Contributor+) Information Exposure.\n*   **Location:** Custom REST API endpoints registered in `app\u002FHttp\u002FRoutes\u002Froutes.php` and handled by `TablesController` and `TableItemsController`.\n*   **Cause:** Insufficient authorization checks within the `UserPolicy` or the controller methods. While the Admin UI is restricted via `ninja_table_admin_role()` in `AdminMenuHandler.php`, the underlying REST API allows any logged-in user to query table lists and row data.\n\n### 2. Attack Vector Analysis\n*   **Endpoint:** `\u002Fwp-json\u002Fninja-tables\u002Fv1\u002Ftables` and `\u002Fwp-json\u002Fninja-tables\u002Fv1\u002Ftables\u002F{id}\u002Fitem`\n*   **HTTP Method:** `GET`\n*   **Authentication:** Authenticated (Contributor level or higher).\n*   **Precondition:** At least one table must exist in the system (created by an Administrator).\n*   **Required Headers:** `X-WP-Nonce` (standard WordPress REST API nonce).\n\n### 3. Code Flow\n1.  **Route Registration:** In `app\u002FHttp\u002FRoutes\u002Froutes.php`, routes are grouped under `$router->withPolicy('UserPolicy')`.\n    *   `$route->get('\u002F', [TablesController::class, 'index'])` (Prefix: `tables`)\n    *   `$route->get('\u002F', [TableItemsController::class, 'index'])` (Prefix: `tables\u002F{id}\u002Fitem`)\n2.  **Request Handling:** When a Contributor sends a request to `\u002Fwp-json\u002Fninja-tables\u002Fv1\u002Ftables`, the `UserPolicy` is evaluated. If it only checks `is_user_logged_in()`, the request proceeds.\n3.  **Data Retrieval:**\n    *   `TablesController::index()` calls `Post::getPosts()`, which queries the `ninja-table` post type without ownership restrictions.\n    *   `TableItemsController::index()` (implied logic) queries the `ninja_table_items` database table for the given table ID and returns all rows.\n4.  **Sink:** The data is returned as a JSON response via `$this->json($data, 200)` in the controller.\n\n### 4. Nonce Acquisition Strategy\nThe plugin uses the standard WordPress REST API nonce (`wp_rest`). Any authenticated user, including a Contributor, can retrieve this nonce from the default WordPress admin dashboard.\n\n1.  **Login:** Log in as the Contributor user using `browser_login`.\n2.  **Navigate:** Navigate to the WordPress Profile page (`\u002Fwp-admin\u002Fprofile.php`).\n3.  **Extract:** Execute JavaScript to retrieve the nonce from the global `wpApiSettings` object provided by WordPress core.\n    *   **Command:** `browser_eval(\"wpApiSettings.nonce\")`\n4.  **Fallback:** If `wpApiSettings` is not available, the nonce can be extracted from the page source where scripts are localized (searching for `\"nonce\":\"...\"`).\n\n### 5. Exploitation Strategy\n\n#### Step 1: List All Tables\nRetrieve the list of all tables to find the ID of a target table containing sensitive information.\n*   **Endpoint:** `\u002Fwp-json\u002Fninja-tables\u002Fv1\u002Ftables`\n*   **Method:** `GET`\n*   **Headers:**\n    *   `Content-Type: application\u002Fjson`\n    *   `X-WP-Nonce: [EXTRACTED_NONCE]`\n*   **Expected Response:** A JSON object containing an array of tables in the `data` key.\n\n#### Step 2: Extract Table Data\nRetrieve the actual rows from the target table identified in Step 1.\n*   **Endpoint:** `\u002Fwp-json\u002Fninja-tables\u002Fv1\u002Ftables\u002F{TABLE_ID}\u002Fitem`\n*   **Method:** `GET`\n*   **Headers:**\n    *   `Content-Type: application\u002Fjson`\n    *   `X-WP-Nonce: [EXTRACTED_NONCE]`\n*   **Expected Response:** A JSON object containing the values of every row in the table, potentially exposing PII or internal configurations.\n\n#### Step 3: Leak User Information (Optional\u002FAlternative)\nCheck if author information is exposed.\n*   **Endpoint:** `\u002Fwp-json\u002Fninja-tables\u002Fv1\u002Fwp-posts\u002Fauthors`\n*   **Method:** `GET`\n*   **Headers:** `X-WP-Nonce: [EXTRACTED_NONCE]`\n\n### 6. Test Data Setup\n1.  **Administrator Action:**\n    *   Create a table named \"Sensitive Customer Data\".\n    *   Add columns: \"Name\", \"Email\", \"Phone\".\n    *   Insert a row: `{\"Name\": \"John Doe\", \"Email\": \"john@secret.com\", \"Phone\": \"555-0199\"}`.\n2.  **Attacker Action:**\n    *   Create a user with the **Contributor** role.\n\n### 7. Expected Results\n*   The Contributor user, who should not have access to Ninja Tables data, successfully receives a `200 OK` response from the REST endpoints.\n*   The JSON response contains the private table's title, settings, and row values (e.g., `john@secret.com`).\n\n### 8. Verification Steps\n1.  **Verify via WP-CLI:** Check the database directly to confirm the table ID and content match the leaked data.\n    *   `wp post list --post_type=ninja-table`\n    *   `wp db query \"SELECT * FROM wp_ninja_table_items WHERE table_id = [ID]\"`\n2.  **Check Permissions:** Confirm the Contributor user lacks the `manage_options` capability.\n    *   `wp user cap list [USER_ID]`\n\n### 9. Alternative Approaches\nIf the `ninja-tables\u002Fv1` namespace is different in the specific version:\n1.  Check `app\u002FHooks\u002FHandlers\u002FAdminMenuHandler.php` inside `getRestInfo()`:\n    ```php\n    $ns  = $app->config->get('app.rest_namespace'); \u002F\u002F Check config\u002Fapp.php or equivalent\n    $ver = $app->config->get('app.rest_version');\n    ```\n2.  Search for `register_rest_route` calls in the entire codebase if the custom router is not used for all versions.\n3.  Test the shortcode-based info exposure in `PublicDataHandler::tableInfoShortcode` by creating a post as Contributor and inserting `[ninja_table_info id=\"[ID]\" field=\"last_editor\"]` to see if it bypasses the `is_preview()` check.","The Ninja Tables plugin for WordPress is vulnerable to information exposure because its REST API and internal shortcode handlers lack adequate authorization checks beyond basic authentication. Authenticated users with Contributor-level access or higher can exploit this to list all table configurations and extract sensitive row data, including PII or internal configurations.","\u002F\u002F app\u002FHttp\u002FRoutes\u002Froutes.php\n$router->withPolicy('UserPolicy')->group(function ($router) {\n    $router->prefix('tables')->group(function ($route) {\n        $route->get('\u002F', [TablesController::class, 'index']);\n        \u002F\u002F ...\n        $route->prefix('\u002F{id}')->group(function ($route) {\n            \u002F\u002F ...\n            $route->prefix('\u002Fitem')->group(function ($route) {\n                $route->get('\u002F', [TableItemsController::class, 'index'])->int('id');\n\n---\n\n\u002F\u002F app\u002FHttp\u002FControllers\u002FTablesController.php, lines 25-33\n$args = array(\n    'posts_per_page' => $perPage,\n    'offset'         => $skip,\n    'orderby'        => Sanitizer::sanitizeTextField(Arr::get($request->all(), 'orderBy')),\n    'order'          => Sanitizer::sanitizeTextField(Arr::get($request->all(), 'order')),\n    'post_type'      => $this->cptName,\n    'post_status'    => 'any',\n);\n\n---\n\n\u002F\u002F app\u002FHooks\u002FHandlers\u002FPublicDataHandler.php, lines 105-110\n$id = absint($id);\n$table = get_post($id);\nif (!$table) {\n    return;\n}","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fninja-tables\u002F5.2.5\u002Fapp\u002FHooks\u002FHandlers\u002FAdminMenuHandler.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fninja-tables\u002F5.2.6\u002Fapp\u002FHooks\u002FHandlers\u002FAdminMenuHandler.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fninja-tables\u002F5.2.5\u002Fapp\u002FHooks\u002FHandlers\u002FAdminMenuHandler.php\t2025-12-17 05:18:32.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fninja-tables\u002F5.2.6\u002Fapp\u002FHooks\u002FHandlers\u002FAdminMenuHandler.php\t2026-01-27 13:17:08.000000000 +0000\n@@ -290,7 +290,9 @@\n         $cptName             = 'ninja-table';\n         $tableCount          = wp_count_posts($cptName);\n         $totalPublishedTable = 0;\n+\t$totalTrashedTable   = 0;\n         $publish             = property_exists($tableCount, \"publish\") ? $tableCount->publish : 0;\n+\t$trash               = property_exists($tableCount, \"trash\") ? $tableCount->trash : 0;\n \n         if ($tableCount && $publish > 1) {\n             $leadStatus = $app->applyFilters('ninja_tables_show_lead', $leadStatus);\n@@ -304,6 +306,10 @@\n             $totalPublishedTable = $publish;\n         }\n \n+        if ($tableCount && $trash > 0) {\n+            $totalTrashedTable = $trash;\n+        }\n+\n         $hasFluentFrom       = defined('FLUENTFORM_VERSION');\n         $isFluentFromUpdated = false;\n         $hasTablePress       = defined('TABLEPRESS_ABSPATH');\n@@ -363,6 +369,7 @@\n             'hasValidLicense'          => get_option('_ninjatables_pro_license_status'),\n             'i18n'                     => I18nStrings::getStrings(),\n             'published_tables'         => $totalPublishedTable,\n+            'trashed_tables'           => $totalTrashedTable,\n             'preview_required_scripts' => array(\n                 $assets . \"css\u002Fninjatables-public.css\",\n                 $assets . \"libs\u002Ffootable\u002Fjs\u002Ffootable.min.js\",\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fninja-tables\u002F5.2.5\u002Fapp\u002FHooks\u002FHandlers\u002FPublicDataHandler.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fninja-tables\u002F5.2.6\u002Fapp\u002FHooks\u002FHandlers\u002FPublicDataHandler.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fninja-tables\u002F5.2.5\u002Fapp\u002FHooks\u002FHandlers\u002FPublicDataHandler.php\t2025-12-17 05:18:32.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fninja-tables\u002F5.2.6\u002Fapp\u002FHooks\u002FHandlers\u002FPublicDataHandler.php\t2026-01-27 13:17:08.000000000 +0000\n@@ -86,8 +86,9 @@\n \n         $id = absint($id);\n         $table = get_post($id);\n-\t\tif (!$table) {\n-\t\t\treturn;\n+\n+        if (!$table || $table->post_type !== 'ninja-table' || $table->post_status !== 'publish') {\n+            return 'Error: Invalid table ID.';\n         }\n \n         $validFields = [\n@@ -169,6 +170,11 @@\n         }\n \n         $id = absint($id);\n+\n+        if (get_post_type($id) !== 'ninja-table' || get_post_status($id) !== 'publish') {\n+            return 'Sorry! The table is not available.';\n+        }\n+\n         $tableSettings = ninja_table_get_table_settings($id, 'public');\n \n         if ($row_id) {\n@@ -281,7 +287,7 @@\n         }\n         $table = get_post($table_id);\n \n-        if (!$table || $table->post_type != 'ninja-table') {\n+        if (!$table || $table->post_type !== 'ninja-table' || $table->post_status !== 'publish') {\n             return;\n         }\n \n@@ -362,7 +368,7 @@\n \n         $table = get_post($table_id);\n \n-        if (!$table || $table->post_type != 'ninja-table') {\n+        if (!$table || $table->post_type !== 'ninja-table' || $table->post_status !== 'publish') {\n             return;\n         }\n \n@@ -50,10 +52,10 @@\n \n     public function store(Request $request)\n     {\n-        if ( ! Sanitizer::sanitizeTextField(Arr::get($request->all(), 'post_title'))) {\n-            $this->sendError(array(\n-                'message' => __('The name field is required.', 'ninja-tables')\n-            ), 423);\n+        if (empty($request->get('post_title'))) {\n+            wp_send_json([\n+                'message' => __('Title is required', 'ninja-tables')\n+            ], 422);\n         }","The exploit leverages the plugin's custom REST API endpoints, which are protected by a 'UserPolicy' that only checks if a user is authenticated rather than enforcing specific administrative roles. \n\n1. Authentication: Log in to the target WordPress site with Contributor-level credentials.\n2. Nonce Retrieval: Obtain a valid WordPress REST API nonce (standard `_wpnonce`) from the admin dashboard (e.g., by checking `wpApiSettings.nonce` in the browser console).\n3. Table Discovery: Send a GET request to `\u002Fwp-json\u002Fninja-tables\u002Fv1\u002Ftables` with the `X-WP-Nonce` header. The server will return a JSON list of all defined tables, regardless of the user's permissions, including table IDs and titles.\n4. Data Extraction: For each discovered table ID, send a GET request to `\u002Fwp-json\u002Fninja-tables\u002Fv1\u002Ftables\u002F{ID}\u002Fitem`. The server responds with the complete set of row data for that table, exposing any information stored within.","gemini-3-flash-preview","2026-05-05 06:40:12","2026-05-05 06:40:46",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","5.2.5","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fninja-tables\u002Ftags\u002F5.2.5","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fninja-tables.5.2.5.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fninja-tables\u002Ftags\u002F5.2.6","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fninja-tables.5.2.6.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fninja-tables\u002Ftags"]