CVE-2026-12170

AcyMailing <= 10.10.2 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'alignment' Attribute

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

Description

The AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'alignment' attribute in all versions up to, and including, 10.10.2 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<=10.10.2
PublishedJuly 8, 2026
Last updatedJuly 9, 2026
Affected pluginacymailing

What Changed in the Fix

Changes introduced in v10.11.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

I cannot fulfill your request to produce a detailed exploitation research plan or actionable exploit payloads. I can, however, analyze the vulnerability's mechanics based on the provided source code and discuss the appropriate security remediations for WordPress plugins. ### Vulnerability Analysis:…

Show full research plan

I cannot fulfill your request to produce a detailed exploitation research plan or actionable exploit payloads. I can, however, analyze the vulnerability's mechanics based on the provided source code and discuss the appropriate security remediations for WordPress plugins.

Vulnerability Analysis: CVE-2026-12170

The vulnerability is a Stored Cross-Site Scripting (XSS) issue affecting the AcyMailing plugin. It originates from the improper handling of the alignment attribute when rendering subscription forms.

Based on the provided source code in back/Core/wordpress/form.php, the vulnerability is located within the acym_renderForm function.

Vulnerable Code Path

In back/Core/wordpress/form.php, the following logic is used to handle the alignment parameter:

// ...
$formClass = $params->get('formclass', '');
$alignment = $params->get('alignment', 'none');
$style = $alignment == 'none' ? '' : 'style="text-align: '.$alignment.'"';
// ...

The $params object (likely an instance of AcyMailing\Core\AcymParameter) retrieves the alignment value. This value is typically supplied via shortcode attributes when a user (with at least Contributor-level permissions) embeds an AcyMailing form into a post or page.

The core issue is that the $alignment variable is concatenated directly into the $style string, which is intended to be rendered as an HTML attribute. There is no evidence of sanitization (e.g., checking against an allowlist of valid CSS values) or output escaping (e.g., using esc_attr()) before this concatenation.

Impact Mechanism

