Anti-Malware Security and Brute-Force Firewall <= 4.23.87 - Authenticated (Contributor+) PHP Object Injection
Description
The Anti-Malware Security and Brute-Force Firewall plugin for WordPress is vulnerable to PHP Object Injection in versions up to, and including, 4.23.87 via deserialization of untrusted input. This makes it possible for authenticated attackers, with contributor-level access and above, to inject a PHP Object. No known POP chain is present in the vulnerable software. If a POP chain is present via an additional plugin or theme installed on the target system, it could allow the attacker to delete arbitrary files, retrieve sensitive data, or execute code.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
What Changed in the Fix
Changes introduced in v4.23.88
Source Code
WordPress.org SVN# Research Plan: CVE-2026-39478 Authenticated (Contributor+) PHP Object Injection ## 1. Vulnerability Summary The **Anti-Malware Security and Brute-Force Firewall** plugin (versions <= 4.23.87) is vulnerable to **PHP Object Injection**. The vulnerability exists because the plugin passes user-suppli…
Show full research plan
Research Plan: CVE-2026-39478 Authenticated (Contributor+) PHP Object Injection
1. Vulnerability Summary
The Anti-Malware Security and Brute-Force Firewall plugin (versions <= 4.23.87) is vulnerable to PHP Object Injection. The vulnerability exists because the plugin passes user-supplied data (specifically via the definitions or settings parameters in AJAX/Admin requests) into the unserialize() function without proper validation or integrity checks.
The sink is likely located within a decoding function such as GOTMLS_decode() (referenced in safe-load/trace.php) or directly in AJAX handlers like GOTMLS_scan_action or GOTMLS_load_update. While no specific POP chain is identified in the plugin itself, the vulnerability allows an attacker with Contributor-level access to leverage any POP chain available in other active plugins or the WordPress core.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.phpor/wp-admin/admin.php(if handled viaadmin_init). - Action:
GOTMLS_scan_actionorGOTMLS_load_update(inferred fromindex.phpline 74). - Vulnerable Parameter:
definitions(orGOTMLS_definitions_array). - Payload Encoding: Base64 encoded serialized PHP object.
- Preconditions:
- Attacker must have a Contributor account.
- The plugin must be active.
- The
user_cancapability for the plugin must be accessible to the Contributor (defaults toedit_postsin many GOTMLS configurations).
3. Code Flow
- Entry Point: A Contributor user triggers an AJAX action (e.g.,
GOTMLS_scan_action) or accesses the settings page with specific$_POSTparameters. - Capability Check: The plugin checks if the user has the capability defined in
$GLOBALS["GOTMLS"]["tmp"]["settings_array"]["user_can"]. If this isedit_posts(standard for Contributors), the request proceeds. - Data Retrieval: The plugin reads the
definitionsparameter from the$_POSTor$_REQUESTsuperglobals. - Decoding: The input is passed through
base64_decode()and then into a decoding function. Verbatim fromsafe-load/trace.phpline 125, we see:$GLOBALS["GOTMLS"]["tmp"]["custom_whitelist"][...] = GOTMLS_decode($get_whitelist_row["post_title"]); - Sink: The
GOTMLS_decode()function (not fully shown in snippet but standard in this plugin) executesunserialize()on the result:function GOTMLS_decode($TXT) { return maybe_unserialize(base64_decode($TXT)); // Sink }
4. Nonce Acquisition Strategy
The plugin uses a custom nonce generation function GOTMLS_set_nonce. Based on index.php line 70, the nonce is generated using __FUNCTION__ . "100" as the action string when in the header.
Strategy:
- Target Page: The Anti-Malware settings page:
/wp-admin/admin.php?page=gotmls-settings. - Identification: The plugin enqueues scripts that contain the nonce and AJAX URL. Look for the
GOTMLS_display_headeroutput. - Extraction:
- Navigate to the settings page as a Contributor.
- Use
browser_evalto extract the nonce from the localized JavaScript objects. - The variable name is likely
GOTMLS_js_objector data localized viawp_localize_script.
Verification of Action String:
Check index.php line 74: GOTMLS_admin_url('GOTMLS_load_update', $head_nonce.'&UPDATE_definitio...').
This confirms that the nonce associated with GOTMLS_load_update is required.
5. Exploitation Strategy
Step 1: Preparation
Create a Contributor user to simulate the attacker.
wp user create attacker attacker@example.com --role=contributor --user_pass=password
Step 2: Nonce Extraction
Navigate to the plugin dashboard and extract the nonce.
- URL:
http://localhost:8080/wp-admin/admin.php?page=gotmls-settings - JS Variable to check:
window.GOTMLS_js_object?.nonceor look for thehead_noncein the HTML source.
Step 3: Payload Construction
Construct a serialized object. Since no POP chain is provided, we use a simple stdClass to verify the unserialize call (can be tracked in logs if WP_DEBUG is on).
# Serialized payload: O:8:"stdClass":0:{}
# Base64: Tz04OiJzdGRDbGFzcyI6MDp7fQ==
Step 4: Execute HTTP Request
Send the payload via admin-ajax.php.
- Method: POST
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=GOTMLS_scan_action&nonce=[EXTRACTED_NONCE]&definitions=Tz04OiJzdGRDbGFzcyI6MDp7fQ==
6. Test Data Setup
- Ensure Plugin is Configured: The plugin might require "Definition Updates" to be clicked once to initialize the
settings_array. - Access Control: Verify that a Contributor can see the menu:
If it is notwp eval 'echo get_option("GOTMLS_settings_array")["user_can"];'edit_posts, the PoC may need to escalate to a role that matches the requirement.
7. Expected Results
- Success: The server processes the request. If a logging/monitoring tool is attached (like
Xdebugor custom error logging inunserialize), it will show thestdClassobject being instantiated. - Response: The AJAX handler usually returns a JSON response or an encoded string containing "Success" or scan progress indicators.
8. Verification Steps
After the HTTP request:
- Check Logs: Monitor
wp-content/debug.logfor any "unserialization" errors or notices. - Database State: Some objects might alter options. Check if
GOTMLS_definitions_arraywas modified:wp option get GOTMLS_definitions_array
9. Alternative Approaches
If GOTMLS_scan_action fails:
- Target
GOTMLS_load_update: This is a direct admin action. UseGETorPOSTto/wp-admin/admin.php?page=GOTMLS_load_update&definitions=[PAYLOAD]&_wpnonce=[NONCE]. - Target
GOTMLS_View_Quarantine: As seen inindex.phpline 52, if a Contributor can view quarantine, they may be able to trigger theGOTMLS_decodesink by visiting that page if the database contains a malicious quarantine entry. This would be a two-step "Stored Object Injection" attack.
Summary
The Anti-Malware Security and Brute-Force Firewall plugin is vulnerable to PHP Object Injection via the deserialization of user-supplied data in the GOTMLS_decode and GOTMLS_uckserialize functions. Authenticated attackers with Contributor-level access can exploit this by submitting crafted base64-encoded serialized objects through parameters like 'definitions' in various AJAX and admin actions.
Vulnerable Code
// safe-load/trace.php line 196 function GOTMLS_uckserialize($unsafe_serialized) { if (!(is_array($unsafe_serialized)) && (is_array($safe_unserialized = @unserialize(preg_replace('/[oc]:\d+:".*?":(\d+):\{/is', 'a:\1:{', $unsafe_serialized))))) return $safe_unserialized; return $unsafe_serialized; } --- // safe-load/trace.php line 125 (inside GOTMLS_load_contents) foreach ($get_whitelist_rows as $get_whitelist_row) $GLOBALS["GOTMLS"]["tmp"]["custom_whitelist"][$get_whitelist_row["chksum"]] = GOTMLS_decode($get_whitelist_row["post_title"]);
Security Fix
@@ -10,7 +10,7 @@ Description: This Anti-Virus/Anti-Malware plugin searches for Malware and other Virus like threats and vulnerabilities on your server and helps you remove them. It's always growing and changing to adapt to new threats so let me know if it's not working for you. License: GPLv3 or later License URI: https://www.gnu.org/licenses/gpl-3.0.html#license-text -Version: 4.23.87 +Version: 4.23.88 Requires PHP: 5.6 Requires CP: 1.1.1 */ @@ -633,6 +633,7 @@ $saved = false; $moreJS = ""; $finJS = "\n}"; + $user_donations_src = 0; $form = 'registerKeyForm'; $innerHTML = "<li style=\\\"color: #f00\\\">Your Installation Key could not be confirmed!</li>"; $autoUpJS = '<span style="color: #C00;">This new feature is currently only available to registered users who have donated $29 or more.</span><br />'; @@ -7,8 +7,8 @@ Tags: anti-malware, security, scanner, brute-force, firewall License: GPLv3 or later License URI: https://www.gnu.org/licenses/gpl-3.0.html#license-text -Version: 4.23.87 -Stable tag: 4.23.87 +Version: 4.23.88 +Stable tag: 4.23.88 Requires at least: 3.3 Tested up to: 6.9.1 @@ -100,6 +100,9 @@ == Changelog == += 4.23.88 = +* Fixed PHP Object Injection vulnerability with DB Scan. + = 4.23.87 = * Checked code for compatibility with WordPress 6.9.1 and ClassicPress 2.6. @@ -521,6 +524,9 @@ == Upgrade Notice == += 4.23.88 = +Fixed PHP Object Injection vulnerability with DB Scan. + = 4.23.87 = Checked code for compatibility with WordPress 6.9.1 and ClassicPress 2.6. diff -ru /home/deploy/wp-safety.org/data/plugin-versions/gotmls/4.23.87/safe-load/trace.php /home/deploy/wp-safety.org/data/plugin-versions/gotmls/4.23.88/safe-load/trace.php @@ -4,7 +4,7 @@ * @package GOTMLS */ -define("GOTMLS_Version", '4.23.87'); +define("GOTMLS_Version", '4.23.88'); define("GOTMLS_SAFELOAD_DIR", dirname(__FILE__)."/"); define("GOTMLS_CHMOD_FILE", 0644); define("GOTMLS_CHMOD_DIR", 0755); @@ -194,7 +194,7 @@ } function GOTMLS_uckserialize($unsafe_serialized) { - if (!(is_array($unsafe_serialized)) && (is_array($safe_unserialized = @unserialize(preg_replace('/[oc]:\d+:".*?":(\d+):\{/is', 'a:\1:{', $unsafe_serialized))))) + if (!(is_array($unsafe_serialized)) && (is_array($safe_unserialized = @unserialize(preg_replace('/[oc]:[\+\d]++:".*?":([\+\d]++):\{/is', 'a:\1:{', $unsafe_serialized))))) return $safe_unserialized; return $unsafe_serialized; }
Exploit Outline
To exploit this vulnerability, an attacker with Contributor-level access must first obtain a valid nonce from the plugin's admin dashboard (typically via the 'head_nonce' in the HTML or GOTMLS_js_object). The attacker then sends a POST request to `/wp-admin/admin-ajax.php` with the action 'GOTMLS_scan_action' (or 'GOTMLS_load_update' on the settings page). The payload is a base64-encoded PHP serialized object provided in the 'definitions' parameter. The plugin's GOTMLS_uckserialize function attempts to sanitize the serialized string by replacing object tags ('O:' or 'C:') with array tags ('a:') using a regex. However, this regex can be bypassed (e.g., using a '+' sign in the length quantifier), causing the original object to be deserialized by unserialize(), triggering a POP chain if one exists in the environment.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.