- Vulnerability Background
CVE-2025-14901 is an authorization bypass in the Bit Form – Contact Form Plugin for WordPress. The issue resides in the plugin’s workflow trigger endpoint, specifically the bitforms_trigger_workflow AJAX action implemented in includes/Frontend/Ajax/FrontendAjax.php.
What is this vulnerability?
- The endpoint was intended to validate a nonce/token before executing configured form workflows.
- Due to a flawed conditional, the nonce check was only enforced when the requester was logged in.
- Unauthenticated requests could bypass the check entirely.
Why is it critical?
- The workflow execution path can invoke webhooks, email notifications, CRM integrations, and automation platforms.
- Unauthorized execution allows attackers to trigger these integrations without legitimate form submission.
- This can result in spam, unsolicited data propagation, and abuse of downstream systems.
Affected systems and versions:
- Bit Form – Contact Form Plugin for WordPress
- All versions up to and including 2.21.6
- Fixed in 2.21.7 or later
- Technical Details
Root cause analysis
- The vulnerable code used this authorization check:
if (!wp_verify_nonce($request->token, $request->id) && is_user_logged_in()) - The condition combines nonce validation failure with
is_user_logged_in()using logical AND. - For unauthenticated users,
is_user_logged_in()is false, so the condition never triggers, even if the nonce is invalid or absent. - As a result, unauthenticated requests were permitted to continue to workflow execution.
Attack vector and exploitation conditions
- The attacker must know or obtain:
- the form ID
- the workflow trigger token
- the entry ID
- the log ID
- These values can be exposed in legitimate form submission responses.
- The attacker issues a POST to
wp-admin/admin-ajax.phpwith:action=bitforms_trigger_workflowid=<formID>token=<request token>cronNotOk[0]=<entryID>cronNotOk[1]=<logID>
- No authenticated WordPress session is required for the vulnerable path.
Security implications
- Unauthorized workflow execution can:
- send emails or notifications without user consent
- post data to external webhooks
- create or update CRM records
- trigger automation workflows in third-party systems
- The bug effectively turns a public endpoint into a replayable command channel for configured integrations.
- Patch Analysis
What code changes were made?
- The broken nonce/authentication condition was removed from
includes/Frontend/Ajax/FrontendAjax.php. - The new logic calls
Helpers::validateWorkflowTriggerToken($request, $formID)and rejects requests when this helper returns invalid. - The endpoint now returns a 403 JSON error on invalid validation.
- In addition, validation of the
cronNotOkarray was added:- check that both indices exist
- ensure values are numeric
- sanitize values with
absint()
How do these changes fix the vulnerability?
- The patch removes the flawed conditional that bypassed authorization for unauthenticated requests.
- By delegating validation to
Helpers::validateWorkflowTriggerToken, the endpoint can enforce token validation regardless of login state. - Explicit input validation prevents malformed or tampered entry/log IDs from being used to trigger workflow execution.
Security improvements introduced
- consistent token validation for all requesters
- explicit rejection of invalid requests with 403
- stronger input validation and sanitization for entry/log identifiers
- likely improved logging of invalid workflow trigger attempts
Additional note
- The patch also included dependency metadata changes (e.g.,
enshrined/svg-sanitizeversion bump from 0.19.0 to 0.22.0 invendor/composer/installed.php). This is a separate improvement addressing SVG sanitization library security, but the core CVE fix is in the AJAX workflow trigger logic.
- Proof of Concept (PoC) Guide
Prerequisites
- target site running Bit Form – Contact Form Plugin version 2.21.6 or earlier
- ability to obtain a legitimate workflow trigger token, entry ID, and log ID
- public access to the target site’s WordPress AJAX endpoint
Step-by-step exploitation
- Capture a legitimate form submission response from the target site.
- Extract:
id(form ID)token(workflow trigger token)entryIDlogID
- Construct a POST request to
/wp-admin/admin-ajax.phpwith form fields:action=bitforms_trigger_workflowid=<formID>token=<token>cronNotOk[0]=<entryID>cronNotOk[1]=<logID>
- Send the request without WordPress authentication cookies.
- Observe whether the response indicates success and whether configured integrations are executed.
Expected behavior vs exploited behavior
- Expected behavior:
- unauthorized or invalid requests are rejected
- workflows execute only after valid authentication and nonce validation
- Exploited behavior:
- unauthenticated request is accepted
- configured workflows execute using the supplied entry/log identifiers
- downstream integrations are triggered without a legitimate form submit
Verification
- A positive verification is acceptance of an unauthenticated POST to
bitforms_trigger_workflowfollowed by integration activity. - A negative test is rejection of the same request after patching to 2.21.7 or later.
- Recommendations
Mitigation strategies
- upgrade Bit Form – Contact Form Plugin to version 2.21.7 or later immediately
- if upgrade is not possible, block or monitor
admin-ajax.phprequests containingaction=bitforms_trigger_workflow - restrict access to the AJAX endpoint via WAF rules if practical
Detection methods
- audit web server logs for POST requests to
/wp-admin/admin-ajax.phpwithaction=bitforms_trigger_workflow - look for requests lacking authentication headers/cookies
- correlate unexpected workflow triggers with form submission events
- monitor downstream integration endpoints for anomalous calls
Best practices to prevent similar issues
- never couple CSRF/nonce validation with login state for public endpoints
- enforce token validation for both authenticated and unauthenticated AJAX actions
- centralize authorization logic and avoid duplicated conditionals
- validate and sanitize all request-derived IDs before use
- keep dependency libraries up to date, especially sanitization packages and input parsers