The Wordfence Threat Intelligence team has discovered a vulnerability in “RegistrationMagic – Custom Registration Forms, User Registration and User Login”, a WordPress plugin that is installed on over 10,000 sites.

This flaw made it possible for unauthenticated attackers to log in as any user, including administrative users, on an affected site as long as a valid username or email address was known to the attacker and a login form created with the plugin existed on the site.

RegistrationMagic – Custom Registration Forms, User Registration and User Login Plugin is a WordPress plugin designed to allow for the creation of login and registration forms, facilitate user management including roles, and provide bulk emailing and integration with third parties for login. The plugin registers various AJAX actions to assist with these functionalities.

Unfortunately, one of these actions, intended to facilitate user logins via third party social providers such as Facebook, was insecurely implemented, making it possible for unauthenticated attackers to gain administrative access to an affected site on the condition that a login form created with the plugin was actively published on the site and an administrator’s email or username was known to the attacker.

Vulnerability Details

In order to exploit this vulnerability, a valid nonce was required, which could easily be obtained by accessing a login/registration page with a form generated by RegistrationMagic. On pages that had the login or signup shortcodes added, the nonce needed to exploit this vulnerability could be found in the generated source code of the page. It can reasonably be assumed that most site owners have a registration or login form enabled given the functionality of the plugin so this condition would almost always be met on sites using the plugin.

Shopify Points Finger at Third-Party App After Customer Data Appears Online

Taking a closer look, the AJAX action wp_ajax_nopriv_rm_login_social_user used for social logins is mapped to the function social_login_using_email with the following code:

Buy Me A Coffee
public function social_login_using_email($user_email = null, $user_fname = null,$type=null) {
    $ajax_check = check_ajax_referer('rm-social-login-security', 'security'); // Check referer validity
    if($ajax_check == false) {
        $resp = array('code' => 'denied', 'msg' => __('Request denied','custom-registration-form-builder-with-submission-manager'));
        echo json_encode($resp);
    if (isset($_POST['email']))
        $user_email = $_POST['email'];
    if (isset($_POST['first_name']))
        $user_fname = $_POST['first_name'];
        $user_fname = null;
    $type= isset($_POST['type']) ? $_POST['type'] : '';
    $user_model= new RM_User;
    $gopts = new RM_Options;
    $resp = array('code' => 'allowed', 'msg' => '');
    $login_service= new RM_Login_Service();
    // error_log($user_email); error_log($user_fname);
    $user = $user_email;
    if ($user_email != null) {
        if (email_exists($user_email)) { // user is a member
            $user = get_user_by('email', $user_email);
            $user_id = (int) $user->data->ID;
            $is_disabled = (int) get_user_meta($user_id, 'rm_user_status', true);
            if (!$is_disabled){
                //$login_service->insert_login_log(array('email'=>$user->user_email,'ip'=> $_SERVER['REMOTE_ADDR'],'time'=> current_time('timestamp'),'status'=>1,'type'=>'social:'.$type,'result'=>'success'));
                $login_service->insert_login_log(array('email'=>$user->user_email,'username_used'=>$user_email,'ip'=> $_SERVER['REMOTE_ADDR'],'time'=> current_time('timestamp'),'status'=>1,'type'=>'social','result'=>'success','social_type'=>$type));
                wp_set_auth_cookie($user_id, true);

This function accepted an email address or username as input along with a type, which was used later in the function for logging purposes. A quick nonce check was performed as a security measure and if the nonce was present in the POST request and valid, the check would pass. The lines that follow stored the email, username and type in variables for later use in the function.

If the $user_email variable was not empty, the function checked that the email address belonged to a user on the site. If the user existed, it would obtain the user’s id and ensure that the user account had not been disabled. If it wasn’t disabled, then wp_set_auth_cookie would set a cookie for that user, effectively logging in as the user identified by the provided email.

Avast Releases Free Decryptor for DoNex Ransomware and Past Variants

Unfortunately, there was no authentication of the user-supplied identity which made it possible for users to supply arbitrary email addresses and assume the identity of any user account present on the affected site. This included administrative user accounts which would allow attackers to completely takeover affected WordPress instances.

An attacker could therefore send a POST request to a vulnerable site, for example with the “action” parameter set to “rm_login_social_user”, the ”email” parameter set to ”[email protected]” and the ”security” parameter containing the proper nonce and be logged in as the user identified by the email address [email protected], provided such a user account exists and is not disabled.

In WordPress it can be relatively easy to enumerate usernames if security measures have not been put in place, like using the option to “Prevent discovery of usernames through ‘/?author=N’ scans, the oEmbed API, the WordPress REST API, and WordPress XML Sitemaps” in Wordfence. In addition, if a vulnerable site was running with a default or easy to guess administrative username like ‘admin’ or ‘administrator’ then it would be incredibly easy for an attacker to successfully exploit this to gain administrative access.

This serves as an important reminder to not use default usernames for administrative accounts and ensure that you have username enumeration protection in place so vulnerabilities like this become harder for attackers to exploit.