PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.0.1
Defender Security – Malware Scanner, Login Security & Firewall v3.0.1
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 / model / lockout-log.php

lockout-log.php in Defender Security – Malware Scanner, Login Security & Firewall 3.0.1, at src/model/lockout-log.php

564 lines 13.1 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\Model;
4
5 use WP_Defender\DB;
6 use WP_Defender\Model\Setting\User_Agent_Lockout;
7 use WP_Defender\Traits\Formats;
8 use WP_Defender\Component\Table_Lockout;
9
10 class Lockout_Log extends DB {
11 use Formats;
12
13 public const AUTH_FAIL = 'auth_fail', AUTH_LOCK = 'auth_lock';
14 public const ERROR_404 = '404_error', LOCKOUT_404 = '404_lockout', ERROR_404_IGNORE = '404_error_ignore';
15 public const LOCKOUT_UA = 'ua_lockout';
16
17 public const INFINITE_SCROLL_SIZE = 50;
18
19 protected $table = 'defender_lockout_log';
20
21 /**
22 * @var int
23 * @defender_property
24 */
25 public $id;
26 /**
27 * @var string
28 * @defender_property
29 */
30 public $log;
31 /**
32 * @var string
33 * @defender_property
34 */
35 public $ip;
36 /**
37 * @var int
38 * @defender_property
39 */
40 public $date;
41 /**
42 * @var string
43 * @defender_property
44 */
45 public $user_agent;
46 /**
47 * @var string
48 * @defender_property
49 */
50 public $type;
51 /**
52 * @var int
53 * @defender_property
54 */
55 public $blog_id;
56 /**
57 * @var string
58 * @defender_property
59 */
60 public $tried;
61 /**
62 * @var string
63 * @defender_property
64 */
65 public $country_iso_code;
66
67 /**
68 * Pulling the logs as data, use in Logs tab
69 * $filters will have those params
70 * -date_from
71 * -date_to
72 * == Defaults is 7 days and always require
73 * -type: optional
74 * -ip: optional.
75 *
76 * @param array $filters
77 * @param int $paged
78 * @param string $order_by
79 * @param string $order
80 * @param int $page_size
81 *
82 * @return Lockout_Log[]
83 */
84 public static function query_logs(
85 $filters = array(),
86 $paged = 1,
87 $order_by = 'id',
88 $order = 'desc',
89 $page_size = 50
90 ) {
91 $orm = self::get_orm();
92 $orm->get_repository( self::class )
93 ->where(
94 'date',
95 'between',
96 array(
97 $filters['from'],
98 $filters['to'],
99 )
100 );
101
102 if ( isset( $filters['ip'] ) && ! empty( $filters['ip'] ) ) {
103 $orm->where( 'ip', 'like', '%' . $filters['ip'] . '%' );
104 }
105 if ( isset( $filters['type'] ) && ! empty( $filters['type'] ) ) {
106 $orm->where( 'type', $filters['type'] );
107 }
108
109 if ( ! empty( $filters['ban_status'] ) ) {
110 $ban_status_where = self::ban_status_where( $filters['ban_status'] );
111
112 if ( 3 === count( $ban_status_where ) ) {
113 $orm->where( ... $ban_status_where );
114 }
115 }
116
117 if ( ! empty( $order_by ) && ! empty( $order ) ) {
118 $orm->order_by( $order_by, $order );
119 }
120
121 if( -1 === (int) $page_size ) {
122 $page_size = self::INFINITE_SCROLL_SIZE;
123 }
124
125 if ( false !== $page_size ) {
126 $offset = ( $paged - 1 ) * $page_size;
127 $orm->limit( "$offset,$page_size" );
128 }
129
130 return $orm->get();
131 }
132
133 /**
134 * This similar to @query_logs, but we count the total row.
135 *
136 * @param $date_from
137 * @param $date_to
138 * @param string $type
139 * @param string $ip
140 * @param array $filters Array consists key value pair to pass in SQL where condition.
141 *
142 * @return string|null
143 */
144 public static function count( $date_from, $date_to, $type = '', $ip = '', $filters = array() ) {
145 $orm = self::get_orm();
146 $orm->get_repository( self::class )
147 ->where(
148 'date',
149 'between',
150 array(
151 $date_from,
152 $date_to,
153 )
154 );
155
156 if ( ! empty( $type ) ) {
157 if ( is_array( $type ) ) {
158 $orm->where( 'type', 'in', $type );
159 } else {
160 $orm->where( 'type', $type );
161 }
162 }
163
164 if ( ! empty( $ip ) ) {
165 $orm->where( 'ip', 'like', "%$ip%" );
166 }
167
168 if ( ! empty( $filters['ban_status'] ) ) {
169 $ban_status_where = self::ban_status_where( $filters['ban_status'] );
170
171 if ( 3 === count( $ban_status_where ) ) {
172 $orm->where( ... $ban_status_where );
173 }
174 }
175
176 return $orm->count();
177 }
178
179 /**
180 * Count login lockout in the last 7 days.
181 *
182 * @return string|null
183 */
184 public static function count_login_lockout_last_7_days() {
185 $start = strtotime( '-7 days' );
186 $end = time();
187
188 return self::count( $start, $end, self::AUTH_LOCK );
189 }
190
191 /**
192 * Count 404 lockout in the last 7 days.
193 *
194 * @return string|null
195 */
196 public static function count_404_lockout_last_7_days() {
197 $start = strtotime( '-7 days' );
198 $end = time();
199
200 return self::count( $start, $end, self::LOCKOUT_404 );
201 }
202
203 /**
204 * Count UA lockout in the last 7 days.
205 *
206 * @return string|null
207 */
208 public static function count_ua_lockout_last_7_days() {
209 $start = strtotime( '-7 days' );
210 $end = time();
211
212 return self::count( $start, $end, self::LOCKOUT_UA );
213 }
214
215 /**
216 * A shortcut for quickly count lockout in last 24 hours.
217 *
218 * @return string|null
219 */
220 public static function count_lockout_in_24_hours() {
221 $start = strtotime( '-24 hours' );
222 $end = time();
223
224 return self::count(
225 $start,
226 $end,
227 array(
228 self::AUTH_LOCK,
229 self::LOCKOUT_404,
230 self::LOCKOUT_UA,
231 )
232 );
233 }
234
235 /**
236 * A shortcut for quickly count lockout in last 24 hours.
237 *
238 * @return string|null
239 */
240 public static function count_lockout_in_7_days() {
241 $start = strtotime( '-7 days' );
242 $end = time();
243
244 return self::count(
245 $start,
246 $end,
247 array(
248 self::AUTH_LOCK,
249 self::LOCKOUT_404,
250 self::LOCKOUT_UA,
251 )
252 );
253 }
254
255 /**
256 * A shortcut for count lockout in 30 days.
257 *
258 * @return string|null
259 */
260 public static function count_lockout_in_30_days() {
261 $start = strtotime( '-30 days' );
262 $end = time();
263
264 return self::count(
265 $start,
266 $end,
267 array(
268 self::AUTH_LOCK,
269 self::LOCKOUT_404,
270 self::LOCKOUT_UA,
271 )
272 );
273 }
274
275 /**
276 * Get the last time a lockout happened.
277 *
278 * @param false $for_hub
279 *
280 * @return false|string
281 */
282 public static function get_last_lockout_date( $for_hub = false ) {
283 $data = self::query_logs(
284 array(
285 'from' => strtotime( '-30 days' ),
286 'to' => time(),
287 ),
288 1,
289 'id',
290 'desc',
291 1
292 );
293 $last = array_shift( $data );
294 if ( ! is_object( $last ) ) {
295 return 'n/a';
296 }
297
298 return $for_hub
299 ? $last->persistent_hub_datetime_format( $last->date )
300 : $last->format_date_time( $last->date );
301 }
302
303 /**
304 * Remove all data.
305 *
306 * @return bool
307 */
308 public static function truncate() {
309 $orm = self::get_orm();
310
311 return $orm->get_repository( self::class )
312 ->truncate();
313 }
314
315 /**
316 * Remove data by time period.
317 *
318 * @param int $timestamp
319 * @param int $limit
320 *
321 * @return void
322 */
323 public static function remove_logs( $timestamp, $limit ) {
324 $orm = self::get_orm();
325 $orm->get_repository( self::class )
326 ->where( 'date', '<=', $timestamp )
327 ->order_by( 'id' )
328 ->limit( $limit )
329 ->delete_by_limit();
330 }
331
332 /**
333 * Get log summary.
334 *
335 * @return array
336 */
337 public static function get_summary() {
338 // Time.
339 $current_time = current_time( 'timestamp' );
340 $today_midnight = strtotime( '-24 hours', $current_time );
341 $first_this_week = strtotime( '-7 days', $current_time );
342
343 // Prepare columns
344 $select = array(
345 'MAX(date) as lockout_last',
346 'COUNT(*) as lockout_this_month',
347 // 24 hours
348 "COUNT(IF(date > {$today_midnight}, 1, NULL)) as lockout_today",
349 "COUNT(IF(date > {$today_midnight} AND type = '" . self::LOCKOUT_404 . "', 1, NULL)) as lockout_404_today",
350 "COUNT(IF(date > {$today_midnight} AND type = '" . self::AUTH_LOCK . "', 1, NULL)) as lockout_login_today",
351 "COUNT(IF(date > {$today_midnight} AND type = '" . self::LOCKOUT_UA . "', 1, NULL)) as lockout_ua_today",
352 // 7 days
353 "COUNT(IF(date > {$first_this_week} AND type = '" . self::LOCKOUT_404 . "', 1, NULL)) as lockout_404_this_week",
354 "COUNT(IF(date > {$first_this_week} AND type = '" . self::AUTH_LOCK . "', 1, NULL)) as lockout_login_this_week",
355 "COUNT(IF(date > {$first_this_week} AND type = '" . self::LOCKOUT_UA . "', 1, NULL)) as lockout_ua_this_week",
356 );
357 $select = implode( ',', $select );
358
359 $orm = self::get_orm();
360 $result = $orm->get_repository( self::class )
361 ->select( $select )
362 ->where( 'type', 'in', array( self::LOCKOUT_404, self::AUTH_LOCK, self::LOCKOUT_UA ) )
363 ->where( 'date', '>=', strtotime( '-30 days', $current_time ) )
364 ->get_results();
365
366 return $result[0] ?? array();
367 }
368
369 /**
370 * @param string $type
371 *
372 * @return string
373 */
374 protected static function get_log_tag( $type ) {
375 switch ( $type ) {
376 case self::LOCKOUT_404:
377 case self::ERROR_404:
378 case self::ERROR_404_IGNORE:
379 $tag = '404';
380 break;
381 case self::AUTH_FAIL:
382 case self::AUTH_LOCK:
383 $tag = 'login';
384 break;
385 case self::LOCKOUT_UA:
386 default:
387 $tag = 'bots';
388 break;
389 }
390
391 return $tag;
392 }
393
394 /**
395 * @param string $type
396 *
397 * @return string
398 */
399 protected static function get_log_tag_class( $type ) {
400 switch ( $type ) {
401 case self::AUTH_LOCK:
402 case self::LOCKOUT_404:
403 case self::LOCKOUT_UA:
404 $badge_bg = 'bg-badge-red';
405 break;
406 case self::AUTH_FAIL:
407 case self::ERROR_404:
408 case self::ERROR_404_IGNORE:
409 default:
410 $badge_bg = 'bg-badge-green';
411 break;
412 }
413
414 return $badge_bg;
415 }
416
417 /**
418 * @param string $type
419 *
420 * @return string
421 */
422 protected static function get_log_container_class( $type ) {
423 switch ( $type ) {
424 case self::AUTH_LOCK:
425 case self::LOCKOUT_404:
426 case self::LOCKOUT_UA:
427 $class = 'sui-error';
428 break;
429 case self::AUTH_FAIL:
430 case self::ERROR_404:
431 case self::ERROR_404_IGNORE:
432 default:
433 $class = 'sui-warning';
434 break;
435 }
436
437 return $class;
438 }
439
440 /**
441 * Get data from db and format it for ready to use on frontend.
442 *
443 * @param array $filters
444 * @param int $paged
445 * @param string $order_by
446 * @param string $order
447 * @param int $page_size
448 *
449 * @return array
450 */
451 public static function get_logs_and_format(
452 $filters = array(),
453 $paged = 1,
454 $order_by = 'id',
455 $order = 'desc',
456 $page_size = 50
457 ) {
458 $logs = self::query_logs( $filters, $paged, $order_by, $order, $page_size );
459 $data = array();
460 $ua_model = wd_di()->get( User_Agent_Lockout::class );
461 foreach ( $logs as $item ) {
462 $ip_model = Lockout_Ip::get( $item->ip );
463 $log = $item->export();
464 $log['date'] = $item->format_date_time( $item->date );
465 $log['format_date'] = $item->get_date( $item->date );
466 $log['tag'] = self::get_log_tag( $item->type );
467 $log['tag_class'] = self::get_log_tag_class( $item->type );
468 $log['container_class'] = self::get_log_container_class( $item->type );
469 if ( self::LOCKOUT_UA === $item->type ) {
470 if ( \WP_Defender\Component\User_Agent::REASON_BAD_POST === $item->tried ) {
471 $log['description'] = __( 'Lockout occurred due to attempted access with empty User-Agent and Referer headers. By default, IP addresses that send POST requests with empty User-Agent and Referer headers will be automatically banned. You can disable this option in the User Agent Banning settings, or you can unban the locked out IP address below.', 'wpdef' );
472 $log['type_label'] = __( 'Type', 'wpdef' );
473 $log['type_value'] = __( 'Empty Headers', 'wpdef' );
474 $arr_statuses = $ip_model->get_access_status();
475 } else {
476 $log['description'] = sprintf(
477 /* translators: %s: log, %d: user_agent */
478 __( '%1$s: <strong>%2$s</strong>. This user agent is considered bad bots and may harm your site.', 'wpdef' ),
479 $item->log,
480 $item->user_agent
481 );
482 $log['type_label'] = __( 'User Agent name', 'wpdef' );
483 $log['type_value'] = $item->user_agent;
484 $arr_statuses = $ua_model->get_access_status( $item->user_agent );
485 }
486 } else {
487 $log['description'] = $item->log;
488 $log['type_label'] = __( 'Type', 'wpdef' );
489 $log['type_value'] = str_replace( '_', ' ', $item->type );
490 $arr_statuses = $ip_model->get_access_status();
491 }
492 // There may be several statuses.
493 $log['access_status'] = $arr_statuses;
494 $log['access_status_text'] = $ip_model->get_access_status_text( $arr_statuses[0] );
495 $data[] = $log;
496 }
497
498 return $data;
499 }
500
501 /**
502 * Get the first log by ID.
503 *
504 * @param int $id
505 *
506 * @return null|object
507 */
508 public static function find_by_id( $id ) {
509 $orm = self::get_orm();
510
511 return $orm->get_repository( self::class )
512 ->where( 'id', $id )
513 ->first();
514 }
515
516 /**
517 * Delete current log.
518 */
519 public function delete() {
520 $orm = self::get_orm();
521 $orm->get_repository( self::class )->delete(
522 array(
523 'id' => $this->id,
524 )
525 );
526 }
527
528 /**
529 * Prepare user-agent where condition based on the ban status variant.
530 *
531 * @param string $ban_status_type Ban status type.
532 *
533 * @return array Where condition arguments or empty array.
534 */
535 private static function ban_status_where( $ban_status_type ) {
536 $table_lockout = wd_di()->get( Table_Lockout::class );
537
538 if ( $table_lockout::STATUS_NOT_BAN === $ban_status_type ) {
539 $ua_model = wd_di()->get( User_Agent_Lockout::class );
540
541 $blocklist = $ua_model->get_lockout_list( 'blocklist' );
542 $allowlist = $ua_model->get_lockout_list( 'allowlist' );
543
544 $all = array_merge( $blocklist, $allowlist );
545
546 return array( 'user_agent', 'not regexp', implode( '|', $all ) );
547 } elseif ( $table_lockout::STATUS_BAN === $ban_status_type ) {
548 $ua_model = wd_di()->get( User_Agent_Lockout::class );
549
550 $blocklist = $ua_model->get_lockout_list( 'blocklist' );
551
552 return array( 'user_agent', 'regexp', implode( '|', $blocklist ) );
553 } elseif ( $table_lockout::STATUS_ALLOWLIST === $ban_status_type ) {
554 $ua_model = wd_di()->get( User_Agent_Lockout::class );
555
556 $allowlist = $ua_model->get_lockout_list( 'allowlist' );
557
558 return array( 'user_agent', 'regexp', implode( '|', $allowlist ) );
559 }
560
561 return array();
562 }
563 }
564