key_login_msg = 'aiowps_login_msg_id'; // As a first authentication step, check if user's IP is locked. add_filter('authenticate', array($this, 'block_ip_if_locked'), 1, 1); // Check whether user needs to be manually approved after default WordPress authenticate hooks (with priority 20). add_filter('authenticate', array($this, 'check_manual_registration_approval'), 30, 1); // Check login CAPTCHA if ($aio_wp_security->configs->get_value('aiowps_enable_login_captcha')) { add_filter('authenticate', array($this, 'check_captcha'), 20, 1); } // As a last authentication step, perform post authentication steps add_filter('authenticate', array($this, 'post_authenticate'), 100, 3); add_action('aiowps_force_logout_check', array($this, 'aiowps_force_logout_action_handler')); add_action('wp_logout', array($this, 'wp_logout_action_handler'), 10, 1); add_filter('login_message', array($this, 'aiowps_login_message')); //WP filter to add or modify messages on the login page // Display disable lockdown message if (is_admin() && AIOWPSecurity_Utility_Permissions::has_manage_cap() && $aio_wp_security->is_login_lockdown_by_const() && $this->is_admin_page_to_display_disable_login_lockdown_by_const_notice()) { add_action('all_admin_notices', array($this, 'disable_login_lockdown_by_const_notice')); } add_action('set_auth_cookie', array($this, 'handle_logged_in_user'), 10, 4); //cron job to remove expired users from logged_in table add_action('delete_expired_logged_in_users_event', array($this, 'delete_expired_logged_in_users')); add_filter('retrieve_password_message', array($this, 'aiowps_retrieve_password_message'), 10, 1); } /** * Check whether the admin page is to display disable login lockdown by const notice. * * @return boolean True if the notice will be displayed, Otherwise false. */ private function is_admin_page_to_display_disable_login_lockdown_by_const_notice() { global $pagenow; if (in_array($pagenow, array('index.php', 'plugins.php'))) { return true; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. Ignore. } elseif (('admin.php' == $pagenow && isset($_GET['page']) && false !== strpos(sanitize_title(wp_unslash($_GET['page'])), AIOWPSEC_MENU_SLUG_PREFIX)) && !$this->is_locked_ip_addresses_tab_admin_page()) { return true; } return false; } /** * Check whether the admin page is Locked IP Addresses Tab page. * * @return boolean True if is Locked IP Addresses Tab page, Otherwise false. */ private function is_locked_ip_addresses_tab_admin_page() { global $pagenow; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce. return ('admin.php' == $pagenow && isset($_GET['page']) && 'aiowpsec' == $_GET['page'] && isset($_GET['tab']) && 'locked-ip' == $_GET['tab']); } /** * Displays admin to disable lockdown message. * * @return Void */ public function disable_login_lockdown_by_const_notice() { if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) { return; } echo '

'. esc_html__('You have disabled login lockout by defining the AIOS_DISABLE_LOGIN_LOCKOUT constant value as true, and the login lockout setting has enabled it.', 'all-in-one-wp-security-and-firewall') . ' ' . /* translators: 1: Locked IP Addresses admin page link */ sprintf(esc_html__('Delete your login lockout IP from %s and define the AIOS_DISABLE_LOGIN_LOCKOUT constant value as false.', 'all-in-one-wp-security-and-firewall'), '' . esc_html__('Locked IP addresses', 'all-in-one-wp-security-and-firewall') . '' ). '

