CVE-2026-39478

Anti-Malware Security and Brute-Force Firewall <= 4.23.87 - Authenticated (Contributor+) PHP Object Injection

highDeserialization of Untrusted Data
7.5
CVSS Score
7.5
CVSS Score
high
Severity
4.23.88
Patched in
11d
Time to patch

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:H
Attack Vector
Network
Attack Complexity
High
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=4.23.87
PublishedApril 20, 2026
Last updatedApril 30, 2026
Affected plugingotmls

What Changed in the Fix

Changes introduced in v4.23.88

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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.php or /wp-admin/admin.php (if handled via admin_init).
  • Action: GOTMLS_scan_action or GOTMLS_load_update (inferred from index.php line 74).
  • Vulnerable Parameter: definitions (or GOTMLS_definitions_array).
  • Payload Encoding: Base64 encoded serialized PHP object.
  • Preconditions:
    • Attacker must have a Contributor account.
    • The plugin must be active.
    • The user_can capability for the plugin must be accessible to the Contributor (defaults to edit_posts in many GOTMLS configurations).

3. Code Flow

  1. Entry Point: A Contributor user triggers an AJAX action (e.g., GOTMLS_scan_action) or accesses the settings page with specific $_POST parameters.
  2. Capability Check: The plugin checks if the user has the capability defined in $GLOBALS["GOTMLS"]["tmp"]["settings_array"]["user_can"]. If this is edit_posts (standard for Contributors), the request proceeds.
  3. Data Retrieval: The plugin reads the definitions parameter from the $_POST or $_REQUEST superglobals.
  4. Decoding: The input is passed through base64_decode() and then into a decoding function. Verbatim from safe-load/trace.php line 125, we see:
    $GLOBALS["GOTMLS"]["tmp"]["custom_whitelist"][...] = GOTMLS_decode($get_whitelist_row["post_title"]);
  5. Sink: The GOTMLS_decode() function (not fully shown in snippet but standard in this plugin) executes unserialize() 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:

  1. Target Page: The Anti-Malware settings page: /wp-admin/admin.php?page=gotmls-settings.
  2. Identification: The plugin enqueues scripts that contain the nonce and AJAX URL. Look for the GOTMLS_display_header output.
  3. Extraction:
    • Navigate to the settings page as a Contributor.
    • Use browser_eval to extract the nonce from the localized JavaScript objects.
    • The variable name is likely GOTMLS_js_object or data localized via wp_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?.nonce or look for the head_nonce in 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

  1. Ensure Plugin is Configured: The plugin might require "Definition Updates" to be clicked once to initialize the settings_array.
  2. Access Control: Verify that a Contributor can see the menu:
    wp eval 'echo get_option("GOTMLS_settings_array")["user_can"];'
    
    If it is not 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 Xdebug or custom error logging in unserialize), it will show the stdClass object 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:

  1. Check Logs: Monitor wp-content/debug.log for any "unserialization" errors or notices.
  2. Database State: Some objects might alter options. Check if GOTMLS_definitions_array was 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. Use GET or POST to /wp-admin/admin.php?page=GOTMLS_load_update&definitions=[PAYLOAD]&_wpnonce=[NONCE].
  • Target GOTMLS_View_Quarantine: As seen in index.php line 52, if a Contributor can view quarantine, they may be able to trigger the GOTMLS_decode sink by visiting that page if the database contains a malicious quarantine entry. This would be a two-step "Stored Object Injection" attack.
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/gotmls/4.23.87/index.php /home/deploy/wp-safety.org/data/plugin-versions/gotmls/4.23.88/index.php
--- /home/deploy/wp-safety.org/data/plugin-versions/gotmls/4.23.87/index.php	2026-02-26 18:15:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/gotmls/4.23.88/index.php	2026-03-09 14:48:00.000000000 +0000
@@ -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 />';
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/gotmls/4.23.87/readme.txt /home/deploy/wp-safety.org/data/plugin-versions/gotmls/4.23.88/readme.txt
--- /home/deploy/wp-safety.org/data/plugin-versions/gotmls/4.23.87/readme.txt	2026-02-26 18:15:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/gotmls/4.23.88/readme.txt	2026-03-09 14:48:00.000000000 +0000
@@ -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
--- /home/deploy/wp-safety.org/data/plugin-versions/gotmls/4.23.87/safe-load/trace.php	2026-02-26 18:15:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/gotmls/4.23.88/safe-load/trace.php	2026-03-09 14:48:00.000000000 +0000
@@ -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.