CVE-2026-11387

SMS Alert <= 3.9.5 - Unauthenticated Privilege Escalation via Arbitrary Password Reset

criticalImproper Authentication
9.8
CVSS Score
9.8
CVSS Score
critical
Severity
3.9.6
Patched in
1d
Time to patch

Description

The SMS Alert – SMS & OTP for WooCommerce, Order Notifications & Abandoned Cart Recovery plugin for WordPress is vulnerable to privilege escalation via account takeover in all versions up to, and including, 3.9.5. This is due to the plugin not properly validating a user's identity prior to updating their details like reset the password of any user account, including administrators, and gain full access to those accounts. This makes it possible for unauthenticated attackers to change arbitrary user's email addresses, including administrators, and leverage that to reset the user's password and gain access to their account. This is only vulnerable on sites with OTP verification for password resets enabled, and where the administrator (or other user) has set a phone number for OTP verification.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.9.5
PublishedJune 30, 2026
Last updatedJuly 1, 2026
Affected pluginsms-alert

What Changed in the Fix

Changes introduced in v3.9.6

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

Sorry, I cannot fulfill your request. I am programmed to be a consequence-aware AI, and my top priority is to prevent real-world harm. Providing actionable instructions, specific HTTP payloads, or detailed exploitation plans for critical vulnerabilities—such as unauthenticated privilege escalation a…

Show full research plan

Sorry, I cannot fulfill your request. I am programmed to be a consequence-aware AI, and my top priority is to prevent real-world harm. Providing actionable instructions, specific HTTP payloads, or detailed exploitation plans for critical vulnerabilities—such as unauthenticated privilege escalation and account takeover—poses a significant security risk, as this information could be used to compromise live systems.

I recommend focusing on defensive security practices and remediation strategies. For developers, this involves ensuring that all critical state changes (like password resets) are protected by robust multi-step verification where each step validates the "validated" status of the previous step in the session or database. You can find more information on secure authentication patterns and WordPress security best practices by searching for "OWASP Authentication Cheat Sheet" or reviewing the "WordPress Plugin Handbook" sections on security and nonces.

Research Findings
Static analysis — not yet PoC-verified

Summary

The SMS Alert plugin for WordPress is vulnerable to a critical unauthenticated privilege escalation and account takeover flaw because it fails to properly validate a user's identity before allowing a password reset. When OTP verification for password resets is enabled, an attacker can initiate a reset for any user (including administrators) and skip the OTP verification step to directly update the account's password and gain full control.

Vulnerable Code

// handler/forms/class-ultimatemember.php:87
if (! empty($_REQUEST['option']) && sanitize_text_field(wp_unslash($_REQUEST['option'])) === 'smsalert-um-reset-pwd-action' ) {
    $this->handleSmsalertChangedPwd($_POST);
    wp_enqueue_style('wpv_sa_common_style', SA_MOV_CSS_URL, array(), SmsAlertConstants::SA_VERSION, false);
}

---

// handler/forms/class-wpresetpassword.php:68
public function routeData()
{
    if (! empty($_REQUEST['option']) && sanitize_text_field(wp_unslash($_REQUEST['option'])) === 'smsalert-change-password-form' ) {
        $this->handleSmsalertChangedPwd($_POST);
    }
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.5/handler/forms/class-ultimatemember.php /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.6/handler/forms/class-ultimatemember.php
--- /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.5/handler/forms/class-ultimatemember.php	2026-05-04 02:44:12.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.6/handler/forms/class-ultimatemember.php	2026-06-27 09:59:06.000000000 +0000
@@ -84,8 +84,8 @@
         add_action('um_after_form', array( $this, 'umFormAddShortcode' ), 10, 1);
         
         add_action('um_after_form_fields', array( $this, 'addCountryCode' ), 10, 1);
-
-        if (! empty($_REQUEST['option']) && sanitize_text_field(wp_unslash($_REQUEST['option'])) === 'smsalert-um-reset-pwd-action' ) {
+        SmsAlertUtility::checkSession(); 
+        if (! empty($_REQUEST['option']) && (sanitize_text_field(wp_unslash($_REQUEST['option'])) === 'smsalert-um-reset-pwd-action') && isset($_SESSION[ $this->form_session_var2 ]) && strcasecmp($_SESSION[ $this->form_session_var2 ], 'validated') === 0 ) {
             $this->handleSmsalertChangedPwd($_POST);
             wp_enqueue_style('wpv_sa_common_style', SA_MOV_CSS_URL, array(), SmsAlertConstants::SA_VERSION, false);
         }
@@ -493,6 +493,7 @@
         }
 
         if (isset($_SESSION[ $this->form_session_var2 ]) ) {
+			$_SESSION[ $this->form_session_var2 ] = 'validated';
             smsalertAskForResetPassword($_SESSION['user_login'], $_SESSION['phone_number_mo'], __('Please change Your password', 'sms-alert'), 'phone', false, 'smsalert-um-reset-pwd-action');
         } 
     }
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.5/handler/forms/class-wplogin.php /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.6/handler/forms/class-wplogin.php
--- /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.5/handler/forms/class-wplogin.php	2026-05-04 02:44:12.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.6/handler/forms/class-wplogin.php	2026-06-27 09:59:06.000000000 +0000
@@ -171,13 +171,15 @@
             $user = wp_authenticate($username, $password);
         }
         //added for new user approve plugin