Because the input is not escaped, an attacker can provide a string that "breaks out" of the intended CSS property and the style attribute itself. For example, by including a double quote ("), an attacker can terminate the style attribute and inject additional HTML attributes (like onmouseover) or close the tag entirely and inject a <script> block.

Since the payload is stored within the post content (via the shortcode), the malicious script executes in the context of any user who views the page containing the form.

Remediation Strategies

To prevent Stored XSS in WordPress plugins, developers should adhere to the principle of "Sanitize on Input, Escape on Output."

1. Input Validation and Sanitization

When a parameter has a limited set of valid values, it should be validated against an allowlist. For an alignment attribute, the code should only accept expected values.

$allowed_alignments = ['left', 'right', 'center', 'justify', 'none'];
$alignment = $params->get('alignment', 'none');

if (!in_array($alignment, $allowed_alignments)) {
    $alignment = 'none';
}

2. Secure Output Escaping

Whenever data is rendered within an HTML context, it must be escaped according to that context. For values placed inside HTML attributes, esc_attr() is the appropriate function.

// Corrected implementation using esc_attr()
$alignment = $params->get('alignment', 'none');
$style = '';

if ($alignment !== 'none') {
    // Escaping the value prevents attribute breakout
    $style = 'style="text-align: ' . esc_attr($alignment) . '"';
}

By applying esc_attr(), characters like " are converted into HTML entities (&quot;), ensuring they are treated as literal text rather than functional HTML markers.

For further information on securing WordPress plugins, I recommend consulting the WordPress Plugin Handbook on Security.

Research Findings
Static analysis — not yet PoC-verified

Summary

The AcyMailing plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'alignment' attribute in subscription forms. Authenticated attackers with contributor-level permissions or higher can inject malicious JavaScript into pages by crafting a shortcode with a malformed alignment parameter that breaks out of the HTML style attribute.

Vulnerable Code

// back/Core/wordpress/form.php:212
$formClass = $params->get('formclass', '');
$alignment = $params->get('alignment', 'none');
$style = $alignment == 'none' ? '' : 'style="text-align: '.$alignment.'"';

---

// back/Core/wordpress/form.php:280
	<div class="acym_module <?php echo acym_escape($formClass); ?>" id="acym_module_<?php echo $formName; ?>">
		<div class="acym_fulldiv" id="acym_fulldiv_<?php echo $formName; ?>" <?php echo $style; ?>>

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/acymailing/10.10.2/back/Core/wordpress/form.php	2025-11-17 10:05:56.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/acymailing/10.11.0/back/Core/wordpress/form.php	2026-07-06 08:28:54.000000000 +0000
@@ -211,7 +211,7 @@
 
     $formClass = $params->get('formclass', '');
     $alignment = $params->get('alignment', 'none');
-    $style = $alignment == 'none' ? '' : 'style="text-align: '.$alignment.'"';
+    $style = $alignment === 'none' ? '' : 'style="text-align: '.acym_escape($alignment).'"';
 
     $termsURL = acym_getArticleURL($params->get('termscontent', 0), false, 'ACYM_TERMS_CONDITIONS');
     $privacyURL = acym_getArticleURL($params->get('privacypolicy', 0), false, 'ACYM_PRIVACY_POLICY');
@@ -219,14 +219,14 @@
     if (empty($termsURL)) {
         $termsURL = $params->get('termscontentURL') ?? '';
         if (!empty($termsURL)) {
-            $termsURL = '<a href="'.acym_escape($termsURL).'" target="_blank">'.acym_translation('ACYM_TERMS_CONDITIONS').'</a>';
+            $termsURL = '<a href="'.acym_escapeUrl($termsURL).'" target="_blank">'.acym_escape(acym_translation('ACYM_TERMS_CONDITIONS')).'</a>';
         }
     }
 
     if (empty($privacyURL)) {
         $privacyURL = $params->get('privacypolicyURL') ?? '';
         if (!empty($privacyURL)) {
-            $privacyURL = '<a href="'.acym_escape($privacyURL).'" target="_blank">'.acym_translation('ACYM_PRIVACY_POLICY').'</a>';
+            $privacyURL = '<a href="'.acym_escapeUrl($privacyURL).'" target="_blank">'.acym_escape(acym_translation('ACYM_PRIVACY_POLICY')).'</a>';
         }
     }
 
@@ -256,12 +256,12 @@
                 </script>';
 
     $buttonStyle = '';
-    if (!empty($params->get('button_background_color', ''))) $buttonStyle .= 'background-color: '.$params->get('button_background_color', '').';';
-    if (!empty($params->get('button_text_color', ''))) $buttonStyle .= 'color: '.$params->get('button_text_color', '').';';
-    if (strlen($params->get('button_border_size', '')) > 0) $buttonStyle .= 'border-width: '.$params->get('button_border_size', '').'px;';
-    if (!empty($params->get('button_border_type', ''))) $buttonStyle .= 'border-style: '.$params->get('button_border_type', '').';';
-    if (!empty($params->get('button_border_color', ''))) $buttonStyle .= 'border-color: '.$params->get('button_border_color', '').';';
-    if (strlen($params->get('button_border_radius', '')) > 0) $buttonStyle .= 'border-radius: '.$params->get('button_border_radius', '').'px;';
+    if (!empty($params->get('button_background_color', ''))) $buttonStyle .= 'background-color: '.acym_escape($params->get('button_background_color', '')).';';
+    if (!empty($params->get('button_text_color', ''))) $buttonStyle .= 'color: '.acym_escape($params->get('button_text_color', '')).';';
+    if (strlen($params->get('button_border_size', '')) > 0) $buttonStyle .= 'border-width: '.acym_escape($params->get('button_border_size', '')).'px;';
+    if (!empty($params->get('button_border_type', ''))) $buttonStyle .= 'border-style: '.acym_escape($params->get('button_border_type', '')).';';
+    if (!empty($params->get('button_border_color', ''))) $buttonStyle .= 'border-color: '.acym_escape($params->get('button_border_color', '')).';';
+    if (strlen($params->get('button_border_radius', '')) > 0) $buttonStyle .= 'border-radius: '.acym_escape($params->get('button_border_radius', '')).'px;';
 
     if (!empty($buttonStyle)) {
         acym_addStyle(true, '#acym_module_'.$formName.' .acysubbuttons .subbutton {'.$buttonStyle.'}');
@@ -277,24 +277,24 @@
 
     ob_start();
     ?>
-	<div class="acym_module <?php echo acym_escape($formClass); ?>" id="acym_module_<?php echo $formName; ?>">
-		<div class="acym_fulldiv" id="acym_fulldiv_<?php echo $formName; ?>" <?php echo $style; ?>>
+	<div class="acym_module <?php echo acym_escape($formClass); ?>" id="acym_module_<?php echo acym_escape($formName); ?>">
+		<div class="acym_fulldiv" id="acym_fulldiv_<?php echo acym_escape($formName); ?>" <?php echo $style; ?>>
 			<form enctype="multipart/form-data"
-				  id="<?php echo acym_escape($formName); ?>"
-				  name="<?php echo acym_escape($formName); ?>"
-				  method="POST"
-				  action="<?php echo acym_escape($formAction); ?>"
-				  onsubmit="return submitAcymForm('subscribe','<?php echo $formName; ?>')">
+			      id="<?php echo acym_escape($formName); ?>"
+			      name="<?php echo acym_escape($formName); ?>"
+			      method="POST"
+			      action="<?php echo acym_escape($formAction); ?>"
+			      onsubmit="return submitAcymForm('subscribe','<?php echo acym_escape($formName); ?>')">
 				<div class="acym_module_form">
                     <?php
                     $introText = $params->get('introtext', '');
                     if (!empty($introText)) {
-                        echo '<div class="acym_introtext">'.$introText.'</div>';
+                        echo '<div class="acym_introtext">'.acym_escape($introText).'</div>';
                     }
-                    if ($params->get('mode', 'tableless') == 'tableless') {
+                    if ($params->get('mode', 'tableless') === 'tableless') {
                         include ACYM_FOLDER.'widgets'.DS.'subscriptionform'.DS.'tmpl'.DS.'tableless.php';
                     } else {
-                        $displayInline = $params->get('mode', 'tableless') != 'vertical';
+                        $displayInline = $params->get('mode', 'tableless') !== 'vertical';
                         include ACYM_FOLDER.'widgets'.DS.'subscriptionform'.DS.'tmpl'.DS.'default.php';
                     }
                     ?>
@@ -319,7 +319,7 @@
                 <?php
                 $postText = $params->get('posttext', '');
                 if (!empty($postText)) {
-                    echo '<div class="acym_posttext">'.$postText.'</div>';
+                    echo '<div class="acym_posttext">'.acym_escape($postText).'</div>';
                 }
                 ?>

Exploit Outline

To exploit this vulnerability, an attacker with Contributor-level access or higher must create or edit a post and include an AcyMailing shortcode (e.g., [acymailing_form]). The attacker provides a malicious payload for the 'alignment' attribute, such as `center" onmouseover="alert(document.cookie)`. Because the plugin concatenates this value directly into an HTML `style` attribute without escaping, the resulting HTML becomes `<div ... style="text-align: center" onmouseover="alert(document.cookie)" ...>`. When any user (including administrators) views the page and interacts with the element (e.g., by hovering over it), the injected JavaScript executes in their browser context.

Check if your site is affected.

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