Proof/CVE-2026-15054
Bit Form checked whether a form was active when it decided to render it — but not when it decided to process a submission. An unauthenticated attacker could ask the AJAX layer for a valid token set on an inactive form and post to it, creating an entry and firing every attached workflow, email notifications included.
An inactive or unpublished form is a form the site has deliberately withheld — a draft, a seasonal promotion, a workflow still under construction. The active-state check is the authorization decision that keeps such a form from doing work. If that check runs only when rendering, but not when accepting input, the form is effectively live to anyone who can reach the endpoint directly.
An inactive form must not accept submissions or fire its workflows, regardless of how a request reaches the submission endpoint.
The bug was not that Bit Form issued submission tokens. The bug was that the active-state gate governed the front-end render path while the AJAX token and submit actions treated every form — active or not — as fair game.
Only published forms appear on the site, so a page visitor can only submit forms that are active. That front-end assumption was allowed to stand in for an authorization check at the endpoint. The AJAX actions that mint tokens and accept submissions never asked whether the form was active before doing their work.
bitforms_onload_added_field_and_property (nopriv):
load form by id
issue CSRF + identity tokens // no is_active(form) check
bitforms_submit_form (nopriv):
verify tokens
create entry
run workflows / notifications // no is_active(form) check
// with WP-Cron disabled:
cronNotOk token -> execute workflows explicitly
// missing at every endpoint: if (!form.active) return 403;Because the token-issuing action and the submit action are both registered for unauthenticated (nopriv) access, an attacker never needs the rendered page. They request tokens for an inactive form by ID, then submit against them — the entry is written and the attached workflows run.
Unauthenticated request yields CSRF + identity tokens for an inactive form.
Tokens are accepted; an entry is created for the inactive form.
Attached workflows execute, including outbound email.
With WP-Cron off, a replayed token forces explicit workflow execution.
The attacker identifies a form the site has not published — a draft or disabled form whose ID is guessable or enumerable — and never loads a public page for it.
A request to the bitforms_onload_added_field_and_property action returns the CSRF and identity tokens the submit endpoint expects, even for the inactive form.
Using those tokens, the attacker posts to bitforms_submit_form. An entry is created and the form's workflows execute — email notifications included.
On installs with WP-Cron disabled, the attacker replays a cronNotOk token to explicitly trigger workflow execution rather than waiting on a scheduler.
Draft-form entries accumulate and outbound notifications fire from a form the operator believed was inert — usable for spam relay, data seeding, or probing unfinished workflows.
The finding was promoted only after the vulnerable path, the expected-behavior control and the fixed control were retained together.
An active form accepts a submission and runs its workflow — the intended behavior.
The same unauthenticated flow against an inactive form still creates an entry and fires notifications.
Token, submit and cron endpoints return HTTP 403 "Form is not active" for inactive forms.
Not rendering a form is a UI decision. Refusing to process it is an authorization decision. The two were conflated: the site's front end hid inactive forms, and that concealment was treated as though it enforced access.
The durable engineering rule is that state which governs whether an action is permitted must be re-checked at every endpoint that performs the action — especially unauthenticated AJAX handlers, which never see the front-end gate at all.
The workflow did not stop at "the submit handler lacks an active check." Each stage had to produce evidence, or a refutation, before the candidate could move forward.
Trace the nopriv token and submit actions and locate every place the form's active state is (and is not) consulted.
Challenge the assumption that hiding inactive forms on the front end prevents submissions to them.
Drive the token → submit → workflow flow directly against an inactive form, unauthenticated, and observe the notification.
Active-form expected control, exact marker, cron-disabled replay path, and re-run against 3.1.2 as the fixed control.
Review source, replay, impact, version range, wording and public-safety boundary before reporting to the vendor.
WPScan scores CVE-2026-15054 at 3.7 Low on CVSS 3.1. The score reflects limited, integrity-only impact — no data disclosure, no availability loss — and an attack that depends on specific form state. It does not require authentication.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N
The practical risk is workflow abuse: entries written to forms the operator considers offline, and notification email dispatched from those forms — a spam and data-integrity concern more than a takeover. The AC:H term reflects the token-acquisition step; PR:N confirms no account is needed.
| Plugin | Affected range | Fixed |
|---|---|---|
bit-form (Bit Form) | ≤ 3.1.1 (all < 3.1.2) | 3.1.2 |
Sites that keep no inactive or draft forms have a smaller exposure window, but any Bit Form install below 3.1.2 should upgrade.
The fix is not "hide the endpoint." The durable rule is that the active-state check must run at every endpoint that acts on a form, not only where the form is rendered.
nopriv AJAX handlers as untrusted entry points that must re-validate object state, never inheriting front-end assumptions.The bug lived between rendering and processing. The front end knew the form was inactive and hid it. The submission endpoints did not, and acted on it anyway. Concealment was mistaken for enforcement.
The CVE is the identifier. The active-form control is what proved the endpoint had no opinion about form state until 3.1.2 gave it one.
Credit: Discovered and reported by Charles Vosburgh. Research was AI-assisted through source mapping, hypothesis generation, runtime construction and skeptical validation; final review and disclosure ownership remained human.