isLoginAllowedFromIp() ) { return $user; } global $limit_login_my_error_shown; $limit_login_my_error_shown = true; $error = new \WP_Error(); // This error should be the same as in "shake it" filter below $error->add('too_many_retries', $this->composeErrorMessage()); return $error; } public function onFailedLogin(string $username) { $ip = $this->getAddress(); /* if currently locked-out, do not add to retries */ $lockouts = get_option('protect_login_limit_login_lockouts', []); if(isset($lockouts[$ip]) && time() < $lockouts[$ip]) { return; } /* Get the arrays with retries and retries-valid information */ $retries = get_option('kompass_limit_login_retries', []); $valid = get_option('kompass_limit_login_retries_valid', []); /* Check validity and add one to retries */ if (isset($retries[$ip])) { //} && isset($valid[$ip]) && time() < $valid[$ip]) { $retries[$ip] ++; } else { $retries[$ip] = 1; } update_option('kompass_limit_login_retries', $retries); /* lockout? */ if($retries[$ip] % get_option('kompass_limit_login_allowed_retries', 0) != 0) { return; } $retries_long = get_option('kompass_limit_login_allowed_retries', 1) * get_option('kompass_limit_login_allowed_lockouts', 1); if ($retries[$ip] >= $retries_long) { $lockouts[$ip] = time() + get_option('kompass_limit_login_long_duration', 86400); } else { $lockouts[$ip] = time() + get_option('kompass_limit_login_lockout_duration', 900); } update_option('kompass_limit_login_lockouts', $lockouts); /* do any notification */ $this->notify($username); } private function notifyByEmail($user) { $ip = $this->getAddress(); $lockouts = get_option('kompass_limit_login_lockouts', []); if (!isset($lockouts[$ip])) { return; } $blocked_until = $lockouts[$ip]; $retries = get_option('kompass_limit_login_retries', []); $currentRetries = $retries[$ip]; $notify_after = get_option('kompass_limit_login_notify_email_after', 1); if ($currentRetries % $notify_after !== 0) { return; } $blogname = get_option('blogname', 'none'); $subject = sprintf(__("[%s] Too many failed login attempts" , 'limit-login-attempts') , $blogname); $message = 'Neue Sperrung auf deiner Webseite: ' . PHP_EOL . 'IP-Adresse: ' . $ip . PHP_EOL . 'Gesperrt bis: ' . date('d.m.Y H:i', $blocked_until); $admin_email = get_option('admin_email'); wp_mail($admin_email, $subject, $message); } /* Handle notification in event of lockout */ private function notify($user) { $args = get_option('kompass_limit_login_lockout_notify', []); if (!is_array($args)) { $args = [$args]; } foreach ($args as $mode) { switch (trim($mode)) { case 'email': $this->notifyByEmail($user); break; } } } private function composeErrorMessage() { $ip = $this->getAddress(); $lockouts = get_option('kompass_limit_login_lockouts'); $msg = __('ERROR: Too many failed login attempts.', 'limit-login-attempts') . ' '; if (!is_array($lockouts) || !isset($lockouts[$ip]) || time() >= $lockouts[$ip]) { /* Huh? No timeout active? */ $msg .= __('Please try again later.', 'limit-login-attempts'); return $msg; } $when = ceil(($lockouts[$ip] - time()) / 60); if ($when > 60) { $when = ceil($when / 60); $msg .= sprintf(_n('Please try again in %d hour.', 'Please try again in %d hours.', $when, 'limit-login-attempts'), $when); } else { $msg .= sprintf(_n('Please try again in %d minute.', 'Please try again in %d minutes.', $when, 'limit-login-attempts'), $when); } return $msg; } private static function getAddress($typeName = '') { global $limitLoginAttemptsSettings; $typeOriginal = $typeName; if (empty($typeName)) { $typeName = get_option('kompass_limit_loginclient_type', self::DIRECT_ADDR); } if (isset($_SERVER[$typeName]) && filter_var($_SERVER[$typeName], FILTER_VALIDATE_IP)) { return $_SERVER[$typeName]; } /* * Not found. Did we get proxy type from option? * If so, try to fall back to direct address. */ if ( empty($typeName) && $typeOriginal == self::PROXY_ADDR && isset($_SERVER[self::DIRECT_ADDR]) && filter_var($_SERVER[self::DIRECT_ADDR], FILTER_VALIDATE_IP)) { /* * NOTE: Even though we fall back to direct address -- meaning you * can get a mostly working plugin when set to PROXY mode while in * fact directly connected to Internet it is not safe! * * Client can itself send HTTP_X_FORWARDED_FOR header fooling us * regarding which IP should be banned. */ return $_SERVER[self::DIRECT_ADDR]; } return ''; } public function isLoginAllowedFromIp() { $ip = $this->getAddress(); if (in_array($ip, get_option('kompass_limit_login_blocklist', []))) { return false; } if (in_array($ip, get_option('kompass_limit_login_allowlist', []))) { return true; } /* lockout active? */ $lockouts = get_option('kompass_limit_login_lockouts', []); return (!is_array($lockouts) || !isset($lockouts[$ip]) || time() >= $lockouts[$ip]); } public function checkFailedCookies($cookie_elements) { $this->clearAuthCookie(); /* * Invalid username gets counted every time. */ $this->onFailedLogin($cookie_elements['username']); } private function clearAuthCookie() { wp_clear_auth_cookie(); if (!empty($_COOKIE[AUTH_COOKIE])) { $_COOKIE[AUTH_COOKIE] = ''; } if (!empty($_COOKIE[SECURE_AUTH_COOKIE])) { $_COOKIE[SECURE_AUTH_COOKIE] = ''; } if (!empty($_COOKIE[LOGGED_IN_COOKIE])) { $_COOKIE[LOGGED_IN_COOKIE] = ''; } } public function onValidCookie($cookie_elements, $user) { /* * As all meta values get cached on user load this should not require * any extra work for the common case of no stored value. */ if (get_user_meta($user->ID, 'kompass_limit_login_previous_cookie')) { delete_user_meta($user->ID, 'kompass_limit_login_previous_cookie'); } } function clearLoginCookie($cookie_elements) { $this->clearAuthCookie(); /* * Under some conditions an invalid auth cookie will be used multiple * times, which results in multiple failed attempts from that one * cookie. * * Unfortunately I've not been able to replicate this consistently and * thus have not been able to make sure what the exact cause is. * * Probably it is because a reload of for example the admin dashboard * might result in multiple requests from the browser before the invalid * cookie can be cleard. * * Handle this by only counting the first attempt when the exact same * cookie is attempted for a user. */ extract($cookie_elements, EXTR_OVERWRITE); // Check if cookie is for a valid user $user = get_user_by('login', $username); if (!$user) { // "shouldn't happen" for this action $this->onFailedLogin($username); return; } $previous_cookie = get_user_meta($user->ID, 'kompass_limit_login_previous_cookie', true); if ($previous_cookie && $previous_cookie == $cookie_elements) { // Identical cookies, ignore this attempt return; } // Store cookie if ($previous_cookie) update_user_meta($user->ID, 'kompass_limit_login_previous_cookie', $cookie_elements); else add_user_meta($user->ID, 'kompass_limit_login_previous_cookie', $cookie_elements, true); $this->onFailedLogin($username); } public function handleCookies() { if ($this->isLoginAllowedFromIp()) { return; } $this->clearAuthCookie(); } }