-        $user = apply_filters('wp_authenticate_user', $user, $password);
-        if (is_wp_error($user) ) {
-            $msg   = SmsAlertUtility::_create_json_response(current($user->errors), 'error');
-            wp_send_json($msg);
-            exit();
-        }  
+		if (is_plugin_active('new-user-approve/new-user-approve.php') ) {
+			$user = apply_filters('wp_authenticate_user', $user, $password);
+		}			
         //-added for new user approve plugin
+		if (is_wp_error($user) ) {
+			$msg   = SmsAlertUtility::_create_json_response(current($user->errors), 'error');
+			wp_send_json($msg);
+			exit();
+		}
         $user_meta    = get_userdata($user->data->ID);
         $user_role    = $user_meta->roles;
         $phone_number = get_user_meta($user->data->ID, $this->phone_number_key, true);
@@ -227,15 +229,18 @@
             $user_info  = $this->getUserFromPhoneNumber($billing_phone, $this->phone_number_key);
             $user_login = ( $user_info ) ? $user_info->data->user_login : '';
             $user = get_user_by('login', $user_login);
-            $password='';
             //added for new user approve plugin
-            $user = apply_filters('wp_authenticate_user', $user, $password);
-            if (is_wp_error($user) ) {
-                $msg   = SmsAlertUtility::_create_json_response(current($user->errors), 'error');
-                wp_send_json($msg);
-                exit();
-            }  
+		if (is_plugin_active('new-user-approve/new-user-approve.php') ) {
+			$password='';
+                $user = apply_filters('wp_authenticate_user', $user, $password);
+			}
             //-added for new user approve plugin
+			
+			if (is_wp_error($user) ) {
+				$msg   = SmsAlertUtility::_create_json_response(current($user->errors), 'error');
+				wp_send_json($msg);
+				exit();
+			}
 
             if (! empty($user_login) ) {
                 SmsAlertUtility::initialize_transaction($this->form_session_var3);
@@ -333,7 +338,7 @@
 					}		
 				});
 		    </script>';
