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

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

636 lines 18.0 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 ) && $service_ua->is_bad_user_agent( $user_agent ) ) {
302 $service_ua->block_user_agent_or_ip( $user_agent, $ip, Component_User_Agent::REASON_BAD_USER_AGENT );
303 $this->actions_for_blocked( $service_ua->get_message() );
304 }
305 }
306
307 $notfound_lockout = wd_di()->get( Notfound_Lockout::class );
308 if ( $notfound_lockout->enabled && false === $notfound_lockout->detect_logged && is_user_logged_in() ) {
309 /**
310 * We don't need to check the IP if:
311 * the current user can logged-in and isn't from blacklisted,
312 * the option detect_404_logged is disabled.
313 */
314 return;
315 }
316
317 // Check blacklist.
318 $model = Lockout_Ip::get( $ip );
319 if ( is_object( $model ) && $model->is_locked() ) {
320 $remaining_time = $model->remaining_release_time();
321 $this->actions_for_blocked( $model->lockout_message, $remaining_time );
322 }
323 }
324
325 /**
326 * Check if the access is from our staff access.
327 *
328 * @return bool
329 */
330 private function is_a_staff_access() {
331 if ( defined( 'WPMUDEV_DISABLE_REMOTE_ACCESS' ) && true === constant( 'WPMUDEV_DISABLE_REMOTE_ACCESS' ) ) {
332 return false;
333 }
334
335 $wpmu_dev = new WPMUDEV();
336 $is_remote_access = $wpmu_dev->get_apikey() &&
337 true === \WPMUDEV_Dashboard::$api->remote_access_details( 'enabled' );
338
339 if (
340 $is_remote_access &&
341 (
342 $this->is_authenticated_staff_access() ||
343 $this->is_commencing_staff_access()
344 )
345 ) {
346 $access = \WPMUDEV_Dashboard::$site->get_option( 'remote_access' );
347 $this->log( var_export( $access, true ), self::FIREWALL_LOG );
348
349 return true;
350 }
351
352 return false;
353 }
354
355 /**
356 * Check if the first commencing request is proper staff remote access.
357 *
358 * @return bool
359 */
360 private function is_commencing_staff_access() {
361 $access = \WPMUDEV_Dashboard::$site->get_option( 'remote_access' );
362
363 return wp_doing_ajax() &&
364 isset( $_GET['action'] ) &&
365 'wdpunauth' === $_GET['action'] &&
366 isset( $_POST['wdpunkey'] ) &&
367 hash_equals( $_POST['wdpunkey'], $access['key'] );
368 }
369
370 /**
371 * Check is the access from authenticated staff.
372 *
373 * @return bool
374 */
375 private function is_authenticated_staff_access() {
376
377 return isset( $_COOKIE['wpmudev_is_staff'] ) &&
378 '1' === $_COOKIE['wpmudev_is_staff'];
379 }
380
381 /**
382 * Remove all IP logs.
383 * @param Request $request
384 *
385 * @return Response
386 * @defender_route
387 */
388 public function empty_logs( Request $request ) {
389 if ( Lockout_Log::truncate() ) {
390
391 return new Response(
392 true,
393 array(
394 'message' => __( 'Your logs have been successfully deleted.', 'wpdef' ),
395 'interval' => 1,
396 )
397 );
398 }
399
400 return new Response(
401 false,
402 array(
403 'message' => __( 'Failed remove!', 'wpdef' ),
404 )
405 );
406 }
407
408 /**
409 * Return summary data.
410 *
411 * @return array
412 */
413 public function get_summary() {
414 $summary = Lockout_Log::get_summary();
415
416 return array(
417 'lockout_last' => isset( $summary['lockout_last'] ) ?
418 $this->format_date_time( $summary['lockout_last'] ) :
419 __( 'Never', 'wpdef' ),
420 'lockout_today' => $summary['lockout_today'] ?? 0,
421 'lockout_this_month' => $summary['lockout_this_month'] ?? 0,
422 'lockout_login_today' => $summary['lockout_login_today'] ?? 0,
423 'lockout_login_this_week' => $summary['lockout_login_this_week'] ?? 0,
424 'lockout_404_today' => $summary['lockout_404_today'] ?? 0,
425 'lockout_404_this_week' => $summary['lockout_404_this_week'] ?? 0,
426 'lockout_ua_today' => $summary['lockout_ua_today'] ?? 0,
427 'lockout_ua_this_week' => $summary['lockout_ua_this_week'] ?? 0,
428 );
429 }
430
431 public function remove_settings() {
432 ( new \WP_Defender\Model\Setting\Login_Lockout )->delete();
433 ( new \WP_Defender\Model\Setting\Blacklist_Lockout )->delete();
434 ( new Notfound_Lockout )->delete();
435 ( new \WP_Defender\Model\Setting\Firewall )->delete();
436 ( new User_Agent_Lockout() )->delete();
437 }
438
439 public function remove_data() {
440 Lockout_Log::truncate();
441 }
442
443 /**
444 * All the variables that we will show on frontend, both in the main page, or dashboard widget.
445 *
446 * @return array
447 */
448 public function data_frontend() {
449 $summary_data = $this->get_summary();
450 $data = array(
451 'login' => array(
452 'week' => $summary_data['lockout_login_this_week'],
453 'day' => $summary_data['lockout_login_today'],
454 ),
455 'nf' => array(
456 'week' => $summary_data['lockout_404_this_week'],
457 'day' => $summary_data['lockout_404_today'],
458 ),
459 'ua' => array(
460 'week' => $summary_data['lockout_ua_this_week'],
461 'day' => $summary_data['lockout_ua_today'],
462 ),
463 'month' => $summary_data['lockout_this_month'],
464 'day' => $summary_data['lockout_today'],
465 'last_lockout' => $summary_data['lockout_last'],
466 'settings' => $this->model->export(),
467 'login_lockout' => wd_di()->get( \WP_Defender\Model\Setting\Login_Lockout::class )->enabled,
468 'nf_lockout' => wd_di()->get( Notfound_Lockout::class )->enabled,
469 'report' => wd_di()->get( Firewall_Report::class )->to_string(),
470 'notification_lockout' => 'enabled' === wd_di()->get( Firewall_Notification::class )->status,
471 'ua_lockout' => wd_di()->get( User_Agent_Lockout::class )->enabled,
472 );
473
474 return array_merge( $data, $this->dump_routes_and_nonces() );
475 }
476
477 /**
478 * @param array $data
479 */
480 public function import_data( $data ) {
481 $model = $this->model;
482
483 $model->import( $data );
484 if ( $model->validate() ) {
485 $model->save();
486 }
487 }
488
489 /**
490 * @return array
491 */
492 public function export_strings() {
493 $strings = array( __( 'Active', 'wpdef' ) );
494 $is_pro = ( new \WP_Defender\Behavior\WPMUDEV() )->is_pro();
495 $firewall_notification = new \WP_Defender\Model\Notification\Firewall_Notification();
496 $firewall_report = new \WP_Defender\Model\Notification\Firewall_Report();
497 $model_ua_lockout = new \WP_Defender\Model\Setting\User_Agent_Lockout();
498
499 $strings[] = sprintf(
500 /* translators: ... */
501 __( 'User agent banning %s', 'wpdef' ),
502 (bool) $model_ua_lockout->enabled ? __( 'active', 'wpdef' ) : __( 'inactive', 'wpdef' )
503 );
504 if ( 'enabled' === $firewall_notification->status ) {
505 $strings[] = __( 'Email notifications active', 'wpdef' );
506 }
507 if ( $is_pro && 'enabled' === $firewall_report->status ) {
508 $strings[] = sprintf(
509 /* translators: ... */
510 __( 'Email reports sending %s', 'wpdef' ),
511 $firewall_report->frequency
512 );
513 } elseif ( ! $is_pro ) {
514 $strings[] = sprintf(
515 /* translators: ... */
516 __( 'Email report inactive %s', 'wpdef' ),
517 '<span class="sui-tag sui-tag-pro">Pro</span>'
518 );
519 }
520
521 return $strings;
522 }
523
524 /**
525 * @param array $config
526 * @param bool $is_pro
527 *
528 * @return array
529 */
530 public function config_strings( $config, $is_pro ) {
531 $strings = array( __( 'Active', 'wpdef' ) );
532 if ( isset( $config['ua_banning_enabled'] ) ) {
533 $strings[] = sprintf(
534 /* translators: ... */
535 __( 'User agent banning %s', 'wpdef' ),
536 (bool) $config['ua_banning_enabled'] ? __( 'active', 'wpdef' ) : __( 'inactive', 'wpdef' )
537 );
538 }
539 if ( isset( $config['notification'] ) && 'enabled' === $config['notification'] ) {
540 $strings[] = __( 'Email notifications active', 'wpdef' );
541 }
542 if ( $is_pro && 'enabled' === $config['report'] ) {
543 $strings[] = sprintf(
544 /* translators: ... */
545 __( 'Email reports sending %s', 'wpdef' ),
546 $config['report_frequency']
547 );
548 } elseif ( ! $is_pro ) {
549 $strings[] = sprintf(
550 /* translators: ... */
551 __( 'Email report inactive %s', 'wpdef' ),
552 '<span class="sui-tag sui-tag-pro">Pro</span>'
553 );
554 }
555
556 return $strings;
557 }
558
559 /**
560 * Schedule cleanup blocklist ips event.
561 */
562 private function schedule_cleanup_blocklist_ips_event() {
563 // Sometimes multiple requests come at the same time.
564 // So we will only count the web requests.
565 if ( defined( 'DOING_AJAX' ) || defined( 'DOING_CRON' ) ) {
566 return;
567 }
568
569 $clear = get_site_option( 'wpdef_clear_schedule_firewall_cleanup_temp_blocklist_ips', false );
570
571 if ( $clear ) {
572 wp_clear_scheduled_hook( 'firewall_cleanup_temp_blocklist_ips' );
573 }
574
575 if ( wp_next_scheduled( 'firewall_cleanup_temp_blocklist_ips' ) ) {
576 return;
577 }
578
579 $interval = $this->model->ip_blocklist_cleanup_interval;
580
581 if ( ! $interval || 'never' === $interval ) {
582 return;
583 }
584
585 wp_schedule_event( time() + 15, $interval, 'firewall_cleanup_temp_blocklist_ips' );
586 }
587
588 /**
589 * Maybe add a filter to extend mime types.
590 *
591 * @since 2.6.3
592 */
593 public function maybe_extend_mime_types() {
594 if ( is_admin() ) {
595 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
596 $current_query = wp_parse_url( $current_url, PHP_URL_QUERY );
597 $current_query = $current_query ?? '';
598 $referer_url = ! empty( $_SERVER['HTTP_REFERER'] ) ?
599 filter_var( $_SERVER['HTTP_REFERER'], FILTER_SANITIZE_URL ) :
600 '';
601 $referer_query = wp_parse_url( $referer_url, PHP_URL_QUERY );
602 $referer_query = $referer_query ?? '';
603
604 parse_str( $current_query, $current_queries );
605 parse_str( $referer_query, $referer_queries );
606
607 if (
608 ( preg_match( '#^' . network_admin_url() . '#i', $current_url ) &&
609 ! empty( $current_queries['page'] ) && $this->slug === $current_queries['page']
610 ) ||
611 ( preg_match( '#^' . network_admin_url() . '#i', $referer_url ) &&
612 ! empty( $referer_queries['page'] ) && $this->slug === $referer_queries['page']
613 )
614 ) {
615 // Add action hook here.
616 add_filter( 'upload_mimes', array( &$this, 'extend_mime_types' ) );
617 }
618 }
619 }
620
621 /**
622 * Filter list of allowed mime types and file extensions.
623 *
624 * @param array $types
625 *
626 * @return array
627 */
628 public function extend_mime_types( $types ) {
629 if ( empty( $types['csv'] ) ) {
630 $types['csv'] = 'text/csv';
631 }
632
633 return $types;
634 }
635 }
636