Strangers Are Supposed to Talk to the Chatbot: Unauthenticated Object Injection in QuantumCloud Forms

At 11:16:37Z on 18 October 2026, NVD published CVE-2026-110886: a deserialization-of-untrusted-data flaw in QuantumCloud’s WPBot – ChatBot Conversational Forms WordPress plugin, versions through 1.5.1, rated CVSS 3.1 9.8 CRITICAL (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), CWE-502. The PR:N is not a configuration accident. A chatbot’s entire purpose is accepting input from people who have no account, and the vulnerable endpoint is registered for exactly those people. We read the NVD record, the Patchstack advisory, and the 1.5.1, 1.5.2, and 1.5.3 release packages first-hand, and diffed them locally.

The entry point is wpbot_capture_form_value(), the AJAX handler that records each conversational answer as the visitor works through the form. In all three releases it is registered twice:

add_action( 'wp_ajax_wpbot_capture_form_value',        'wpbot_capture_form_value' );
add_action( 'wp_ajax_nopriv_wpbot_capture_form_value', 'wpbot_capture_form_value' );

The second line is the vulnerability’s precondition: nopriv means WordPress invokes the handler for logged-out requests. Every answer a stranger types into the chatbot — $_POST['answer'], $_POST['entry'], $_POST['name'] — arrives at server-side code with no authentication, by design. What 1.5.1 did with those answers is the finding. Stored entry values were decoded with maybe_unserialize() at four sinks: classes/entry/meta.php:40, classes/entry/field.php:116 and :134, and classes/admin.php:990. WordPress’s maybe_unserialize() passes no class whitelist, so a crafted serialized object stored in an entry instantiates an arbitrary class and invokes __wakeup / __destruct gadget chains — the standard PHP object-injection primitive, reachable here without credentials because the chatbot is the submission channel.

The fix, in the vendor’s own words

Version 1.5.2, credited by Patchstack to a report from hinhlx on 9 October (advisory published 16 October), closes the sinks by replacing every entry-value maybe_unserialize() with unserialize( $value, array( 'allowed_classes' => false ) ). The vendor’s comments, added at each call site, describe the threat model accurately and are worth quoting verbatim: “maybe_unserialize() passes no class whitelist, so a crafted serialized object stored in meta_value would instantiate an arbitrary class and invoke __wakeup / __destruct gadget chains” and “With allowed_classes => false, object tokens produce a harmless __PHP_Incomplete_Class and no constructor or magic method runs.” The same release also fixes the source, not just the sink: the capture handler now rejects any answer for which is_serialized() returns true, responding 400 ‘Invalid answer value.’ before anything reaches the database.

Then, today — 19 October, with a wordpress.org timestamp of 12:19 GMT — version 1.5.3 shipped a second, structural layer. The entry-continuation flow now mints a per-entry nonce (wp_create_nonce( 'wpbot_entry_' . $entry )) when the entry row is created and demands it back on every subsequent request, failing closed with 403 ‘Invalid or expired session token.’ when the nonce is missing or wrong; the entry id is coerced with absint() so client-supplied strings like undefined or null collapse to “new entry” instead of reaching comparisons; and stored answers are flattened to plain sanitized scalar strings. The readme changelog for 1.5.3 says only “Fixed a form issue” — three consecutive releases in this plugin’s changelog read “Improved Security” or less — which undersells a genuine authorization boundary the way changelogs so often do. Read the diff, not the changelog.

Honest scoping

Three caveats, stated plainly. First, footprint: wordpress.org reports 1,000+ active installs for this plugin. That is two orders of magnitude below the 90,000-install AI Engine chatbot surface covered here this week, and Patchstack’s warning that flaws like this feed mass-exploit campaigns should be read as a statement about the defect class, not this plugin’s share of it. Second, residual sinks: 1.5.3 still calls maybe_unserialize() on form configuration blobs in qcformbuilder-core.php and classes/db/form.php. Those consume admin-saved configuration rather than visitor input, so the unauthenticated path is closed, but the primitive survives where an administrator or a lesser privileged-plugin flaw could reach it — worth one hardening pass, not one alarm. Third, the NVD record sits at vulnStatus: Deferred, and the CVE does not appear in the CISA KEV catalog (1,748 entries, 16 October release); there is no published evidence of exploitation. Patchstack’s own scoring note — 9.8, “expected to become exploited” — is a forecast, and forecasts are not telemetry.

The reason this earns coverage despite the small install base is architectural, and it is the same reason the AI Engine object-ownership flaws earned coverage at ninety times the footprint: a conversational front end is an unauthenticated deserialization boundary by construction. Any plugin that turns stranger-typed chat answers into stored server-side structures must treat the serialization format as hostile at both ends — reject it at submission, refuse to instantiate it at read — and this vendor has now done both, then added the nonce binding that makes one visitor’s entry unaddressable by another. That is the correct three-layer shape, and it is worth studying regardless of which chatbot plugin you run.

What to do

  • Update to 1.5.3, not just 1.5.2. The advisory names 1.5.2 as the patched version, but 1.5.3 — released today — adds the per-entry nonce binding and the entry-id coercion that close the neighboring weaknesses (cross-visitor entry writes, type-juggling on the entry id). Take the current release.
  • Scan stored entries for serialized payloads. Any row in wfb_form_entry_values or the entry-meta table whose value satisfies is_serialized() predates the submission filter and deserves forensic review; a stored O:...:"..." object token in a visitor-reachable column is evidence of probing at minimum.
  • Ask the changelog question of your own chatbot stack. Any conversational plugin, MCP tool, or agent front end that persists visitor input should be able to answer: where does a serialized structure from the visitor get decoded, with what class whitelist, and what binds one visitor’s stored state to that visitor. If the answer is “nowhere / none / nothing,” you are running 1.5.1.

Sources: