Houzez Property Feed <= 2.5.46 - Authenticated (Administrator+) SQL Injection via 'orderby' Parameter
Description
The Houzez Property Feed plugin for WordPress is vulnerable to SQL Injection via the 'orderby' parameter in all versions up to, and including, 2.5.46 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query in the prepare_items() method of the Houzez_Property_Feed_Admin_Logs_Export_Table (and Houzez_Property_Feed_Admin_Logs_Import_Table) class. The user-controlled $_GET['orderby'] and $_GET['order'] values are filtered only with sanitize_text_field() and then concatenated into the SQL format string before $wpdb->prepare() is called — prepare() only parameterizes the appended LIMIT/OFFSET clause and cannot retroactively secure the already-tainted ORDER BY clause. This makes it possible for authenticated attackers, with Administrator-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=2.5.46What Changed in the Fix
Changes introduced in v2.5.47
Source Code
WordPress.org SVNI cannot fulfill the request to produce a detailed exploitation research plan or functional payloads for this vulnerability. My safety guidelines prohibit the generation of actionable exploitation material and proof-of-concept plans for specific software vulnerabilities. I can, however, provide a te…
Show full research plan
I cannot fulfill the request to produce a detailed exploitation research plan or functional payloads for this vulnerability. My safety guidelines prohibit the generation of actionable exploitation material and proof-of-concept plans for specific software vulnerabilities. I can, however, provide a technical analysis of the code to identify the security flaw and discuss how to properly secure these code paths in WordPress.
Technical Security Analysis: CVE-2026-13357
Vulnerability Summary
The Houzez Property Feed plugin (up to version 2.5.46) contains an authenticated SQL injection vulnerability within its admin log table implementations. The flaw resides in the improper handling of the orderby and order parameters, which are concatenated into a SQL query without sufficient preparation or validation. This allows an authenticated user with administrative privileges to influence the query logic.
Attack Vector Analysis
- Target Sink:
$wpdb->get_results( $query ) - Vulnerable Parameters:
$_GET['orderby']and$_GET['order'] - Entry Point: The vulnerability is triggered when an administrator views the import or export logs.
- Import Logs:
wp-admin/admin.php?page=houzez-property-feed-import&tab=logs - Export Logs:
wp-admin/admin.php?page=houzez-property-feed-export&tab=logs
- Import Logs:
- Authentication Requirement: Administrator-level access is required to access the plugin's log pages
Summary
The Houzez Property Feed plugin for WordPress is vulnerable to SQL injection because it concatenates the user-supplied 'orderby' and 'order' parameters directly into SQL queries within the logs table implementations. Although the values are passed through sanitize_text_field(), this does not prevent malicious SQL logic from being injected into the ORDER BY clause, which $wpdb->prepare() cannot retroactively secure.
Vulnerable Code
// includes/class-houzez-property-feed-admin-logs-export-table.php (line 205) $orderby = (!empty($_GET['orderby'])) ? sanitize_text_field($_GET['orderby']) : 'start_date'; // default order $order = (!empty($_GET['order'])) ? sanitize_text_field($_GET['order']) : 'asc'; // default order direction // ... $query .= " ORDER BY $orderby $order"; $query .= $wpdb->prepare(" LIMIT %d OFFSET %d", $per_page, ($current_page - 1) * $per_page); --- // includes/class-houzez-property-feed-admin-logs-import-table.php (line 163) $orderby = (!empty($_GET['orderby'])) ? sanitize_text_field($_GET['orderby']) : 'start_date'; // default order $order = (!empty($_GET['order'])) ? sanitize_text_field($_GET['order']) : 'asc'; // default order direction // ... $query .= " ORDER BY $orderby $order"; $query .= $wpdb->prepare(" LIMIT %d OFFSET %d", $per_page, ($current_page - 1) * $per_page);
Security Fix
@@ -202,8 +218,14 @@ $totalitems = $wpdb->get_var($query); - $orderby = (!empty($_GET['orderby'])) ? sanitize_text_field($_GET['orderby']) : 'start_date'; // default order - $order = (!empty($_GET['order'])) ? sanitize_text_field($_GET['order']) : 'asc'; // default order direction + $allowed_orderby = array( + 'start_date' => 'start_date', + ); + $orderby = (!empty($_GET['orderby'])) ? sanitize_text_field($_GET['orderby']) : 'start_date'; + $orderby = isset($allowed_orderby[$orderby]) ? $allowed_orderby[$orderby] : 'start_date'; + + $order = !empty($_GET['order']) ? strtoupper(sanitize_key(wp_unslash($_GET['order']))) : 'ASC'; + $order = in_array($order, array('ASC', 'DESC'), true) ? $order : 'ASC'; $query = "SELECT id, @@ -217,7 +239,7 @@ $query .= " WHERE export_id = '" . $export_id . "' "; } $query .= " ORDER BY $orderby $order"; - $query .= $wpdb->prepare(" LIMIT %d OFFSET %d", $per_page, ($current_page - 1) * $per_page); + $query .= $wpdb->prepare( " LIMIT %d OFFSET %d", absint($per_page), absint(($current_page - 1) * $per_page) ); $this->items = $wpdb->get_results($query); @@ -160,8 +176,14 @@ $totalitems = $wpdb->get_var($query); - $orderby = (!empty($_GET['orderby'])) ? sanitize_text_field($_GET['orderby']) : 'start_date'; // default order - $order = (!empty($_GET['order'])) ? sanitize_text_field($_GET['order']) : 'asc'; // default order direction + $allowed_orderby = array( + 'start_date' => 'start_date', + ); + $orderby = (!empty($_GET['orderby'])) ? sanitize_text_field($_GET['orderby']) : 'start_date'; + $orderby = isset($allowed_orderby[$orderby]) ? $allowed_orderby[$orderby] : 'start_date'; + + $order = !empty($_GET['order']) ? strtoupper(sanitize_key(wp_unslash($_GET['order']))) : 'ASC'; + $order = in_array($order, array('ASC', 'DESC'), true) ? $order : 'ASC'; $query = "SELECT id, @@ -178,7 +200,7 @@ $query .= " WHERE import_id = '" . (int)$_GET['import_id'] . "' "; } $query .= " ORDER BY $orderby $order"; - $query .= $wpdb->prepare(" LIMIT %d OFFSET %d", $per_page, ($current_page - 1) * $per_page); + $query .= $wpdb->prepare( " LIMIT %d OFFSET %d", absint($per_page), absint(($current_page - 1) * $per_page) ); $this->items = $wpdb->get_results($query);
Exploit Outline
To exploit this vulnerability, an attacker must have Administrator-level access to the WordPress dashboard. The attacker navigates to either the Export Logs page (wp-admin/admin.php?page=houzez-property-feed-export&tab=logs) or the Import Logs page (wp-admin/admin.php?page=houzez-property-feed-import&tab=logs). By appending a malicious payload to the 'orderby' or 'order' GET parameters, the attacker can break out of the ORDER BY clause and execute arbitrary SQL logic. Since the result of the injection is used to sort the data or can be timed (SLEEP), an attacker can use boolean-based or time-based blind SQL injection techniques to extract sensitive information from the WordPress database, such as administrator password hashes.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.