CVE-2025-14901

Bit Form – Contact Form Plugin <= 2.21.6 - Missing Authorization to Unauthenticated Workflow Replay

mediumMissing Authorization
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
2.21.7
Patched in
1d
Time to patch

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:L
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
Low
Availability

Technical Details

Affected versions<=2.21.6
PublishedJanuary 6, 2026
Last updatedJanuary 7, 2026
Affected pluginbit-form

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 both wp_ajax_ and wp_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) and log_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

  1. Entry Point: A POST request is sent to admin-ajax.php with action=bitforms_trigger_workflow.
  2. 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']);
    
  3. Vulnerable Logic (Inferred from Patch):
    Inside triggerWorkFlow(), 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
    
  4. Processing: The function retrieves the entry_id and log_id from 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.

  1. Identify Script: The plugin usually enqueues its scripts on pages containing a Bit Form.
  2. Create Test Page: wp post create --post_type=page --post_status=publish --post_content='[bitform id="1"]'
  3. 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_workflow
    • entry_id: The ID of the form entry to replay.
    • log_id: The ID of the specific workflow log to trigger.

6. Test Data Setup

  1. Install Plugin: Ensure bit-form version 2.21.6 is installed.
  2. Create a Form: Create a simple contact form (ID 1).
  3. Configure Workflow: Add a "Webhook" or "Email" workflow that triggers upon form submission.
    • For webhooks, use a request catcher (e.g., http://webhook.site/...).
  4. Submit Form: Perform one legitimate submission to generate an entry_id and log_id in the database.
  5. Record IDs: Note the entry_id and log_id from 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

  1. Database Check:
    Use WP-CLI to verify the entry exists:
    wp db query "SELECT * FROM wp_bitforms_entry WHERE id = 123;"
  2. 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).
  3. 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_id and log_id.
  • Parameter Variation: Check if the endpoint accepts workflow_id instead of log_id to 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.
Research Findings
Static analysis — not yet PoC-verified

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

--- a/includes/Admin/Ajax/AjaxHandler.php
+++ b/includes/Admin/Ajax/AjaxHandler.php
@@ -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.