'; } /** * Terminate the execution via wp_die with 503 status code, if current * user's IP is currently locked. * * @global AIO_WP_Security $aio_wp_security * @param WP_Error|WP_User $user * @return WP_User */ public function block_ip_if_locked($user) { global $aio_wp_security; // Allow users to login when disable AIOWPS_DISABLE_LOCK_DOWN defined true if ($aio_wp_security->is_login_lockdown_by_const()) { return $user; } $user_locked = $this->check_locked_user(); if (null != $user_locked) { $aio_wp_security->debug_logger->log_debug("Login attempt from blocked IP range - ".$user_locked['failed_login_ip'], 2); // Allow the error message to be filtered. /* translators: %s: Error notification with strong HTML tag. */ $error_msg = apply_filters('aiowps_ip_blocked_error_msg', sprintf(__('%s: Access from your IP address has been blocked for security reasons.', 'all-in-one-wp-security-and-firewall'), '' . __('ERROR', 'all-in-one-wp-security-and-firewall') . '') . ' ' . __('Please contact the administrator.', 'all-in-one-wp-security-and-firewall')); // If unlock requests are allowed, add the "Request Unlock" button to the message. $unlock_form = ''; if ($aio_wp_security->configs->get_value('aiowps_allow_unlock_requests') == '1') { $unlock_form = $this->get_unlock_request_form(); $error_msg .= $unlock_form; } $error_msg = apply_filters('aiowps_ip_blocked_output_page', $error_msg, $unlock_form); //filter the complete output of the locked page // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Can not escape form html inside $error_msg. wp_die($error_msg, esc_html__('Service temporarily unavailable', 'all-in-one-wp-security-and-firewall'), 503); } else { return $user; } } /** * Check login CAPTCHA (if enabled). * * @global AIO_WP_Security $aio_wp_security * @param WP_Error|WP_User $user * @return WP_Error|WP_User */ public function check_captcha($user) { global $aio_wp_security; if (is_wp_error($user) || $aio_wp_security->is_login_lockdown_by_const()) { // Authentication has failed already at some earlier step. return $user; } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- PCP warning. No nonce. if (! (isset($_POST['log']) && isset($_POST['pwd']))) { // XML-RPC authentication (not via wp-login.php), nothing to do here. return $user; } if ($aio_wp_security->configs->get_value('aiowps_enable_login_captcha') != '1') { // CAPTCHA not enabled, nothing to do here. return $user; } /* translators: %s: Error notification with strong HTML tag. */ $captcha_error = new WP_Error('authentication_failed', sprintf(__('%s: Your answer was incorrect - please try again.', 'all-in-one-wp-security-and-firewall'), '' . __('ERROR', 'all-in-one-wp-security-and-firewall') . '')); $verify_captcha = $aio_wp_security->captcha_obj->verify_captcha_submit(); if (false === $verify_captcha) { return $captcha_error; } return $user; } /** * Check, whether $user needs to be manually approved by site admin yet. * * @global AIO_WP_Security $aio_wp_security * @param WP_Error|WP_User $user * @return WP_Error|WP_User */ public function check_manual_registration_approval($user) { global $aio_wp_security; if (!($user instanceof WP_User)) { // Not a WP_User - nothing to do here. return $user; } //Check if auto pending new account status feature is enabled if ($aio_wp_security->configs->get_value('aiowps_enable_manual_registration_approval') == '1') { $aiowps_account_status = get_user_meta($user->ID, 'aiowps_account_status', true); if ('pending' == $aiowps_account_status) { // Account needs to be activated yet /* translators: %s: Notification with strong HTML tag. */ return new WP_Error('account_pending', sprintf(__('%s: Your account is currently not active.', 'all-in-one-wp-security-and-firewall'), '' . __('ACCOUNT PENDING', 'all-in-one-wp-security-and-firewall') . '') . ' '. __('An administrator needs to activate your account before you can login.', 'all-in-one-wp-security-and-firewall')); } } return $user; } /** * Handle post authentication steps (in case of failed login): * - increment number of failed logins for $username * - (optionally) lock the user * - (optionally) display a generic error message * * @global AIO_WP_Security $aio_wp_security * @param WP_Error|WP_User $user * @param string $username * @param string $password * @return WP_Error|WP_User */ public function post_authenticate($user, $username, $password) { global $aio_wp_security; if (!is_wp_error($user)) { // Authentication has been successful, there's nothing to do here. return $user; } if (empty($username) || empty($password)) { // Neither log nor block login attempts with empty username or password. return $user; } if ($user->get_error_code() === 'account_pending') { // Neither log nor block users attempting to log in before their registration is approved. return $user; } // Login failed for non-trivial reason AIOWPSecurity_Audit_Events::event_failed_login($username); if ($aio_wp_security->configs->get_value('aiowps_enable_login_lockdown') == '1') { $is_whitelisted = false; //check if lockout whitelist enabled if ($aio_wp_security->configs->get_value('aiowps_lockdown_enable_whitelisting') == '1') { $whitelisted_ips = $aio_wp_security->configs->get_value('aiowps_lockdown_allowed_ip_addresses'); $is_whitelisted = AIOWPSecurity_Utility_IP::is_userip_whitelisted($whitelisted_ips); } if (false === $is_whitelisted) { // Too many failed logins from user's IP? $login_attempts_permitted = absint($aio_wp_security->configs->get_value('aiowps_max_login_attempts')); $too_many_failed_logins = $login_attempts_permitted <= $this->get_login_fail_count(); // Is an invalid username or email the reason for login error? $invalid_username = ($user->get_error_code() === 'invalid_username' || $user->get_error_code() == 'invalid_email'); // Should an invalid username be immediately locked? $invalid_username_lockdown = $aio_wp_security->configs->get_value('aiowps_enable_invalid_username_lockdown') == '1'; $lock_invalid_username = $invalid_username && $invalid_username_lockdown; // Should an invalid username be blocked as per blacklist? $instant_lockout_users_list = $aio_wp_security->configs->get_value('aiowps_instantly_lockout_specific_usernames'); if (!is_array($instant_lockout_users_list)) { $instant_lockout_users_list = array(); } $username_blacklisted = $invalid_username && in_array($username, $instant_lockout_users_list); $lock_reasons = array(); if ($too_many_failed_logins) { $lock_reasons[] = 'too_many_failed_logins'; } if ($lock_invalid_username) { $lock_reasons[] = 'invalid_username'; } if ($username_blacklisted) { $lock_reasons[] = 'username_blacklisted'; } if ($lock_reasons) { $this->lock_the_user($username, implode(',', $lock_reasons)); } } } if ($aio_wp_security->configs->get_value('aiowps_set_generic_login_msg') == '1') { // Return generic error message if configured return new WP_Error('authentication_failed', __('ERROR: Invalid login credentials.', 'all-in-one-wp-security-and-firewall')); } return $user; } /** * This function queries the aiowps_login_lockdown table. * If the release_date has not expired AND the current visitor IP addr matches * it will return a record */ public function check_locked_user() { global $wpdb; $login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKOUT; $ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); //Get the IP address of user if (empty($ip)) return false; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore. $locked_user = $wpdb->get_row($wpdb->prepare("SELECT * FROM $login_lockdown_table WHERE `released` > UNIX_TIMESTAMP() AND `failed_login_ip` = %s", $ip), ARRAY_A); return $locked_user; } /** * This function queries the aiowps_audit_log table and returns the number of failures for current IP range within allowed failure period */ public function get_login_fail_count() { global $wpdb, $aio_wp_security; $audit_log_table = AIOWPSEC_TBL_AUDIT_LOG; $login_retry_interval = $aio_wp_security->configs->get_value('aiowps_retry_time_period') * 60; $now = time(); $ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); // Get the users IP address if (empty($ip)) return false; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore. $login_failures = $wpdb->get_var("SELECT COUNT(ID) FROM $audit_log_table " . "WHERE created + " . esc_sql($login_retry_interval) . " > '" . esc_sql($now) . "' AND " . "ip = '" . esc_sql($ip) . "' AND event_type = 'failed_login'"); return $login_failures; } /** * Get lockout time dynamically multiplied with default lockout time * * @return Integer get lockout time length. */ public function get_dynamic_lockout_time_length() { global $aio_wp_security; $login_fail_count = $this->get_login_fail_count(); $lockout_time_default = $aio_wp_security->configs->get_value('aiowps_lockout_time_length'); if (!is_numeric($lockout_time_default)) { $lockout_time_default = 5; } $lockout_time_max = $aio_wp_security->configs->get_value('aiowps_max_lockout_time_length'); if (!is_numeric($lockout_time_max)) { $lockout_time_max = 60; } $lockout_time_length = (int) ($login_fail_count > 0 ? (3 * $lockout_time_default * ($login_fail_count + 1)) : $lockout_time_default); return $lockout_time_length >= $lockout_time_max ? $lockout_time_max : $lockout_time_length; } /** * Adds an entry to the `aiowps_login_lockdown` table. * * @param string $username User's username or email * @param string $lock_reason * @param bool $is_lockout_email_sent flag for lockout email send */ public function lock_the_user($username, $lock_reason = 'login_fail', $is_lockout_email_sent = 0) { global $aio_wp_security; $login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKOUT; $lock_minutes = $this->get_dynamic_lockout_time_length(); $ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); //Get the IP address of user if (empty($ip)) return; $ip_range = AIOWPSecurity_Utility_IP::get_sanitized_ip_range($ip); //Get the IP range of the current user $user = is_email($username) ? get_user_by('email', $username) : get_user_by('login', $username); //Returns WP_User object if exists $ip_range = apply_filters('aiowps_before_lockdown', $ip_range); if ($user) { //If the login attempt was made using a valid user set variables for DB storage later on $user_id = $user->ID; } else { //If the login attempt was made using a non-existent user then let's set user_id to blank and record the attempted user login name for DB storage later on $user_id = 0; } $lock_time = current_time('mysql', true); $date = new DateTime($lock_time); $add_interval = 'PT'.absint($lock_minutes).'M'; $date->add(new DateInterval($add_interval)); $release_time = $date->format('Y-m-d H:i:s'); $backtrace_log = ''; if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_php_backtrace_in_email')) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace, PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection -- PCP and compatibility warnings. Safe to ignore. $backtrace_log = AIOWPSecurity_Utility::normalise_call_stack_args(debug_backtrace()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- PCP warning. Ignore. $backtrace_log = print_r($backtrace_log, true); } $is_lockout_email_sent = (1 == $aio_wp_security->configs->get_value('aiowps_enable_email_notify') ? 0 : -1); $ip_lookup_result = AIOS_Helper::get_ip_reverse_lookup($ip); $ip_lookup_result = wp_json_encode($ip_lookup_result); if (false === $ip_lookup_result) $ip_lookup_result = null; $lock_seconds = $lock_minutes * MINUTE_IN_SECONDS; $data = array( 'user_id' => $user_id, 'user_login' => $username, 'lockdown_date' => $lock_time, 'release_date' => $release_time, 'failed_login_IP' => $ip, 'lock_reason' => $lock_reason, 'is_lockout_email_sent' => $is_lockout_email_sent, 'backtrace_log' => $backtrace_log, 'ip_lookup_result' => $ip_lookup_result, 'lock_seconds' => $lock_seconds ); $result = AIOWPSecurity_Utility::add_lockout($data); if (false === $result) { $aio_wp_security->debug_logger->log_debug("Error inserting record into ".$login_lockdown_table, 4); } else { do_action('aiowps_lockdown_event', $ip_range, $username); $aio_wp_security->debug_logger->log_debug("The following IP address range has been locked out for exceeding the maximum login attempts: ".$ip_range, 2); } } /** * Send IP Lock notification. * * @param Array $lockout_ips_list have username, ip_range, ip * @param String $backtrace_filepath * * @return Boolean True if mail sent otherwise false. */ private function send_ip_lock_notification_email($lockout_ips_list = array(), $backtrace_filepath = '') { global $aio_wp_security; $send_mail = false; if (0 != count($lockout_ips_list)) { $email_notification_enabled = $aio_wp_security->configs->get_value('aiowps_enable_email_notify'); if (1 == $email_notification_enabled) { $to_email_address = AIOWPSecurity_Utility::get_array_from_textarea_val($aio_wp_security->configs->get_value('aiowps_email_address')); if (empty($to_email_address)) { $to_email_address = array(get_site_option('admin_email')); } $subject = '['.get_option('home').'] '. __('Site Lockout Notification', 'all-in-one-wp-security-and-firewall'); $email_msg = __('User login lockout events had occurred due to too many failed login attempts or invalid username:', 'all-in-one-wp-security-and-firewall')."\n\n"; foreach ($lockout_ips_list as $lockout_ip) { /* translators: %s: User name. */ $email_msg .= sprintf(__('Username: %s', 'all-in-one-wp-security-and-firewall'), $lockout_ip['username']) . "\n"; /* translators: %s: IP Address. */ $email_msg .= sprintf(__('IP address: %s', 'all-in-one-wp-security-and-firewall'), $lockout_ip['ip']) . "\n"; if ('' != $lockout_ip['ip_range']) { /* translators: %s: IP Range. */ $email_msg .= sprintf(__('IP range: %s', 'all-in-one-wp-security-and-firewall'), $lockout_ip['ip_range']) . '.*' . "\n"; } if (!empty($lockout_ip['ip_lookup_result'])) { $ip_lookup_result = json_decode($lockout_ip['ip_lookup_result'], true); $org = empty($ip_lookup_result['org']) ? __('Not Found', 'all-in-one-wp-security-and-firewall') : $ip_lookup_result['org']; $as = empty($ip_lookup_result['as']) ? __('Not Found', 'all-in-one-wp-security-and-firewall') : $ip_lookup_result['as']; /* translators: %s: Org. */ $email_msg .= sprintf(__('Org: %s', 'all-in-one-wp-security-and-firewall'), $org) . "\n"; /* translators: %s: AS. */ $email_msg .= sprintf(__('AS: %s', 'all-in-one-wp-security-and-firewall'), $as) . "\n"; $email_msg = apply_filters('aiowps_login_lockdown_email_message', $email_msg, $ip_lookup_result); } $email_msg .= "\n"; } $email_msg .= __("Log into your site WordPress administration panel to see the duration of the lockout or to unlock the user.", 'all-in-one-wp-security-and-firewall') . "\n"; $email_header = ''; $send_mail = wp_mail($to_email_address, $subject, $email_msg, $email_header, $backtrace_filepath); if (false === $send_mail) { $ips_list = implode(', ', wp_list_pluck($lockout_ips_list, 'ip')); $aio_wp_security->debug_logger->log_debug("Lockout notification email failed to send to " . implode(', ', $to_email_address) . " for IPs ".$ips_list, 4); } } } return $send_mail; } /** * Generates and returns an unlock request link which will be used to send to the user. * * @global type $wpdb * @global AIO_WP_Security $aio_wp_security * @param type $ip_range * @return string or false on failure */ public static function generate_unlock_request_link($ip_range) { //Get the locked user row from locout table global $wpdb, $aio_wp_security; $unlock_link = ''; $lockout_table_name = AIOWPSEC_TBL_LOGIN_LOCKOUT; $secret_rand_key = (md5(uniqid(wp_rand(), true))); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP wanting. Ignore. $res = $wpdb->query($wpdb->prepare("UPDATE $lockout_table_name SET unlock_key = %s WHERE released > UNIX_TIMESTAMP() AND failed_login_ip LIKE %s", $secret_rand_key, "%" . esc_sql($ip_range) . "%")); if (null == $res) { $aio_wp_security->debug_logger->log_debug("No locked user found with IP range ".$ip_range, 4); return false; } else { // Check if unlock request or submitted from a WooCommerce account login page if (isset($_POST['aiowps-woo-login'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- PCP warning. No nonce. $date_time = current_time('mysql'); $data = array('date_time' => $date_time, 'meta_key1' => 'woo_unlock_request_key', 'meta_value1' => $secret_rand_key); $aiowps_global_meta_tbl_name = AIOWPSEC_TBL_GLOBAL_META_DATA; // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder, WordPress.DB.PreparedSQL.NotPrepared -- PCP error. Direct query required. Table name cannot be prepared pre WP 6.2. $sql = $wpdb->prepare("INSERT INTO ".$aiowps_global_meta_tbl_name." (date_time, meta_key1, meta_value1, created) VALUES ('%s', '%s', '%s', UNIX_TIMESTAMP())", $data['date_time'], $data['meta_key1'], $data['meta_value1']); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Prepared above. $result = $wpdb->query($sql); if (false === $result) { $aio_wp_security->debug_logger->log_debug("generate_unlock_request_link() - Error inserting woo_unlock_request_key to AIOWPSEC_TBL_GLOBAL_META_DATA table for secret key ".$secret_rand_key, 4); } } $query_param = array('aiowps_auth_key' => $secret_rand_key); $wp_site_url = AIOWPSEC_WP_URL; $unlock_link = esc_url(add_query_arg($query_param, $wp_site_url)); } return $unlock_link; } /** * This function will process an unlock request when someone clicks on the special URL * It will check if the special random code matches that in lockdown table for the relevant user * If so, it will unlock the user * * @param string $unlock_key * @return void */ public static function process_unlock_request($unlock_key) { global $wpdb, $aio_wp_security; $lockout_table_name = AIOWPSEC_TBL_LOGIN_LOCKOUT; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- PCP error. Direct query required. Table name cannot be prepared pre WP 6.2. $unlock_command = $wpdb->prepare("UPDATE ".$lockout_table_name." SET released = UNIX_TIMESTAMP() WHERE unlock_key = %s", $unlock_key); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Prepared above. $result = $wpdb->query($unlock_command); if (false === $result) { $aio_wp_security->debug_logger->log_debug("Error unlocking user with unlock_key ".$unlock_key, 4); } else { // Now check if this unlock operation is for a WooCommerce login $aiowps_global_meta_tbl_name = AIOWPSEC_TBL_GLOBAL_META_DATA; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- PCP error. Direct query required. Table name cannot be prepared pre WP 6.2. $sql = $wpdb->prepare("SELECT * FROM $aiowps_global_meta_tbl_name WHERE meta_key1=%s AND meta_value1=%s", 'woo_unlock_request_key', $unlock_key); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Prepared above. $woo_result = $wpdb->get_row($sql, OBJECT); if (empty($woo_result)) { $woo_unlock = false; } else { $woo_unlock = true; } if ($aio_wp_security->configs->get_value('aiowps_enable_rename_login_page')=='1') { if (get_option('permalink_structure')) { $home_url = trailingslashit(home_url()); } else { $home_url = trailingslashit(home_url()) . '?'; } if ($woo_unlock) { $login_url = wc_get_page_permalink('myaccount'); //redirect to woo login page if applicable //Now let's cleanup after ourselves and delete the woo-related row in the AIOWPSEC_TBL_GLOBAL_META_DATA table // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore $delete = $wpdb->delete($aiowps_global_meta_tbl_name, array('meta_key1' => 'woo_unlock_request_key', 'meta_value1' => $unlock_key)); if (false === $delete) { $aio_wp_security->debug_logger->log_debug("process_unlock_request(): Error deleting row from AIOWPSEC_TBL_GLOBAL_META_DATA for meta_key1=woo_unlock_request_key and meta_value1=".$unlock_key, 4); } } else { $login_url = $home_url.$aio_wp_security->configs->get_value('aiowps_login_page_slug'); } AIOWPSecurity_Utility::redirect_to_url($login_url); } else { AIOWPSecurity_Utility::redirect_to_url(wp_login_url()); } } } /** * This function sends an unlock request email to a locked out user * * @param string $email * @param string $unlock_link * @return void */ public static function send_unlock_request_email($email, $unlock_link) { global $aio_wp_security; $subject = '['.network_site_url().'] '. __('Unlock request notification', 'all-in-one-wp-security-and-firewall'); /* translators: 1: Email 2: Link */ $email_msg = sprintf(__('You have requested for the account with email address %s to be unlocked.', 'all-in-one-wp-security-and-firewall') . ' ' . __('Please press the link below to unlock your account:', 'all-in-one-wp-security-and-firewall'), $email) . "\n" . sprintf(__('Unlock link: %s', 'all-in-one-wp-security-and-firewall'), $unlock_link) . "\n\n" . __('After pressing the above link you will be able to login to the WordPress administration panel.', 'all-in-one-wp-security-and-firewall') . "\n"; $sendMail = wp_mail($email, $subject, $email_msg); if (false === $sendMail) { $aio_wp_security->debug_logger->log_debug("Unlock Request Notification email failed to send to " . $email, 4); } } /** * Check the settings and log the user after the configured time period * * @param bool $return_url Optional. If true, the function returns the logout URL with a nonce. * Otherwise, it redirects to the logout URL. Default is false. * * @return void|string */ public function aiowps_force_logout_action_handler($return_url = false) { global $aio_wp_security; //$aio_wp_security->debug_logger->log_debug("Force Logout - Checking if any user need to be logged out..."); //if this feature is enabled then do something if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_forced_logout')) { if (is_user_logged_in()) { $current_user = wp_get_current_user(); $user_id = $current_user->ID; $current_time = current_time('mysql', true); $login_time = $this->get_wp_user_aiowps_last_login_time($user_id); if (empty($login_time)) { return; } $diff = strtotime($current_time) - strtotime($login_time); $logout_time_interval_value = $aio_wp_security->configs->get_value('aiowps_logout_time_period'); $logout_time_interval_val_seconds = $logout_time_interval_value * 60; if ($diff > $logout_time_interval_val_seconds) { $aio_wp_security->debug_logger->log_debug("Force Logout - This user logged in more than (".$logout_time_interval_value.") minutes ago. Doing a force log out for the user with username: ".$current_user->user_login); $this->wp_logout_action_handler($user_id); //this will register the logout time/date in the logout_date column $curr_page_url = AIOWPSecurity_Utility::get_current_page_url(); $after_logout_payload = array('redirect_to' => $curr_page_url, 'msg' => $this->key_login_msg.'=session_expired'); //Save some of the logout redirect data to a transient is_multisite() ? set_site_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60) : set_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60); $logout_url = AIOWPSEC_WP_URL.'?aiowpsec_do_log_out=1'; $logout_url = AIOWPSecurity_Utility::add_query_data_to_url($logout_url, 'al_additional_data', '1'); $logout_url_with_nonce = html_entity_decode(wp_nonce_url($logout_url, 'aio_logout')); if ($return_url) { return $logout_url_with_nonce; } AIOWPSecurity_Utility::redirect_to_url($logout_url_with_nonce); } } } } /** * Get last logged in time of given user id. * * @param integer $user_id * @return mixed Last login time. False for an invalid $user_id (non-numeric, zero, or negative value). An empty string if a valid but non-existing user ID is passed. */ public function get_wp_user_aiowps_last_login_time($user_id) { $last_login = apply_filters('aiowps_get_last_login_time', get_user_meta($user_id, 'aiowps_last_login_time', true), $user_id); return $last_login; } /** * Updates the last login time in user meta, the login activity table. * * @global wpdb $wpdb * @global AIO_WP_Security $aio_wp_security * * @param string $user_login * @param WP_User $user * * @return void */ private static function update_login_activity($user_login, $user) { AIOWPSecurity_Audit_Events::event_successful_login($user_login); $login_date_time = current_time('mysql', true); update_user_meta($user->ID, 'aiowps_last_login_time', $login_date_time); //store last login time in meta table } /** * Remove the last login time for all users from meta table on deactivation. * * @return void */ public static function remove_login_activity() { delete_metadata('user', '0', 'aiowps_last_login_time', '', true); //remove from meta table for all users last login time } public static function wp_login_action_handler($user_login, $user = '') { global $aio_wp_security; if ('' == $user) { //Try and get user object $user = get_user_by('login', $user_login); //This should return WP_User obj if (!$user) { $aio_wp_security->debug_logger->log_debug("AIOWPSecurity_User_Login::wp_login_action_handler: Unable to get WP_User object for login ".$user_login, 4); return; } } if (is_super_admin($user->ID)) { $logging_into_correct_site = true; } else { $user_sites = get_blogs_of_user($user->ID); $current_site_id = get_current_blog_id(); $logging_into_correct_site = false; foreach ($user_sites as $site) { if ($site->userblog_id == $current_site_id) { $logging_into_correct_site = true; break; } } } if ($logging_into_correct_site) { self::update_login_activity($user_login, $user); } else { $user_primary_site = get_active_blog_for_user($user->ID); switch_to_blog($user_primary_site->blog_id); self::update_login_activity($user_login, $user); restore_current_blog(); } } /** * Handles logout events and modifies the login activity record for the current user. * * @param int $user_id - ID of user logging out * @param boolean $force_logout - if user is force logged out * * @return void */ public function wp_logout_action_handler($user_id, $force_logout = false) { global $aio_wp_security; $user = get_userdata($user_id); if (false === $user) { $aio_wp_security->debug_logger->log_debug("AIOWPSecurity_User_Login::wp_logout_action_handler: Unable to get WP_User object", 4); return; } $this->delete_logged_in_user($user->ID); if (is_super_admin($user->ID)) { $logging_out_of_correct_site = true; } else { $user_sites = get_blogs_of_user($user->ID); $current_site_id = get_current_blog_id(); $logging_out_of_correct_site = false; foreach ($user_sites as $site) { if ($site->userblog_id == $current_site_id) { $logging_out_of_correct_site = true; break; } } } if ($logging_out_of_correct_site) { AIOWPSecurity_Audit_Events::event_successful_logout($user->user_login, $force_logout); } else { $user_primary_site = get_active_blog_for_user($user->ID); switch_to_blog($user_primary_site->blog_id); AIOWPSecurity_Audit_Events::event_successful_logout($user->user_login, $force_logout); restore_current_blog(); } } /** * The handler for the WP "login_message" filter * Adds custom messages to the other messages that appear above the login form. * * NOTE: This method is automatically called by WordPress for displaying * text above the login form. * * @param string $message the output from earlier login_message filters * @return string */ public function aiowps_login_message($message = '') { global $aio_wp_security; $msg = ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No nonce. if (isset($_GET[$this->key_login_msg]) && !empty($_GET[$this->key_login_msg])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No nonce. $logout_msg = wp_strip_all_tags(sanitize_title(wp_unslash($_GET[$this->key_login_msg]))); } if (!empty($logout_msg)) { switch ($logout_msg) { case 'session_expired': /* translators: %s: Minute count */ $msg = sprintf(__('Your session has expired because it has been over %d minutes since your last login.', 'all-in-one-wp-security-and-firewall'), $aio_wp_security->configs->get_value('aiowps_logout_time_period')); $msg .= ' ' . __('Please log back in to continue.', 'all-in-one-wp-security-and-firewall'); break; case 'admin_user_changed': $msg = __('You were logged out because you just changed the "admin" username.', 'all-in-one-wp-security-and-firewall'); $msg .= ' ' . __('Please log back in to continue.', 'all-in-one-wp-security-and-firewall'); break; default: } } if (!empty($msg)) { $msg = htmlspecialchars($msg, ENT_QUOTES, 'UTF-8'); $message .= '

'. $msg . '

'; } return $message; } /** * This function will generate an unlock request form to be inserted inside * error message when user gets locked out. * * @return string */ public function get_unlock_request_form() { global $aio_wp_security; $unlock_request_form = ''; //Let's encode some hidden data and make a form $unlock_secret_string = $aio_wp_security->configs->get_value('aiowps_unlock_request_secret_key'); $current_time = time(); $enc_result = base64_encode($current_time.$unlock_secret_string); $unlock_request_form .= '
'; $unlock_request_form .= ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- No nonce. if (isset($_POST['woocommerce-login-nonce'])) { $unlock_request_form .= ''; } $unlock_request_form .= '
'; return $unlock_request_form; } /** * Returns all logged in users for specific subsite of multisite installation. * * @param bool $sitewide - checks if logged in users should be fetched sitewide * * @return array */ public static function get_logged_in_users($sitewide = true) { global $wpdb; $logged_in_users_table = AIOWPSEC_TBL_LOGGED_IN_USERS; if ($sitewide) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore. $users_online = $wpdb->get_results("SELECT * FROM `{$logged_in_users_table}`", 'ARRAY_A'); } else { $current_blog_id = get_current_blog_id(); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore. $users_online = $wpdb->get_results($wpdb->prepare("SELECT * FROM `{$logged_in_users_table}` WHERE site_id = %d", $current_blog_id), 'ARRAY_A'); } if (empty($users_online)) return array(); return $users_online; } /** * Send email notification to an user that has flag is_lockout_email_sent is 0. * * @return Void */ public function send_login_lockout_emails() { global $wpdb, $aio_wp_security; // if user enabled notification email then only have to send $email_notification_enabled = $aio_wp_security->configs->get_value('aiowps_enable_email_notify'); if (0 == $email_notification_enabled) { return; } // get recent lockout records on top to notify // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- PCP error. Direct query required. $sql = $wpdb->prepare('SELECT id, user_login, failed_login_ip, backtrace_log, ip_lookup_result FROM ' .AIOWPSEC_TBL_LOGIN_LOCKOUT. ' WHERE is_lockout_email_sent = %d ORDER BY id DESC', 0); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Prepared above. $result = $wpdb->get_results($sql); if (empty($result)) { return; } $login_lockout_ids_send_emails = array(); $lockout_ips_backtrace_log = array(); $lockout_ips_list = array(); $backtrace_filepath = ''; foreach ($result as $row) { $ip_range = AIOWPSecurity_Utility_IP::get_sanitized_ip_range($row->failed_login_ip); $lockout_ips_list[] = array('username' => $row->user_login, 'ip' => $row->failed_login_ip, 'ip_range' => $ip_range, 'ip_lookup_result' => $row->ip_lookup_result); $login_lockout_ids_send_emails[] = $row->id; if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_php_backtrace_in_email') && '' != $row->backtrace_log) { $lockout_ips_backtrace_log[] = array('backtrace_log' => $row->backtrace_log); } } if (0 != count($lockout_ips_backtrace_log)) { $backtrace_filepath = AIOWPSecurity_Utility::login_lockdown_email_backtrace_log_file($lockout_ips_backtrace_log); } $this->send_ip_lock_notification_email($lockout_ips_list, $backtrace_filepath); if ('' != $backtrace_filepath) { wp_delete_file($backtrace_filepath); } if (!empty($login_lockout_ids_send_emails)) { $aio_wp_security->debug_logger->log_debug(sprintf('The IP lock notification emails of login lockout ids [%s] are sent.', implode(', ', $login_lockout_ids_send_emails)), 4); // update all email to as sent. // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- PCP error. Direct query required. Table name cannot be prepared pre WP 6.2. $sql = $wpdb->prepare('UPDATE '.AIOWPSEC_TBL_LOGIN_LOCKOUT.' SET is_lockout_email_sent = %d WHERE is_lockout_email_sent = %d', 1, 0); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Prepared above. $update_result = $wpdb->query($sql); if (false === $update_result) { $error_msg = empty($wpdb->last_error) ? 'Could not receive the reason for the failure' : $wpdb->last_error; $aio_wp_security->debug_logger->log_debug_cron("Lockout email flag is not updated in database due to error: {$error_msg}", 4); } } } /** * Stores logged-in user in the logged_in_user table * * @param int $user_id - id of user logging in * @param int $expiration - expiration timestamp of cookie * * @return void */ public function store_logged_in_user($user_id, $expiration) { global $wpdb, $aio_wp_security; $logged_in_users_table = AIOWPSEC_TBL_LOGGED_IN_USERS; $ip_address = AIOWPSecurity_Utility_IP::get_user_ip_address(); $userdata = get_userdata($user_id); $username = $userdata->user_login; $login_time = time(); // Check if a record with the given user_id already exists // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Direct query required. Table name cannot be prepared pre WP 6.2. $existing_record = $wpdb->get_row($wpdb->prepare("SELECT * FROM " . $logged_in_users_table . " WHERE user_id = %d", $user_id)); if ($existing_record) { // Update the existing record // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore. $result = $wpdb->update( $logged_in_users_table, array( 'ip_address' => $ip_address, 'site_id' => get_current_blog_id(), 'username' => $username, 'expires' => $expiration ), array('user_id' => $user_id) ); } else { // Create a new record // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore. $result = $wpdb->insert( $logged_in_users_table, array( 'user_id' => $user_id, 'ip_address' => $ip_address, 'expires' => $expiration, 'site_id' => get_current_blog_id(), 'username' => $username, 'created' => $login_time ) ); } if (false === $result) { $generic_error_message = $existing_record ? "Error updating record in " . $logged_in_users_table : "Error inserting record into ".$logged_in_users_table; $error_message = empty($wpdb->last_error) ? $generic_error_message : $wpdb->last_error; $aio_wp_security->debug_logger->log_debug($error_message, 4); } } /** * Handles the data coming from the 'set_auth_cookie' hook * * @param string $auth_cookie - the generated auth_cookie * @param int $expire - expiration timestamp of cookie if remember is marked * @param int $expiration - expiration timestamp of cookie * @param int $user_id - id of user logging in * * @return void */ public function handle_logged_in_user($auth_cookie, $expire, $expiration, $user_id) { if (empty($auth_cookie)) return; //check if auth cookie is empty, meaning login was not successful $expiration = $expire > 0 ? $expire : $expiration; if (is_multisite() && !is_super_admin()) { $user_blog = get_active_blog_for_user($user_id); switch_to_blog($user_blog->blog_id); // switch to user blog incase they try to log in from wrong subsite $this->store_logged_in_user($user_id, $expiration); restore_current_blog(); } else { $this->store_logged_in_user($user_id, $expiration); } } /** * Deletes logged-in user from the logged_in_user table * * @param int $user_id * @return bool */ public function delete_logged_in_user($user_id) { global $wpdb, $aio_wp_security; $logged_in_users_table = AIOWPSEC_TBL_LOGGED_IN_USERS; if (empty($user_id)) return true; // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore. $result = $wpdb->delete( $logged_in_users_table, array('user_id' => $user_id) ); if (false === $result) { $error_message = empty($wpdb->last_error) ? "Error deleting record from " . $logged_in_users_table : $wpdb->last_error; $aio_wp_security->debug_logger->log_debug($error_message, 4); } return $result; } /** * Cron job function for removing data with expired session from the logged-in user table * * @return void */ public function delete_expired_logged_in_users() { global $wpdb, $aio_wp_security; $logged_in_users_table = AIOWPSEC_TBL_LOGGED_IN_USERS; // Delete data with expired cookie // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Direct query required, $result = $wpdb->query( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- PCP error. Direct query required. Table name cannot be prepared pre WP 6.2. $wpdb->prepare("DELETE FROM " . $logged_in_users_table . " WHERE expires < %d", time()) ); if (false === $result) { $error_message = empty($wpdb->last_error) ? "Error deleting records from ".$logged_in_users_table : $wpdb->last_error; $aio_wp_security->debug_logger->log_debug($error_message, 4); } } /** * This function rewrites the password reset message * * @param string $message - The password reset email message to be edited * * @return string - Email message to be sent for password reset */ public function aiowps_retrieve_password_message($message) { $ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); //Get the IP address of user // Find the position of the IP Address string in the message $ip_string = isset($_SERVER['REMOTE_ADDR']) ? rest_is_ip_address(wp_unslash($_SERVER['REMOTE_ADDR'])) : ''; $ip_pos = strpos($message, $ip_string); // If the IP Address string is found in the message and not the same as AIOWPS ip, replace it with the replacement string if (false !== $ip_pos && $ip !== $ip_string) { $replacement = "$ip.\r\n\r\n"; $message = substr_replace($message, $replacement, $ip_pos); } return $message; } }