CVE-2026-2840

Email Encoder – Protect Email Addresses and Phone Numbers <= 2.4.4 - Authenticated (Contributor+) Stored Cross-Site Scripting via eeb_mailto Shortcode

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
2.4.5
Patched in
0d
Time to patch

Description

The Email Encoder – Protect Email Addresses and Phone Numbers plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'eeb_mailto' shortcode in all versions up to, and including, 2.4.4 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with contributor level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.4.4
PublishedApril 15, 2026
Last updatedApril 15, 2026
Affected pluginemail-encoder-bundle

What Changed in the Fix

Changes introduced in v2.4.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan targets a Stored Cross-Site Scripting (XSS) vulnerability in the **Email Encoder – Protect Email Addresses and Phone Numbers** plugin. The vulnerability allows a Contributor-level user to inject arbitrary JavaScript via the `[eeb_mailto]` shortcode. ### 1. Vulnerability Summary *…

Show full research plan

This research plan targets a Stored Cross-Site Scripting (XSS) vulnerability in the Email Encoder – Protect Email Addresses and Phone Numbers plugin. The vulnerability allows a Contributor-level user to inject arbitrary JavaScript via the [eeb_mailto] shortcode.

1. Vulnerability Summary

  • Vulnerability: Stored Cross-Site Scripting (XSS)
  • Component: eeb_mailto shortcode handler.
  • Affected Versions: <= 2.4.4
  • Mechanism: The plugin fails to sanitize or escape attributes passed to the [eeb_mailto] shortcode, specifically the display attribute (and potentially extra_attrs). When the plugin "encodes" the email to protect it from bots (often using JavaScript document.write or similar methods), it includes the malicious payload in the output without sufficient filtering.

2. Attack Vector Analysis

  • Endpoint: WordPress Post Editor (via wp-admin/post-new.php or REST API).
  • Action: Creating or updating a post/page containing the [eeb_mailto] shortcode.
  • Role Required: Contributor or higher (any role capable of using shortcodes).
  • Payload Parameter: Shortcode attributes (e.g., display, extra_attrs).
  • Preconditions: The plugin must be active. The default protection method (with_javascript) is typically the most susceptible to XSS in this context.

3. Code Flow

  1. Entry Point: A user with Contributor permissions saves a post containing: [eeb_mailto email="test@example.com" display="<img src=x onerror=alert(1)>"].
  2. Registration: The shortcode is registered in Legacy\EmailEncoderBundle\Email_Encoder_Settings via the $template_tags array (mapping eeb_mailto to template_tag_eeb_mailto).
  3. Execution: When the post is rendered, WordPress calls the handler. According to core/includes/functions/template-tags.php, this calls EEB()->functions->eeb_mailto( ...$args ).
  4. Processing (Inferred): The Functions::eeb_mailto method processes the attributes. If the display attribute is not escaped using esc_html() or wp_kses(), the raw HTML is carried into the protection logic.
  5. Sink: The logic (similar to the AJAX handle() method in class-email-encoder-bundle-ajax.php) constructs an <a> tag:
    $link = '<a href="mailto:' . $email . '" ...>' . $display . '</a>';
  6. Protection Layer: If the "Protect using Javascript" method is active, this $link string is passed to an encoder. Many encoders in this plugin simply obfuscate the string and use a JS-based decoder on the frontend.
  7. Execution: When a victim (e.g., Admin) views the post, the JS decoder reconstructs the $link and inserts it into the DOM, executing the onerror payload.

4. Nonce Acquisition Strategy

This vulnerability is triggered by saving a post, which uses standard WordPress core nonces for the Gutenberg editor or Classic editor.

  • The PoC agent does not need to extract a plugin-specific nonce to exploit the shortcode.
  • The agent should use wp-cli to create the post, which bypasses the need for manual nonce management in the browser.

