Email Encoder – Protect Email Addresses and Phone Numbers <= 2.4.4 - Authenticated (Contributor+) Stored Cross-Site Scripting via eeb_mailto Shortcode
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:NTechnical Details
<=2.4.4What Changed in the Fix
Changes introduced in v2.4.5
Source Code
WordPress.org SVNThis 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_mailtoshortcode handler. - Affected Versions: <= 2.4.4
- Mechanism: The plugin fails to sanitize or escape attributes passed to the
[eeb_mailto]shortcode, specifically thedisplayattribute (and potentiallyextra_attrs). When the plugin "encodes" the email to protect it from bots (often using JavaScriptdocument.writeor 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.phpor 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
- Entry Point: A user with Contributor permissions saves a post containing:
[eeb_mailto email="test@example.com" display="<img src=x onerror=alert(1)>"]. - Registration: The shortcode is registered in
Legacy\EmailEncoderBundle\Email_Encoder_Settingsvia the$template_tagsarray (mappingeeb_mailtototemplate_tag_eeb_mailto). - Execution: When the post is rendered, WordPress calls the handler. According to
core/includes/functions/template-tags.php, this callsEEB()->functions->eeb_mailto( ...$args ). - Processing (Inferred): The
Functions::eeb_mailtomethod processes the attributes. If thedisplayattribute is not escaped usingesc_html()orwp_kses(), the raw HTML is carried into the protection logic. - Sink: The logic (similar to the AJAX
handle()method inclass-email-encoder-bundle-ajax.php) constructs an<a>tag:$link = '<a href="mailto:' . $email . '" ...>' . $display . '</a>'; - Protection Layer: If the "Protect using Javascript" method is active, this
$linkstring is passed to an encoder. Many encoders in this plugin simply obfuscate the string and use a JS-based decoder on the frontend. - Execution: When a victim (e.g., Admin) views the post, the JS decoder reconstructs the
$linkand inserts it into the DOM, executing theonerrorpayload.
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-clito create the post, which bypasses the need for manual nonce management in the browser.
5. Exploitation Strategy
- Authentication: Login as a user with the Contributor role.
- 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)']
- Payload 1 (Tag-based):
- Trigger: As an Administrator, navigate to the newly created post on the frontend.
- Observation: Verify that the JavaScript executes in the Admin's browser context.
6. Test Data Setup
- Target User: Create a contributor user:
wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - 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) - 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 invalidsrc, triggering theonerrorevent and showing an alert box with the domain.
8. Verification Steps
- Check Post Content:
wp post get <POST_ID> --field=post_content - Examine Frontend Output: Use the
http_requesttool 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> - Verify Execution: Use
browser_navigateto the post URL and check for thealertor presence of the injected element in the DOM usingbrowser_eval.
9. Alternative Approaches
- Method Obfuscation: If the default JS method is patched, try switching the plugin settings to
char_encodeorwithout_javascriptviawp option update WP_Email_Encoder_Bundle_options ...to see if thedisplayattribute 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_outputinclass-email-encoder-bundle-ajax.phpuseswp_kses_postoneebDisplay. If the shortcode handler omits this check entirely, that confirms the source of the vulnerability.
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
@@ -1,4 +1,5 @@ <?php +if ( ! defined( 'ABSPATH' ) ) exit; return [ @@ -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 ) { @@ -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.