| 1 |
<?php |
| 2 |
|
| 3 |
if (!defined('WEBTOTEM_INIT') || WEBTOTEM_INIT !== true) { |
| 4 |
if (!headers_sent()) { |
| 5 |
header('HTTP/1.1 403 Forbidden'); |
| 6 |
} |
| 7 |
die("Protected By WebTotem!"); |
| 8 |
} |
| 9 |
/** |
| 10 |
* WebTotem bruteforce protection class for Wordpress. |
| 11 |
*/ |
| 12 |
class WebTotemBFProtection{ |
| 13 |
|
| 14 |
/** |
| 15 |
* Check brute force attempts. |
| 16 |
* |
| 17 |
* @param WP_User $user |
| 18 |
* WP_User. |
| 19 |
* |
| 20 |
* @return mixed |
| 21 |
*/ |
| 22 |
public static function checkBruteForceAttempts( $user, $username ) { |
| 23 |
$ip = WebTotem::getUserIP(); |
| 24 |
$login_attempts_enabled = WebTotemOption::getPluginSettings('login_attempts'); |
| 25 |
$errorCodes = [ |
| 26 |
'invalid_username', |
| 27 |
'invalid_email', |
| 28 |
'incorrect_password', |
| 29 |
'twofactor_invalid', |
| 30 |
'authentication_failed', |
| 31 |
'wtotem_two_factor_failed', |
| 32 |
]; |
| 33 |
|
| 34 |
if($login_attempts_enabled){ |
| 35 |
|
| 36 |
$message = sprintf( __('Exceeded the maximum number of login failures which is: %1$s.', 'wtotem'), WebTotemOption::getPluginSettings('login_number_of_attempts')); |
| 37 |
|
| 38 |
if(self::isIpBlocked($ip, 'login')){ |
| 39 |
return new \WP_Error('wtotem_login_failure', $message); |
| 40 |
} |
| 41 |
|
| 42 |
$temp_option = self::getTempLogin($ip); |
| 43 |
if(is_wp_error($user) && in_array($user->get_error_code(), $errorCodes)) { |
| 44 |
$tries = get_transient($temp_option); |
| 45 |
if($tries){ |
| 46 |
$tries++; |
| 47 |
} else { |
| 48 |
$tries = 1; |
| 49 |
} |
| 50 |
if($tries >= WebTotemOption::getPluginSettings('login_number_of_attempts')){ |
| 51 |
self::lockOutIp($ip, 'login'); |
| 52 |
return new \WP_Error('wtotem_login_failure', $message); |
| 53 |
} |
| 54 |
set_transient($temp_option, $tries, 60); |
| 55 |
} else if(is_object($user) && get_class($user) == 'WP_User'){ |
| 56 |
delete_transient($temp_option); //reset counter on success |
| 57 |
} |
| 58 |
} |
| 59 |
|
| 60 |
if(is_wp_error($user) && ($user->get_error_code() == 'invalid_username' || $user->get_error_code() == 'invalid_email' || $user->get_error_code() == 'incorrect_password') ){ |
| 61 |
return new \WP_Error( 'incorrect_password', sprintf( wp_kses(__( '<strong>ERROR</strong>: The username or password you entered is incorrect. <a href="%2$s" title="Password Lost and Found">Lost your password</a>?', 'wordfence' ), array('strong'=>array(), 'a'=>array('href'=>array(), 'title'=>array()))), $username, wp_lostpassword_url() ) ); |
| 62 |
} |
| 63 |
return $user; |
| 64 |
} |
| 65 |
|
| 66 |
/** |
| 67 |
* Check reset password attempts |
| 68 |
* |
| 69 |
* @return mixed |
| 70 |
*/ |
| 71 |
public static function lostPassword($errors) { |
| 72 |
$ip = WebTotem::getUserIP(); |
| 73 |
$password_reset_number_of_attempts = WebTotemOption::getPluginSettings('password_reset_number_of_attempts'); |
| 74 |
$message = sprintf( __('Exceeded the maximum number of tries to recover their password which is set at: %1$s', 'wtotem'), $password_reset_number_of_attempts); |
| 75 |
|
| 76 |
if(self::isIpBlocked($ip, 'lost_password')){ |
| 77 |
$errors = new \WP_Error('wtotem_lost_password_failure', $message); |
| 78 |
} |
| 79 |
|
| 80 |
$password_reset_attempts_enabled = WebTotemOption::getPluginSettings('password_reset'); |
| 81 |
if($password_reset_attempts_enabled){ |
| 82 |
$temp_option = self::getTempLostPass($ip); |
| 83 |
$tries = get_transient($temp_option); |
| 84 |
if($tries){ |
| 85 |
$tries++; |
| 86 |
} else { |
| 87 |
$tries = 1; |
| 88 |
} |
| 89 |
if($tries >= WebTotemOption::getPluginSettings('password_reset_number_of_attempts')){ |
| 90 |
self::lockOutIp($ip, 'lost_password'); |
| 91 |
$errors = new \WP_Error('wtotem_lost_password_failure', $message); |
| 92 |
} |
| 93 |
set_transient($temp_option, $tries, 60); |
| 94 |
} |
| 95 |
|
| 96 |
return $errors; |
| 97 |
} |
| 98 |
|
| 99 |
/** |
| 100 |
* Lock out IP. |
| 101 |
* |
| 102 |
* @param string $ip |
| 103 |
* User IP. |
| 104 |
* @param string $reason |
| 105 |
* |
| 106 |
*/ |
| 107 |
public static function lockOutIp($ip, $reason) { |
| 108 |
$blockedTime = time() + WebTotemOption::getPluginSettings('login_minutes_of_ban') * 60; |
| 109 |
WebTotemDB::setData(['ip' => $ip, 'reason' => $reason, 'blockedTime' => $blockedTime], 'blocked_list'); |
| 110 |
} |
| 111 |
|
| 112 |
/** |
| 113 |
* Check if the IP is blocked. |
| 114 |
* |
| 115 |
* @param string $ip |
| 116 |
* User IP. |
| 117 |
* @param string $reason |
| 118 |
* |
| 119 |
*/ |
| 120 |
public static function isIpBlocked($ip, $reason) { |
| 121 |
$data = WebTotemDB::getData(['ip' => $ip, 'reason' => $reason], 'blocked_list'); |
| 122 |
|
| 123 |
if( $data ){ |
| 124 |
if(time() > $data['blockedTime']){ |
| 125 |
WebTotemDB::deleteData(['id' => $data['id']],'blocked_list'); |
| 126 |
} else { |
| 127 |
return true; |
| 128 |
} |
| 129 |
} |
| 130 |
|
| 131 |
return false; |
| 132 |
} |
| 133 |
|
| 134 |
public static function getTempLogin($ip) { |
| 135 |
return 'wtotem_tl_' . bin2hex(self::encodeIpToBinary($ip)); |
| 136 |
} |
| 137 |
|
| 138 |
public static function getTempLostPass($ip) { |
| 139 |
return 'wtotem_tlp_' . bin2hex(self::encodeIpToBinary($ip)); |
| 140 |
} |
| 141 |
|
| 142 |
/** |
| 143 |
* Return the packed binary string of an IPv4 or IPv6 address. |
| 144 |
* |
| 145 |
* @param string $ip |
| 146 |
* @return string |
| 147 |
*/ |
| 148 |
public static function encodeIpToBinary($ip) { |
| 149 |
// convert the 4 char IPv4 to IPv6 mapped version. |
| 150 |
$pton = str_pad(self::supportsIPv6() ? @inet_pton($ip) : self::compatIpCompression($ip), 16, |
| 151 |
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00", STR_PAD_LEFT); |
| 152 |
return $pton; |
| 153 |
} |
| 154 |
|
| 155 |
/** |
| 156 |
* Check PHP was compiled with IPv6 support. |
| 157 |
* |
| 158 |
* @return bool |
| 159 |
*/ |
| 160 |
public static function supportsIPv6() { |
| 161 |
return defined('AF_INET6'); |
| 162 |
} |
| 163 |
|
| 164 |
/** |
| 165 |
* Added compatibility for hosts that do not have inet_pton. |
| 166 |
* |
| 167 |
* @param $ip |
| 168 |
* @return bool|string |
| 169 |
*/ |
| 170 |
public static function compatIpCompression($ip) { |
| 171 |
// IPv4 |
| 172 |
if (preg_match('/^(?:\d{1,3}(?:\.|$)){4}/', $ip)) { |
| 173 |
$octets = explode('.', $ip); |
| 174 |
$bin = chr($octets[0]) . chr($octets[2]) . chr($octets[3]); |
| 175 |
return $bin; |
| 176 |
} |
| 177 |
|
| 178 |
// IPv6 |
| 179 |
if (preg_match('/^((?:[\da-f]{1,4}(?::|)){0,8})(::)?((?:[\da-f]{1,4}(?::|)){0,8})$/i', $ip)) { |
| 180 |
if ($ip === '::') { |
| 181 |
return "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; |
| 182 |
} |
| 183 |
$colon_count = substr_count($ip, ':'); |
| 184 |
$dbl_colon_pos = strpos($ip, '::'); |
| 185 |
if ($dbl_colon_pos !== false) { |
| 186 |
$ip = str_replace('::', str_repeat(':0000', |
| 187 |
(($dbl_colon_pos === 0 || $dbl_colon_pos === strlen($ip) - 2) ? 9 : 8) - $colon_count) . ':', $ip); |
| 188 |
$ip = trim($ip, ':'); |
| 189 |
} |
| 190 |
|
| 191 |
$ip_groups = explode(':', $ip); |
| 192 |
$ipv6_bin = ''; |
| 193 |
foreach ($ip_groups as $ip_group) { |
| 194 |
$ipv6_bin .= pack('H*', str_pad($ip_group, 4, '0', STR_PAD_LEFT)); |
| 195 |
} |
| 196 |
|
| 197 |
return strlen($ipv6_bin) === 16 ? $ipv6_bin : false; |
| 198 |
} |
| 199 |
|
| 200 |
// IPv4 mapped IPv6 |
| 201 |
if (preg_match('/^(?:\:(?:\:0{1,4}){0,4}\:|(?:0{1,4}\:){5})ffff\:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i', $ip, $matches)) { |
| 202 |
$octets = explode('.', $matches[1]); |
| 203 |
return "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" . chr($octets[0]) . chr($octets[1]) . chr($octets[2]) . chr($octets[3]); |
| 204 |
} |
| 205 |
|
| 206 |
return false; |
| 207 |
} |
| 208 |
|
| 209 |
} |
| 210 |
|