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 / controller / firewall.php

firewall.php in Defender Security – Malware Scanner, Login Security & Firewall 3.1.0, at src/controller/firewall.php

653 lines 18.4 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\Controller;
4
5 use Calotes\Component\Request;
6 use Calotes\Component\Response;
7 use Calotes\Helper\HTTP;
8 use WP_Defender\Behavior\WPMUDEV;
9 use WP_Defender\Component\Blacklist_Lockout;
10 use WP_Defender\Component\Config\Config_Hub_Helper;
11 use WP_Defender\Component\User_Agent as Component_User_Agent;
12 use WP_Defender\Controller;
13 use WP_Defender\Model\Lockout_Ip;
14 use WP_Defender\Model\Lockout_Log;
15 use WP_Defender\Model\Notification\Firewall_Report;
16 use WP_Defender\Model\Notification\Firewall_Notification;
17 use WP_Defender\Model\Setting\Notfound_Lockout;
18 use WP_Defender\Model\Setting\User_Agent_Lockout;
19
20 class Firewall extends Controller {
21 use \WP_Defender\Traits\IP;
22 use \WP_Defender\Traits\Formats;
23
24 public const FIREWALL_LOG = 'firewall.log';
25
26 protected $slug = 'wdf-ip-lockout';
27
28 /**
29 * @var \WP_Defender\Model\Setting\Firewall
30 */
31 protected $model;
32
33 /**
34 * @var \WP_Defender\Component\Firewall
35 */
36 public $service;
37
38 public function __construct() {
39 $this->register_page(
40 esc_html__( 'Firewall', 'wpdef' ),
41 $this->slug,
42 array(
43 &$this,
44 'main_view',
45 ),
46 $this->parent_slug
47 );
48 $this->model = wd_di()->get( \WP_Defender\Model\Setting\Firewall::class );
49 $this->service = wd_di()->get( \WP_Defender\Component\Firewall::class );
50 $this->register_routes();
51 $this->maybe_show_demo_lockout();
52 $this->maybe_lockout();
53
54 wd_di()->get( \WP_Defender\Controller\Login_Lockout::class );
55 wd_di()->get( Nf_Lockout::class );
56 wd_di()->get( Blacklist::class );
57 wd_di()->get( Firewall_Logs::class );
58 wd_di()->get( \WP_Defender\Controller\UA_Lockout::class );
59
60 // We will schedule the time to clean up old firewall logs.
61 if ( ! wp_next_scheduled( 'firewall_clean_up_logs' ) ) {
62 wp_schedule_event( time() + 10, 'hourly', 'firewall_clean_up_logs' );
63 }
64
65 // Schedule cleanup blocklist ips event.
66 $this->schedule_cleanup_blocklist_ips_event();
67
68 add_action( 'firewall_clean_up_logs', array( &$this, 'clean_up_firewall_logs' ) );
69 add_action( 'firewall_cleanup_temp_blocklist_ips', array( &$this, 'clean_up_temporary_ip_blocklist' ) );
70 // Additional hooks.
71 add_action( 'defender_enqueue_assets', array( &$this, 'enqueue_assets' ), 11 );
72
73 $this->maybe_extend_mime_types();
74 }
75
76 /**
77 * Clean up all the old logs from the local storage, this will happen per hourly basis.
78 */
79 public function clean_up_firewall_logs() {
80 $this->service->firewall_clean_up_logs();
81 }
82
83 /**
84 * Clean up temporary IP block list.
85 */
86 public function clean_up_temporary_ip_blocklist() {
87 $this->service->firewall_clean_up_temporary_ip_blocklist();
88 }
89
90 /**
91 * This is for handling request from dashboard.
92 * @defender_route
93 * @return Response
94 */
95 public function dashboard_activation() {
96 $il = wd_di()->get( \WP_Defender\Model\Setting\Login_Lockout::class );
97 $nf = wd_di()->get( Notfound_Lockout::class );
98 $ua = wd_di()->get( User_Agent_Lockout::class );
99 $il->enabled = true;
100 $il->save();
101 $nf->enabled = true;
102 $nf->save();
103 $ua->enabled = true;
104 $ua->save();
105
106 return new Response( true, $this->to_array() );
107 }
108
109 /**
110 * Render the view page.
111 */
112 public function main_view() {
113 $this->render( 'main' );
114 }
115
116 /**
117 * Save the main settings.
118 * @param Request $request
119 *
120 * @return Response
121 * @defender_route
122 */
123 public function save_settings( Request $request ) {
124 $data = $request->get_data_by_model( $this->model );
125 $this->model->import( $data );
126 if ( $this->model->validate() ) {
127 $this->service->update_cron_schedule_interval( $data['ip_blocklist_cleanup_interval'] );
128 $this->model->save();
129 Config_Hub_Helper::set_clear_active_flag();
130
131 return new Response(
132 true,
133 array(
134 'message' => __( 'Your settings have been updated.', 'wpdef' ),
135 )
136 );
137 }
138
139 return new Response(
140 false,
141 array(
142 'message' => $this->model->get_formatted_errors(),
143 )
144 );
145 }
146
147 /**
148 * @return array
149 */
150 public function to_array() {
151 $il = wd_di()->get( \WP_Defender\Model\Setting\Login_Lockout::class );
152 $nf = wd_di()->get( Notfound_Lockout::class );
153 $ua = wd_di()->get( User_Agent_Lockout::class );
154
155 return array_merge(
156 array(
157 'summary' => array(
158 'ip' => array(
159 'week' => Lockout_Log::count_login_lockout_last_7_days(),
160 ),
161 'nf' => array(
162 'week' => Lockout_Log::count_404_lockout_last_7_days(),
163 ),
164 'ua' => array(
165 'week' => Lockout_Log::count_ua_lockout_last_7_days(),
166 ),
167 'lastLockout' => Lockout_Log::get_last_lockout_date(),
168 ),
169 'notification' => true,
170 'enabled' => $nf->enabled || $il->enabled || $ua->enabled,
171 'enable_login' => $il->enabled,
172 'enable_404' => $nf->enabled,
173 'enable_ua' => $ua->enabled,
174 ),
175 $this->dump_routes_and_nonces()
176 );
177 }
178
179 public function enqueue_assets() {
180 if ( ! $this->is_page_active() ) {
181 return;
182 }
183
184 wp_enqueue_media();
185
186 wp_localize_script( 'def-iplockout', 'iplockout', $this->data_frontend() );
187 wp_enqueue_script( 'def-iplockout' );
188 $this->enqueue_main_assets();
189
190 do_action( 'defender_ip_lockout_action_assets' );
191 }
192
193 /**
194 * Renders the preview of lockout screen.
195 */
196 private function maybe_show_demo_lockout() {
197 $is_test = HTTP::get( 'def-lockout-demo', 0 );
198 if ( 1 === (int) $is_test ) {
199 $type = HTTP::get( 'type' );
200
201 $remaining_time = 0;
202
203 switch ( $type ) {
204 case 'login':
205 $settings = wd_di()->get( \WP_Defender\Model\Setting\Login_Lockout::class );
206 $message = $settings->lockout_message;
207 $remaining_time = 3600;
208 break;
209 case '404':
210 $settings = wd_di()->get( Notfound_Lockout::class );
211 $message = $settings->lockout_message;
212 $remaining_time = 3600;
213 break;
214 case 'blocklist':
215 $settings = wd_di()->get( \WP_Defender\Model\Setting\Blacklist_Lockout::class );
216 $message = $settings->ip_lockout_message;
217 break;
218 case 'ua-lockout':
219 $settings = wd_di()->get( User_Agent_Lockout::class );
220 $message = $settings->message;
221 break;
222 default:
223 $message = __( 'Demo', 'wpdef' );
224 break;
225 }
226
227 $this->actions_for_blocked( $message, $remaining_time );
228 exit;
229 }
230 }
231
232 /**
233 * Run actions for locked entities.
234 *
235 * @param string $message The message to show.
236 * @param int $remaining_time Remaining countdown time in seconds.
237 */
238 private function actions_for_blocked( $message, $remaining_time = 0 ) {
239 ob_start();
240
241 if ( ! headers_sent() ) {
242 if ( ! defined( 'DONOTCACHEPAGE' ) ) {
243 define( 'DONOTCACHEPAGE', true );
244 }
245
246 header( 'HTTP/1.0 403 Forbidden' );
247 header( 'Cache-Control: no-cache, no-store, must-revalidate, max-age=0' ); // HTTP 1.1.
248 header( 'Pragma: no-cache' ); // HTTP 1.0.
249 header( 'Expires: ' . gmdate('D, d M Y H:i:s', time()-3600) . ' GMT' ); // Proxies.
250 header( 'Clear-Site-Data: "cache"' ); // Clear cache of the current request.
251
252 $this->render_partial(
253 'ip-lockout/locked',
254 array(
255 'message' => $message,
256 'remaining_time' => $remaining_time,
257 )
258 );
259 }
260
261 echo ob_get_clean();
262 exit();
263 }
264
265 /**
266 * We wil check and prevent the access if the current IP is blacklist, or get temporary banned.
267 */
268 public function maybe_lockout() {
269 do_action( 'wd_before_lockout' );
270
271 $the_list = wd_di()->get( \WP_Defender\Model\Setting\Blacklist_Lockout::class );
272 $service = wd_di()->get( Blacklist_Lockout::class );
273 $ip = $this->get_user_ip();
274
275 $model = Lockout_Ip::get( $ip );
276 $is_lockout_ip = is_object( $model ) && $model->is_locked();
277
278 $is_country_whitelisted = ! $service->is_blacklist( $ip ) &&
279 $service->is_country_whitelist( $ip ) && ! $is_lockout_ip;
280
281 // If this IP is whitelisted, so we don't need to blacklist this.
282 if ( $service->is_ip_whitelisted( $ip ) || $is_country_whitelisted ) {
283 return;
284 }
285 // Green light if access staff is enabled.
286 if ( $this->is_a_staff_access() ) {
287 return;
288 }
289
290 if ( $service->is_blacklist( $ip ) || $service->is_country_blacklist( $ip ) ) {
291 // This one is get blacklisted.
292 $this->actions_for_blocked( $the_list->ip_lockout_message );
293 }
294 $service_ua = wd_di()->get( Component_User_Agent::class );
295 if ( $service_ua->is_active_component() ) {
296 $user_agent = $service_ua->sanitize_user_agent();
297 if ( $service_ua->is_bad_post( $user_agent ) ) {
298 $service_ua->block_user_agent_or_ip( $user_agent, $ip, Component_User_Agent::REASON_BAD_POST );
299 $this->actions_for_blocked( $service_ua->get_message() );
300 }
301 if ( ! empty( $user_agent )
302 /**
303 * Additional conditions for User Agent.
304 *
305 * @param bool
306 * @param string $user_agent
307 * @param string $ip
308 *
309 * @since 3.1.0
310 */
311 && apply_filters(
312 'wd_user_agent_additional_check',
313 $service_ua->is_bad_user_agent( $user_agent ),
314 $user_agent,
315 $ip
316 )
317 ) {
318 // Todo: if we use a hook then we should extend cases with a custom reason and send it for log.
319 $service_ua->block_user_agent_or_ip( $user_agent, $ip, Component_User_Agent::REASON_BAD_USER_AGENT );
320 $this->actions_for_blocked( $service_ua->get_message() );
321 }
322 }
323
324 $notfound_lockout = wd_di()->get( Notfound_Lockout::class );
325 if ( $notfound_lockout->enabled && false === $notfound_lockout->detect_logged && is_user_logged_in() ) {
326 /**
327 * We don't need to check the IP if:
328 * the current user can logged-in and isn't from blacklisted,
329 * the option detect_404_logged is disabled.
330 */
331 return;
332 }
333
334 // Check blacklist.
335 $model = Lockout_Ip::get( $ip );
336 if ( is_object( $model ) && $model->is_locked() ) {
337 $remaining_time = $model->remaining_release_time();
338 $this->actions_for_blocked( $model->lockout_message, $remaining_time );
339 }
340 }
341
342 /**
343 * Check if the access is from our staff access.
344 *
345 * @return bool
346 */
347 private function is_a_staff_access() {
348 if ( defined( 'WPMUDEV_DISABLE_REMOTE_ACCESS' ) && true === constant( 'WPMUDEV_DISABLE_REMOTE_ACCESS' ) ) {
349 return false;
350 }
351
352 $wpmu_dev = new WPMUDEV();
353 $is_remote_access = $wpmu_dev->get_apikey() &&
354 true === \WPMUDEV_Dashboard::$api->remote_access_details( 'enabled' );
355
356 if (
357 $is_remote_access &&
358 (
359 $this->is_authenticated_staff_access() ||
360 $this->is_commencing_staff_access()
361 )
362 ) {
363 $access = \WPMUDEV_Dashboard::$site->get_option( 'remote_access' );
364 $this->log( var_export( $access, true ), self::FIREWALL_LOG );
365
366 return true;
367 }
368
369 return false;
370 }
371
372 /**
373 * Check if the first commencing request is proper staff remote access.
374 *
375 * @return bool
376 */
377 private function is_commencing_staff_access() {
378 $access = \WPMUDEV_Dashboard::$site->get_option( 'remote_access' );
379
380 return wp_doing_ajax() &&
381 isset( $_GET['action'] ) &&
382 'wdpunauth' === $_GET['action'] &&
383 isset( $_POST['wdpunkey'] ) &&
384 hash_equals( $_POST['wdpunkey'], $access['key'] );
385 }
386
387 /**
388 * Check is the access from authenticated staff.
389 *
390 * @return bool
391 */
392 private function is_authenticated_staff_access() {
393
394 return isset( $_COOKIE['wpmudev_is_staff'] ) &&
395 '1' === $_COOKIE['wpmudev_is_staff'];
396 }
397
398 /**
399 * Remove all IP logs.
400 * @param Request $request
401 *
402 * @return Response
403 * @defender_route
404 */
405 public function empty_logs( Request $request ) {
406 if ( Lockout_Log::truncate() ) {
407
408 return new Response(
409 true,
410 array(
411 'message' => __( 'Your logs have been successfully deleted.', 'wpdef' ),
412 'interval' => 1,
413 )
414 );
415 }
416
417 return new Response(
418 false,
419 array(
420 'message' => __( 'Failed remove!', 'wpdef' ),
421 )
422 );
423 }
424
425 /**
426 * Return summary data.
427 *
428 * @return array
429 */
430 public function get_summary() {
431 $summary = Lockout_Log::get_summary();
432
433 return array(
434 'lockout_last' => isset( $summary['lockout_last'] ) ?
435 $this->format_date_time( $summary['lockout_last'] ) :
436 __( 'Never', 'wpdef' ),
437 'lockout_today' => $summary['lockout_today'] ?? 0,
438 'lockout_this_month' => $summary['lockout_this_month'] ?? 0,
439 'lockout_login_today' => $summary['lockout_login_today'] ?? 0,
440 'lockout_login_this_week' => $summary['lockout_login_this_week'] ?? 0,
441 'lockout_404_today' => $summary['lockout_404_today'] ?? 0,
442 'lockout_404_this_week' => $summary['lockout_404_this_week'] ?? 0,
443 'lockout_ua_today' => $summary['lockout_ua_today'] ?? 0,
444 'lockout_ua_this_week' => $summary['lockout_ua_this_week'] ?? 0,
445 );
446 }
447
448 public function remove_settings() {
449 ( new \WP_Defender\Model\Setting\Login_Lockout )->delete();
450 ( new \WP_Defender\Model\Setting\Blacklist_Lockout )->delete();
451 ( new Notfound_Lockout )->delete();
452 ( new \WP_Defender\Model\Setting\Firewall )->delete();
453 ( new User_Agent_Lockout() )->delete();
454 }
455
456 public function remove_data() {
457 Lockout_Log::truncate();
458 }
459
460 /**
461 * All the variables that we will show on frontend, both in the main page, or dashboard widget.
462 *
463 * @return array
464 */
465 public function data_frontend() {
466 $summary_data = $this->get_summary();
467 $data = array(
468 'login' => array(
469 'week' => $summary_data['lockout_login_this_week'],
470 'day' => $summary_data['lockout_login_today'],
471 ),
472 'nf' => array(
473 'week' => $summary_data['lockout_404_this_week'],
474 'day' => $summary_data['lockout_404_today'],
475 ),
476 'ua' => array(
477 'week' => $summary_data['lockout_ua_this_week'],
478 'day' => $summary_data['lockout_ua_today'],
479 ),
480 'month' => $summary_data['lockout_this_month'],
481 'day' => $summary_data['lockout_today'],
482 'last_lockout' => $summary_data['lockout_last'],
483 'settings' => $this->model->export(),
484 'login_lockout' => wd_di()->get( \WP_Defender\Model\Setting\Login_Lockout::class )->enabled,
485 'nf_lockout' => wd_di()->get( Notfound_Lockout::class )->enabled,
486 'report' => wd_di()->get( Firewall_Report::class )->to_string(),
487 'notification_lockout' => 'enabled' === wd_di()->get( Firewall_Notification::class )->status,
488 'ua_lockout' => wd_di()->get( User_Agent_Lockout::class )->enabled,
489 );
490
491 return array_merge( $data, $this->dump_routes_and_nonces() );
492 }
493
494 /**
495 * @param array $data
496 */
497 public function import_data( $data ) {
498 $model = $this->model;
499
500 $model->import( $data );
501 if ( $model->validate() ) {
502 $model->save();
503 }
504 }
505
506 /**
507 * @return array
508 */
509 public function export_strings() {
510 $strings = array( __( 'Active', 'wpdef' ) );
511 $is_pro = ( new \WP_Defender\Behavior\WPMUDEV() )->is_pro();
512 $firewall_notification = new \WP_Defender\Model\Notification\Firewall_Notification();
513 $firewall_report = new \WP_Defender\Model\Notification\Firewall_Report();
514 $model_ua_lockout = new \WP_Defender\Model\Setting\User_Agent_Lockout();
515
516 $strings[] = sprintf(
517 /* translators: ... */
518 __( 'User agent banning %s', 'wpdef' ),
519 (bool) $model_ua_lockout->enabled ? __( 'active', 'wpdef' ) : __( 'inactive', 'wpdef' )
520 );
521 if ( 'enabled' === $firewall_notification->status ) {
522 $strings[] = __( 'Email notifications active', 'wpdef' );
523 }
524 if ( $is_pro && 'enabled' === $firewall_report->status ) {
525 $strings[] = sprintf(
526 /* translators: ... */
527 __( 'Email reports sending %s', 'wpdef' ),
528 $firewall_report->frequency
529 );
530 } elseif ( ! $is_pro ) {
531 $strings[] = sprintf(
532 /* translators: ... */
533 __( 'Email report inactive %s', 'wpdef' ),
534 '<span class="sui-tag sui-tag-pro">Pro</span>'
535 );
536 }
537
538 return $strings;
539 }
540
541 /**
542 * @param array $config
543 * @param bool $is_pro
544 *
545 * @return array
546 */
547 public function config_strings( $config, $is_pro ) {
548 $strings = array( __( 'Active', 'wpdef' ) );
549 if ( isset( $config['ua_banning_enabled'] ) ) {
550 $strings[] = sprintf(
551 /* translators: ... */
552 __( 'User agent banning %s', 'wpdef' ),
553 (bool) $config['ua_banning_enabled'] ? __( 'active', 'wpdef' ) : __( 'inactive', 'wpdef' )
554 );
555 }
556 if ( isset( $config['notification'] ) && 'enabled' === $config['notification'] ) {
557 $strings[] = __( 'Email notifications active', 'wpdef' );
558 }
559 if ( $is_pro && 'enabled' === $config['report'] ) {
560 $strings[] = sprintf(
561 /* translators: ... */
562 __( 'Email reports sending %s', 'wpdef' ),
563 $config['report_frequency']
564 );
565 } elseif ( ! $is_pro ) {
566 $strings[] = sprintf(
567 /* translators: ... */
568 __( 'Email report inactive %s', 'wpdef' ),
569 '<span class="sui-tag sui-tag-pro">Pro</span>'
570 );
571 }
572
573 return $strings;
574 }
575
576 /**
577 * Schedule cleanup blocklist ips event.
578 */
579 private function schedule_cleanup_blocklist_ips_event() {
580 // Sometimes multiple requests come at the same time.
581 // So we will only count the web requests.
582 if ( defined( 'DOING_AJAX' ) || defined( 'DOING_CRON' ) ) {
583 return;
584 }
585
586 $clear = get_site_option( 'wpdef_clear_schedule_firewall_cleanup_temp_blocklist_ips', false );
587
588 if ( $clear ) {
589 wp_clear_scheduled_hook( 'firewall_cleanup_temp_blocklist_ips' );
590 }
591
592 if ( wp_next_scheduled( 'firewall_cleanup_temp_blocklist_ips' ) ) {
593 return;
594 }
595
596 $interval = $this->model->ip_blocklist_cleanup_interval;
597
598 if ( ! $interval || 'never' === $interval ) {
599 return;
600 }
601
602 wp_schedule_event( time() + 15, $interval, 'firewall_cleanup_temp_blocklist_ips' );
603 }
604
605 /**
606 * Maybe add a filter to extend mime types.
607 *
608 * @since 2.6.3
609 */
610 public function maybe_extend_mime_types() {
611 if ( is_admin() ) {
612 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
613 $current_query = wp_parse_url( $current_url, PHP_URL_QUERY );
614 $current_query = $current_query ?? '';
615 $referer_url = ! empty( $_SERVER['HTTP_REFERER'] ) ?
616 filter_var( $_SERVER['HTTP_REFERER'], FILTER_SANITIZE_URL ) :
617 '';
618 $referer_query = wp_parse_url( $referer_url, PHP_URL_QUERY );
619 $referer_query = $referer_query ?? '';
620
621 parse_str( $current_query, $current_queries );
622 parse_str( $referer_query, $referer_queries );
623
624 if (
625 ( preg_match( '#^' . network_admin_url() . '#i', $current_url ) &&
626 ! empty( $current_queries['page'] ) && $this->slug === $current_queries['page']
627 ) ||
628 ( preg_match( '#^' . network_admin_url() . '#i', $referer_url ) &&
629 ! empty( $referer_queries['page'] ) && $this->slug === $referer_queries['page']
630 )
631 ) {
632 // Add action hook here.
633 add_filter( 'upload_mimes', array( &$this, 'extend_mime_types' ) );
634 }
635 }
636 }
637
638 /**
639 * Filter list of allowed mime types and file extensions.
640 *
641 * @param array $types
642 *
643 * @return array
644 */
645 public function extend_mime_types( $types ) {
646 if ( empty( $types['csv'] ) ) {
647 $types['csv'] = 'text/csv';
648 }
649
650 return $types;
651 }
652 }
653