5. Exploitation Strategy

  1. Authentication: Login as a user with the Contributor role.
  2. Payload Injection: Create a new post containing the malicious shortcode.
    • Payload 1 (Tag-based): [eeb_mailto email="attacker@example.com" display="<img src=x onerror=alert(window.origin)>"]
    • Payload 2 (Attribute Breakout): [eeb_mailto email="attacker@example.com" extra_attrs='onmouseover=alert(1)']
  3. Trigger: As an Administrator, navigate to the newly created post on the frontend.
  4. Observation: Verify that the JavaScript executes in the Admin's browser context.

6. Test Data Setup

  1. Target User: Create a contributor user:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
  2. Malicious Post: Use WP-CLI to create the post as the contributor:
    wp post create --post_type=post --post_status=publish --post_title="Contact Us" \
      --post_content='Please contact us at: [eeb_mailto email="test@example.com" display="<img src=x onerror=alert(document.domain)>"]' \
      --post_author=$(wp user get attacker --field=ID)
    
  3. Plugin Config: Ensure the default "Protect emails using: automatically the best method (including javascript)" is selected in settings (this is the default state).

7. Expected Results

  • The shortcode should be rendered by the plugin.
  • The HTML source of the page will contain the encoded version of the link.
  • Upon page load, the plugin's JavaScript will decode the payload.
  • The browser will attempt to render the <img> tag with an invalid src, triggering the onerror event and showing an alert box with the domain.

8. Verification Steps

  1. Check Post Content:
    wp post get <POST_ID> --field=post_content
  2. Examine Frontend Output: Use the http_request tool to fetch the post URL and check for the obfuscated string or the injected payload:
    # Look for the encoded snippet or evidence of the encoder JS
    http_request http://localhost:8080/?p=<POST_ID>
    
  3. Verify Execution: Use browser_navigate to the post URL and check for the alert or presence of the injected element in the DOM using browser_eval.

9. Alternative Approaches

  • Method Obfuscation: If the default JS method is patched, try switching the plugin settings to char_encode or without_javascript via wp option update WP_Email_Encoder_Bundle_options ... to see if the display attribute remains unescaped in other protection modes.
  • Shortcode Content: Test if the shortcode supports a content block instead of an attribute: [eeb_mailto email="..."]<script>alert(1)</script>[/eeb_mailto].
  • AJAX Endpoint: While the description focuses on the shortcode, the AJAX handler eeb_get_email_form_output in class-email-encoder-bundle-ajax.php uses wp_kses_post on eebDisplay. If the shortcode handler omits this check entirely, that confirms the source of the vulnerability.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Email Encoder plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) via the 'eeb_mailto' shortcode due to insufficient sanitization and output escaping of attributes like 'display' and 'extra_attrs'. This allows authenticated attackers with Contributor-level permissions or higher to inject arbitrary JavaScript that executes when a user, such as an administrator, views the affected post or page.

Vulnerable Code

// core/includes/classes/class-email-encoder-bundle-ajax.php:67-68
$class     = esc_attr( $this->getSetting( 'class_name', true ) );
$protect   = __( $this->getSetting( 'protection_text', true ), 'email-encoder-bundle' );
$link      = '<a href="mailto:' . $email . '" class="' . $class . '">' . $display . '</a>';

---

