Bit Form – Contact Form Plugin <= 2.21.6 - Missing Authorization to Unauthenticated Workflow Replay
Description
The Bit Form – Contact Form Plugin plugin for WordPress is vulnerable to unauthorized workflow execution due to missing authorization in the triggerWorkFlow function in all versions up to, and including, 2.21.6. This is due to a logic flaw in the nonce verification where the security check only blocks requests when both the nonce verification fails and the user is logged in. This makes it possible for unauthenticated attackers to replay form workflow executions and trigger all configured integrations including webhooks, email notifications, CRM integrations, and automation platforms via the bitforms_trigger_workflow AJAX action granted they can obtain the entry ID and log IDs from a legitimate form submission response.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:LTechnical Details
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2025-14901 ## 1. Vulnerability Summary The **Bit Form – Contact Form Plugin** (<= 2.21.6) contains a logic flaw in its AJAX handler for workflow triggers. The vulnerability exists in the `triggerWorkFlow` function, which is responsible for re-executing configured …
Show full research plan
Exploitation Research Plan - CVE-2025-14901
1. Vulnerability Summary
The Bit Form – Contact Form Plugin (<= 2.21.6) contains a logic flaw in its AJAX handler for workflow triggers. The vulnerability exists in the triggerWorkFlow function, which is responsible for re-executing configured workflows (like webhooks, CRM integrations, or emails) for a specific form entry.
The security check intended to protect this endpoint uses an incorrectly constructed conditional statement. It only terminates the request if both the nonce verification fails and the user is currently logged in. Consequently, unauthenticated users (where is_user_logged_in() returns false) bypass the security check entirely, regardless of the nonce's validity. This allows for unauthorized workflow replay, potentially causing data duplication in CRMs, spamming email notifications, or re-triggering expensive automation webhooks.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
bitforms_trigger_workflow(Registered for bothwp_ajax_andwp_ajax_nopriv_) - Vulnerable Function:
triggerWorkFlow(likely located within the core AJAX controller or workflow handler) - HTTP Method:
POST - Authentication: None (Unauthenticated)
- Preconditions: The attacker must know a valid
entry_id(the ID of a specific form submission) andlog_id(the ID of the workflow execution log). These are often returned in the JSON response of a legitimate form submission or can be guessed/enumerated.
3. Code Flow
- Entry Point: A
POSTrequest is sent toadmin-ajax.phpwithaction=bitforms_trigger_workflow. - Hook Registration: The plugin registers the action:
add_action('wp_ajax_bitforms_trigger_workflow', [$this, 'triggerWorkFlow']); add_action('wp_ajax_nopriv_bitforms_trigger_workflow', [$this, 'triggerWorkFlow']); - Vulnerable Logic (Inferred from Patch):
InsidetriggerWorkFlow(), the code likely contains a check similar to:$nonce = $_POST['nonce']; if (!wp_verify_nonce($nonce, 'bitforms_nonce') && is_user_logged_in()) { wp_send_json_error('Unauthorized', 403); } // Logic proceeds if user is NOT logged in, even if nonce is invalid - Processing: The function retrieves the
entry_idandlog_idfrom the request and instructs the workflow engine to re-run the associated tasks.
4. Nonce Acquisition Strategy
According to the vulnerability description, the nonce check is bypassed for unauthenticated users due to the logic flaw. Therefore, no valid nonce is required for an unauthenticated exploit.
If testing reveals the nonce is still checked in a different way, Bit Form typically localizes its data in a global JS object.
- Identify Script: The plugin usually enqueues its scripts on pages containing a Bit Form.
- Create Test Page:
wp post create --post_type=page --post_status=publish --post_content='[bitform id="1"]' - Browser Extraction:
- Navigate to the page.
- Use
browser_eval("window.bitFormsFront?.nonce")(inferred key name) to extract the token if needed for authenticated testing.
Note: For the primary unauthenticated exploit, simply providing an empty or dummy string for the nonce parameter should suffice.
5. Exploitation Strategy
Step 1: Enumerate/Obtain Entry and Log IDs
A legitimate form submission to admin-ajax.php?action=bitforms_submit_form usually returns the entry_id and execution details in the JSON response.
- Action:
bitforms_submit_form - Response Format:
{"success": true, "data": {"entry_id": 123, ...}}
Step 2: Trigger Workflow Replay
Using the http_request tool, send a forged request to replay the workflow.
- URL:
http://<target>/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Payload:
action=bitforms_trigger_workflow&entry_id=123&log_id=456&nonce=dummy_value - Parameters (Inferred):
action:bitforms_trigger_workflowentry_id: The ID of the form entry to replay.log_id: The ID of the specific workflow log to trigger.
6. Test Data Setup
- Install Plugin: Ensure
bit-formversion 2.21.6 is installed. - Create a Form: Create a simple contact form (ID 1).
- Configure Workflow: Add a "Webhook" or "Email" workflow that triggers upon form submission.
- For webhooks, use a request catcher (e.g.,
http://webhook.site/...).
- For webhooks, use a request catcher (e.g.,
- Submit Form: Perform one legitimate submission to generate an
entry_idandlog_idin the database. - Record IDs: Note the
entry_idandlog_idfrom the response or via database check.
7. Expected Results
- Success Response: The server should return a JSON success message, such as
{"success": true, "data": "Workflow triggered successfully"}. - Side Effect: The configured workflow should execute again. If a webhook was configured, a second request will arrive at the webhook listener with the same data as the first submission.
8. Verification Steps
- Database Check:
Use WP-CLI to verify the entry exists:wp db query "SELECT * FROM wp_bitforms_entry WHERE id = 123;" - Execution Check:
If using a webhook, verify the webhook listener received a second request.
If using email, check the WordPress mail log (if a logging plugin is installed). - Log Check:
Check the plugin's own log table for duplicate entries:wp db query "SELECT * FROM wp_bitforms_workflow_log WHERE entry_id = 123;"
9. Alternative Approaches
If the log_id is difficult to obtain:
- Brute Force/Enumeration: Since IDs are typically auto-incrementing integers, an attacker can iterate through common ranges (e.g., 1-100) for both
entry_idandlog_id. - Parameter Variation: Check if the endpoint accepts
workflow_idinstead oflog_idto trigger all workflows for an entry, rather than a specific logged execution. - Check for Data Leakage: Examine the
bitforms_get_log_details(inferred) AJAX action to see if it allows unauthenticated retrieval of log IDs.
Summary
The Bit Form plugin for WordPress contains a logic flaw in the `triggerWorkFlow` AJAX function that allows unauthenticated users to bypass security checks. Due to an incorrectly structured conditional statement, nonce verification is only enforced when a user is logged in, enabling attackers to replay form workflows such as webhooks and email notifications.
Vulnerable Code
// File: bit-form/includes/Admin/Ajax/AjaxHandler.php (inferred logic) public function triggerWorkFlow() { $nonce = isset($_POST['nonce']) ? sanitize_text_field($_POST['nonce']) : ''; // Vulnerability: Logic flaw only checks nonce if user is logged in // Unauthenticated users (is_user_logged_in() == false) bypass this block if (!wp_verify_nonce($nonce, 'bitforms_nonce') && is_user_logged_in()) { wp_send_json_error(__('Unauthorized', 'bit-form'), 403); } $entry_id = isset($_POST['entry_id']) ? (int) $_POST['entry_id'] : 0; $log_id = isset($_POST['log_id']) ? (int) $_POST['log_id'] : 0; // Logic proceeds to re-trigger workflow integrations based on IDs $this->execute_workflow_replay($entry_id, $log_id); }
Security Fix
@@ -10,7 +10,7 @@ public function triggerWorkFlow() { $nonce = isset($_POST['nonce']) ? sanitize_text_field($_POST['nonce']) : ''; - if (!wp_verify_nonce($nonce, 'bitforms_nonce') && is_user_logged_in()) { + if (!current_user_can('manage_options') || !wp_verify_nonce($nonce, 'bitforms_nonce')) { wp_send_json_error(__('Unauthorized', 'bit-form'), 403); }
Exploit Outline
1. An attacker identifies a page containing a Bit Form and submits a test entry to observe the server's response, which often contains the 'entry_id' and 'log_id'. 2. Alternatively, the attacker can brute-force or enumerate these integer IDs if they are not directly visible. 3. The attacker sends an unauthenticated POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'bitforms_trigger_workflow'. 4. The payload includes the targeted 'entry_id', 'log_id', and a dummy or missing 'nonce'. 5. Because the attacker is not logged in, the logic check (!wp_verify_nonce && is_user_logged_in) evaluates to false, bypassing the 403 error and re-triggering all configured form integrations like webhooks, CRM updates, and email notifications.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.