PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.1.0
Defender Security – Malware Scanner, Login Security & Firewall v3.1.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / component / login-lockout.php

login-lockout.php in Defender Security – Malware Scanner, Login Security & Firewall 3.1.0, at src/component/login-lockout.php

242 lines 6.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WP_Defender\Component;
4
5 use WP_Defender\Model\Lockout_Ip;
6 use WP_Defender\Model\Lockout_Log;
7 use WP_Defender\Component\User_Agent;
8
9 /**
10 * This class will handle the logic lockout when too many failed login attempts.
11 *
12 * Class Login_Lockout
13 *
14 * @package WP_Defender\Component
15 */
16 class Login_Lockout extends \WP_Defender\Component {
17 use \WP_Defender\Traits\Country;
18
19 public const SCENARIO_LOGIN_FAIL = 'login_fail', SCENARIO_LOGIN_LOCKOUT = 'login_lockout', SCENARIO_BAN = 'login_ban';
20
21 /**
22 * @var \WP_Defender\Model\Setting\Login_Lockout
23 */
24 protected $model;
25
26 public function __construct() {
27 $this->model = wd_di()->get( \WP_Defender\Model\Setting\Login_Lockout::class );
28 }
29
30 /**
31 * Adding main hooks.
32 */
33 public function add_hooks() {
34 add_action( 'wp_login_failed', array( &$this, 'process_fail_attempt' ) );
35 add_filter( 'authenticate', array( &$this, 'show_attempt_left' ), 9999, 3 );
36 add_action( 'wp_login', array( &$this, 'clear_login_attempt' ), 10, 2 );
37 }
38
39 /**
40 * When a user logins successfully, we need to clear the info of failed login attempt.
41 * So it won't affect the next time that user logins again.
42 *
43 * @param $user_login
44 * @param $user
45 */
46 public function clear_login_attempt( $user_login, $user ) {
47 $ip = $this->get_user_ip();
48 // Record this.
49 $model = Lockout_Ip::get( $ip );
50 if ( is_object( $model ) ) {
51 $model->meta = array();
52 $model->attempt = 0;
53 $model->save();
54 }
55 }
56
57 /**
58 * Show a message to tell user how many attempt they have until get lockout.
59 *
60 * @param $user
61 * @param $username
62 * @param $password
63 *
64 * @return mixed
65 */
66 public function show_attempt_left( $user, $username, $password ) {
67 if ( is_wp_error( $user )
68 && 'POST' === $_SERVER['REQUEST_METHOD']
69 && ! in_array(
70 $user->get_error_code(),
71 array(
72 'empty_username',
73 'empty_password',
74 ),
75 true
76 )
77 ) {
78 $model = Lockout_Ip::get( $this->get_user_ip() );
79 if ( in_array( $username, $this->model->get_blacklisted_username(), true ) ) {
80 $msg = __(
81 'You have been locked out by the administrator for attempting to login with a banned username',
82 'wpdef'
83 );
84 if ( ! empty( $this->model->lockout_message ) ) {
85 $msg = $this->model->lockout_message;
86 }
87 $user->add(
88 'def_login_attempt',
89 $msg
90 );
91
92 return $user;
93 }
94 // This hook is before the @process_fail_attempt, so we will need to add 1 into the attempt count.
95 $attempt = $model->attempt;
96 ++ $attempt;
97 if ( $attempt < $this->model->attempt ) {
98 $user->add(
99 'def_login_attempt',
100 sprintf( __( '%d login attempts remaining', 'wpdef' ), $this->model->attempt - $attempt )
101 );
102 } else {
103 $user->add( 'def_login_attempt', $this->model->lockout_message );
104 }
105 }
106
107 return $user;
108 }
109
110 /**
111 * From here, we will:
112 * 1. Record the attempt.
113 * 2. Log it.
114 * 3. Do condition check if we should block or not.
115 *
116 * @param string $username
117 */
118 public function process_fail_attempt( $username ) {
119 if ( empty( $username ) ) {
120 return;
121 }
122 $ip = $this->get_user_ip();
123 // Record this.
124 $model = Lockout_Ip::get( $ip );
125 $model = $this->record_fail_attempt( $ip, $model );
126 $this->log_event( $ip, $username, self::SCENARIO_LOGIN_FAIL );
127 // Now check, if it is in a banned username.
128 $ls = $this->model;
129 if ( in_array( $username, $ls->get_blacklisted_username(), true ) ) {
130 $model->lockout_message = '';
131 $model->status = Lockout_Ip::STATUS_BLOCKED;
132 $model->save();
133 $this->log_event( $ip, $username, self::SCENARIO_BAN );
134
135 do_action( 'wd_login_lockout', $model, self::SCENARIO_BAN );
136 do_action( 'wd_blacklist_this_ip', $ip );
137
138 return;
139 }
140 // So if we can lock.
141 $window = strtotime( '-' . $ls->timeframe . 'seconds' );
142 if ( ! is_array( $model->meta['login'] ) ) {
143 $model->meta['login'] = array();
144 }
145 // We will get the latest till oldest, limit by attempt.
146 $checks = array_slice( $model->meta['login'], $ls->attempt * - 1 );
147
148 if ( count( $checks ) < $ls->attempt ) {
149 // Do nothing.
150 return;
151 }
152 // if the last time is larger.
153 $check = min( $checks );
154 if ( $check >= $window ) {
155 // Lockable.
156 $model->status = Lockout_Ip::STATUS_BLOCKED;
157 $model->lock_time = time();
158 if ( 'permanent' === $ls->lockout_type ) {
159 // Add to black list.
160 $model->save();
161 do_action( 'wd_blacklist_this_ip', $ip );
162 } else {
163 $model->lockout_message = $ls->lockout_message;
164 $model->release_time = strtotime( '+' . $ls->duration . ' ' . $ls->duration_unit );
165 $model->save();
166 }
167 // Need to create a log.
168 $this->log_event( $ip, $username, self::SCENARIO_LOGIN_LOCKOUT );
169 do_action( 'wd_login_lockout', $model, self::SCENARIO_LOGIN_LOCKOUT );
170 }
171 }
172
173 /**
174 * Store the failed attempt of current IP.
175 *
176 * @param string $ip
177 * @param Lockout_Ip $model
178 *
179 * @return Lockout_Ip
180 */
181 protected function record_fail_attempt( $ip, $model ) {
182 $model->attempt += 1;
183 $model->ip = $ip;
184 // Cache the time here, so it consumes less memory than query the logs.
185 $model->meta['login'][] = time();
186 $model->save();
187
188 return $model;
189 }
190
191 /**
192 * Log the current event.
193 * We have 3 type of event:
194 * 1. Fail attempt.
195 * 2. Too many fails, get lock.
196 * 3. Login with banned username.
197 *
198 * @param $ip
199 * @param $username
200 * @param $scenario
201 */
202 public function log_event( $ip, $username, $scenario ) {
203 $model = new Lockout_Log();
204 $model->ip = $ip;
205 $model->user_agent = isset( $_SERVER['HTTP_USER_AGENT'] )
206 ? User_Agent::fast_cleaning( $_SERVER['HTTP_USER_AGENT'] )
207 : null;
208 $model->date = time();
209 $model->tried = $username;
210 $model->blog_id = get_current_blog_id();
211
212 $ip_to_country = $this->ip_to_country( $ip );
213
214 if ( ! empty( $ip_to_country ) && isset( $ip_to_country['iso'] ) ) {
215 $model->country_iso_code = $ip_to_country['iso'];
216 }
217
218 switch ( $scenario ) {
219 case self::SCENARIO_LOGIN_FAIL:
220 $model->type = Lockout_Log::AUTH_FAIL;
221 $model->log = sprintf( esc_html__( 'Failed login attempt with username %s', 'wpdef' ), $username );
222 break;
223 case self::SCENARIO_BAN:
224 $model->type = Lockout_Log::AUTH_LOCK;
225 $model->log = sprintf(
226 esc_html__( 'Failed login attempt with a ban username %s', 'wpdef' ),
227 $username
228 );
229 break;
230 case self::SCENARIO_LOGIN_LOCKOUT:
231 default:
232 $model->type = Lockout_Log::AUTH_LOCK;
233 $model->log = __( 'Lockout occurred: Too many failed login attempts', 'wpdef' );
234 break;
235 }
236 $model->save();
237 if ( Lockout_Log::AUTH_LOCK === $model->type ) {
238 do_action( 'defender_notify', 'firewall-notification', $model );
239 }
240 }
241 }
242