// core/includes/classes/class-email-encoder-bundle-helpers.php:177-198
public function sanitize_html_attributes( $extra_attrs ){

	$allowed_attrs = [ 'href', 'title', 'rel', 'class', 'id', 'style', 'target' ];

	// Use a regular expression to match attributes and their values
	preg_match_all('/(\w+)=("[^"]*"|\'[^\']*\')/', $extra_attrs, $matches, PREG_SET_ORDER);

	$sanitized_attrs = [];

	foreach ( $matches as $match ) {

		//Skip undefined arguments
		if( ! in_array( $match[1], $allowed_attrs ) ){
			continue;
		}

		// $match[1] is the attribute name, $match[2] is the attribute value including quotes
		$sanitized_name = sanitize_key( $match[1] ); // Sanitize the attribute name
		$sanitized_value = esc_attr( trim( $match[2], '"\'' ) ); // Remove quotes and escape the value
		$sanitized_value = str_replace( '\\', '', $sanitized_value ); // Remove quotes and escape the value

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/email-encoder-bundle/2.4.4/config/SettingsConfig.php /home/deploy/wp-safety.org/data/plugin-versions/email-encoder-bundle/2.4.5/config/SettingsConfig.php
--- /home/deploy/wp-safety.org/data/plugin-versions/email-encoder-bundle/2.4.4/config/SettingsConfig.php	2025-12-23 05:08:10.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/email-encoder-bundle/2.4.5/config/SettingsConfig.php	2026-03-30 04:07:02.000000000 +0000
@@ -1,4 +1,5 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 
 return [
 
--- /home/deploy/wp-safety.org/data/plugin-versions/email-encoder-bundle/2.4.4/core/includes/classes/class-email-encoder-bundle-ajax.php	2026-02-16 05:52:16.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/email-encoder-bundle/2.4.5/core/includes/classes/class-email-encoder-bundle-ajax.php	2026-03-30 04:07:02.000000000 +0000
@@ -65,7 +67,7 @@
         $EEB       = Email_Encoder::instance();
 
         $class     = esc_attr( $this->getSetting( 'class_name', true ) );
-        $protect   = __( $this->getSetting( 'protection_text', true ), 'email-encoder-bundle' );
+        $protect   = (string) $this->getSetting( 'protection_text', true );
         $link      = '<a href="mailto:" . $email . "' class='" . $class . "'>" . $display . "</a>";
 
         switch ( $method ) {
--- /home/deploy/wp-safety.org/data/plugin-versions/email-encoder-bundle/2.4.4/core/includes/classes/class-email-encoder-bundle-helpers.php	2026-02-16 05:52:16.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/email-encoder-bundle/2.4.5/core/includes/classes/class-email-encoder-bundle-helpers.php	2026-03-30 04:07:02.000000000 +0000
@@ -79,7 +79,7 @@
 		ob_start();
 		?>
-		<div class="notice <?php echo $notice; ?> <?php echo $isit; ?>">
-			<p><?php echo $validated_content; ?></p>
+		<div class="notice <?php echo esc_attr( $notice ); ?> <?php echo esc_attr( $isit ); ?>">
+			<p><?php echo wp_kses_post( $validated_content ); ?></p>
 		</div>
 		<?php
 		$res = ob_get_clean();
@@ -255,7 +255,6 @@
 			// $match[1] is the attribute name, $match[2] is the attribute value including quotes
 			$sanitized_name = sanitize_key( $match[1] ); // Sanitize the attribute name
 			$sanitized_value = esc_attr( trim( $match[2], '"\'' ) ); // Remove quotes and escape the value
-			$sanitized_value = str_replace( '\\', '', $sanitized_value ); // Remove quotes and escape the value
 
 			// Reconstruct the attribute
 			$sanitized_attrs[] = $sanitized_name . '="' . $sanitized_value . '"';

Exploit Outline

The attacker requires Contributor or higher privileges to create or edit a WordPress post. 1. Log in to the WordPress dashboard with a Contributor account. 2. Create a new post or edit an existing one. 3. Embed the `[eeb_mailto]` shortcode with a malicious payload in the `display` attribute, for example: `[eeb_mailto email="attacker@example.com" display="<img src=x onerror=alert(document.domain)>"]`. 4. Alternatively, use the `extra_attrs` attribute to break out of the HTML tag: `[eeb_mailto email="attacker@example.com" extra_attrs='onmouseover=alert(1)']`. 5. Save the post. 6. When an administrator or any other user views the post on the frontend, the plugin's JavaScript decoder (if 'Protect using Javascript' is enabled) will reconstruct the anchor tag with the malicious HTML/JavaScript payload, causing it to execute in the victim's browser context.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.