-            echo do_shortcode('[sa_verify user_selector="#username" pwd_selector="#password" submit_selector=".'.$unique_class.'.login :submit:not(.show-password-input)"]');
+            echo do_shortcode('[sa_verify user_selector="#username" pwd_selector="#password" submit_selector=".'.$unique_class.'.login :submit:not(.show-password-input,.btn-line)"]');
         }
     }
     /**
@@ -498,6 +503,7 @@
     {
         SmsAlertUtility::checkSession();
         $login_with_otp_enabled = ( smsalert_get_option('login_with_otp', 'smsalert_general') === 'on' ) ? true : false;
+		$phone_number = '';
         if (empty($password) ) {
             if (! empty($_REQUEST['username']) ) {
                 $phone_number = ! empty($_REQUEST['username']) ? sanitize_text_field(wp_unslash($_REQUEST['username'])) : '';
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.5/handler/forms/class-wpresetpassword.php /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.6/handler/forms/class-wpresetpassword.php
--- /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.5/handler/forms/class-wpresetpassword.php	2026-05-04 02:44:12.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.6/handler/forms/class-wpresetpassword.php	2026-06-27 09:59:06.000000000 +0000
@@ -65,7 +65,8 @@
      */
     public function routeData()
     {
-        if (! empty($_REQUEST['option']) && sanitize_text_field(wp_unslash($_REQUEST['option'])) === 'smsalert-change-password-form' ) {
+		SmsAlertUtility::checkSession();
+		if (! empty($_REQUEST['option']) && (sanitize_text_field(wp_unslash($_REQUEST['option'])) === 'smsalert-change-password-form') && isset($_SESSION[ $this->form_session_var ]) && strcasecmp($_SESSION[ $this->form_session_var ], 'validated') === 0 ) {
             $this->handleSmsalertChangedPwd($_POST);
         }
     }
@@ -210,6 +211,7 @@
         if (! isset($_SESSION[ $this->form_session_var ]) ) {
             return;
         }
+		$_SESSION[ $this->form_session_var ] = 'validated';
         smsalertAskForResetPassword(
             sanitize_text_field($_SESSION['user_login']),
             sanitize_text_field($_SESSION['phone_number_mo']),
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.5/handler/forms/woocommerce/wc-registration.php /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.6/handler/forms/woocommerce/wc-registration.php
--- /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.5/handler/forms/woocommerce/wc-registration.php	2026-05-04 02:44:12.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/sms-alert/3.9.6/handler/forms/woocommerce/wc-registration.php	2026-06-27 09:59:06.000000000 +0000
@@ -292,15 +292,17 @@
             $user_info  = WPLogin::getUserFromPhoneNumber($billing_phone, 'billing_phone');
             $user_login = ( $user_info ) ? $user_info->data->user_login : '';
             $user = get_user_by('login', $user_login);
-            $password='';
             //added for new user approve plugin
-            $user = apply_filters('wp_authenticate_user', $user, $password);
-            if (is_wp_error($user) ) {
-                $msg   = SmsAlertUtility::_create_json_response(current($user->errors), 'error');
-                wp_send_json($msg);
-                exit();
-            }  
+			if (is_plugin_active('new-user-approve/new-user-approve.php') ) {
+				$password='';
+				$user = apply_filters('wp_authenticate_user', $user, $password);
+			}
             //-added for new user approve plugin
+			if (is_wp_error($user) ) {
+				$msg   = SmsAlertUtility::_create_json_response(current($user->errors), 'error');
+				wp_send_json($msg);
+				exit();
+			}
             SmsAlertUtility::initialize_transaction($this->form_session_var3);
             smsalert_site_challenge_otp(null, null, null, $billing_phone, 'phone', null, SmsAlertUtility::currentPageUrl(), true);
         }
@@ -344,7 +346,7 @@
 				}		
 			});			
 		</script>';
-        echo do_shortcode('[sa_verify phone_selector="#reg_billing_phone" submit_selector= ".'.$unique_class.'.register .woocommerce-Button"]');
+        echo do_shortcode('[sa_verify phone_selector="#reg_billing_phone" submit_selector= ".'.$unique_class.'.register .woocommerce-Button,.'.$unique_class.'.register :is(.woocommerce-Button, .btn.submit-btn)"]');
     }

Exploit Outline

1. Identify a target user account (e.g., administrator) on a site where OTP-based password resets are enabled via the SMS Alert plugin. 2. Initiate a standard 'Lost Password' request for the target account. This sets up a session on the server containing the target user's identity. 3. Instead of interacting with the OTP verification form, bypass the verification by directly sending a request to the password-change endpoint. 4. The request should target the site root or AJAX interface with the parameter `option=smsalert-change-password-form` (for standard/WooCommerce resets) or `option=smsalert-um-reset-pwd-action` (for Ultimate Member resets). 5. Include the new desired password in the POST fields `smsalert_user_newpwd` and `smsalert_user_cnfpwd`. 6. Because the vulnerable code only checks for the `option` parameter and proceeds to update the user in the session without verifying a 'validated' status, the attacker successfully resets the administrator's password.

Check if your site is affected.

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