CVE-2025-11705: Broken Access Control
Anti-Malware Security and Brute-Force Firewall (WordPress plugin, slug: gotmls), versions ≤ 4.23.81
| Author | Ebad Khan |
| Date | July 2026 |
| CVE ID | CVE-2025-11705 |
| CWE | CWE-862 (Missing Authorization) |
| CVSS 3.1 (Wordfence) | AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Affected versions | ≤ 4.23.81 |
Summary
The Anti-Malware Security and Brute-Force Firewall plugin for WordPress (versions up to and including 4.23.81) contains a broken access control vulnerability in its admin-ajax.php action handlers. A permission-check function, GOTMLS_user_can(), is correctly implemented and correctly evaluated inside the shared bootstrap function GOTMLS_ajax_load_update(), but its result is never used to halt execution for unauthorized users. As a result, any authenticated user with the lowest WordPress role (Subscriber) can invoke administrator-only AJAX actions and receive full, unredacted responses, including the contents of the plugin's malware quarantine.
This writeup documents independent root-cause analysis and reproduction of the publicly disclosed vulnerability, performed in an isolated lab environment against the exact affected version.
Affected Component
- File:
index.php(main plugin bootstrap and AJAX router) - Supporting file:
images/index.php(definesGOTMLS_user_can())
The plugin registers a family of AJAX actions dynamically:
$ajax_functions = array('load_update', 'log_session', 'empty_trash', 'fix',
'logintime', 'lognewkey', 'position', 'scan', 'View_Quarantine', 'whitelist');
foreach ($ajax_functions as $ajax_function) {
add_action("wp_ajax_GOTMLS_$ajax_function", "GOTMLS_ajax_$ajax_function");
add_action("wp_ajax_nopriv_GOTMLS_$ajax_function",
substr($ajax_function, 0, 3) == "log" ? "GOTMLS_ajax_$ajax_function" : "GOTMLS_ajax_nopriv");
}
Every handler in this list (with the exception of the nopriv-routed logging actions) is reachable by any authenticated user, regardless of role, because WordPress's wp_ajax_* hook system only requires a valid login and not a specific capability. Enforcing the intended admin-only restriction is left entirely to the plugin's own code.
Root Cause
Each AJAX handler calls a shared bootstrap function, GOTMLS_ajax_load_update(), before doing its own work:
function GOTMLS_ajax_load_update() {
global $wpdb;
$GOTMLS_nonce_found = GOTMLS_get_nonce();
$YES_user_can = GOTMLS_user_can();
...
GOTMLS_user_can() (defined in images/index.php) is a correctly written capability check:
function GOTMLS_user_can() {
if (is_multisite())
$GLOBALS["GOTMLS"]["tmp"]["settings_array"]["user_can"] = "manage_network";
elseif (!isset($GLOBALS["GOTMLS"]["tmp"]["settings_array"]["user_can"]) || ...)
$GLOBALS["GOTMLS"]["tmp"]["settings_array"]["user_can"] = "activate_plugins";
if (current_user_can($GLOBALS["GOTMLS"]["tmp"]["settings_array"]["user_can"]))
return true;
else
return false;
}
It correctly wraps WordPress's native current_user_can() against the activate_plugins capability, which is held only by Administrators (or manage_network on multisite), not Subscribers.
The defect is that GOTMLS_ajax_load_update() computes this result into $YES_user_can but only consults it for one narrow branch, gating the plugin's definition-update-download logic:
if (isset($_REQUEST["UPDATE_definitions_array"]) && ... && $YES_user_can) {
...
}
The function never calls wp_die(), return, or otherwise halts execution when $YES_user_can is false. Execution simply falls through and GOTMLS_ajax_load_update() returns normally to its caller.
Downstream handlers such as GOTMLS_ajax_View_Quarantine() compound the problem by not performing any capability check of their own:
function GOTMLS_ajax_View_Quarantine() {
GOTMLS_ajax_load_update();
die(GOTMLS_html_tags(array("html" => array("body" =>
GOTMLS_get_header().GOTMLS_box(GOTMLS_Quarantine_Trash()
.__("View Quarantine",'gotmls'), GOTMLS_get_quarantine())))));
}
The permission check exists in the codebase, executes on every request, and is silently discarded outside of the one code path that consults it. This is a textbook instance of CWE-862 (Missing Authorization): the capability to check permissions is present, but the enforcement is absent at the actual point of privileged action.
Proof of Concept
Environment
- Ubuntu 24.04 LTS Virtual Machine running WordPress through the official Docker image
- Plugin: Anti-Malware Security and Brute-Force Firewall v4.23.81, installed from the official WordPress.org repository
- Two accounts created: an Administrator (site owner) and a Subscriber (
subscriber-acc), the lowest privileged authenticated role in WordPress - Fully isolated lab VM, no production data
Steps
- Authenticate to WordPress as
subscriber-acc(Subscriber role, holds no elevated capabilities, includingactivate_plugins). - Extract the
wordpress_logged_in_*session cookie for that account. - Send a direct request to the plugin's
View_QuarantineAJAX action using only that Subscriber session:
curl -s "http://<target>/wp-admin/admin-ajax.php?action=GOTMLS_View_Quarantine" \
-H "Cookie: wordpress_logged_in_<hash>=<redacted session value>"
Result
The request returned HTTP 200 with a fully rendered instance of the plugin's admin-only "View Quarantine" panel, including page chrome, styling, and the live quarantine listing, despite the requesting account holding no administrative capabilities. No permission-denied response, redirect, or authentication challenge was returned.
This confirms that a Subscriber-level account can successfully invoke an action gated (in intent, not in enforcement) behind activate_plugins. On a production site with items actually present in quarantine, this same request would return the file paths and metadata of quarantined files, information the official advisory characterizes as capable of exposing sensitive server-side data.
Impact
- Confidentiality: High. Low-privileged, non-administrative accounts (Subscriber and above) can retrieve data intended to be restricted to site administrators.
- Integrity / Availability: Not affected; the reproduced action is read-only.
- Attack complexity: Low. A single authenticated request is sufficient.
- Privileges required: Low (Subscriber), the default role for self-registered users on sites with open registration.
Remediation
Update to version 4.23.83 or later, where the vendor's changelog explicitly credits this fix: "Fixed missing capability check on admin-ajax API."
Disclosure Timeline
This document is an independent reproduction and root-cause writeup performed for personal research and portfolio purposes. No original discovery is claimed.
References
- NVD: CVE-2025-11705
- Wordfence Vulnerability Database entry
- Vendor changelog,
gotmlsplugin (WordPress.org)