PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.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
← All changes | src/component/class-scan.php +111 -50 5.6.1 → 6.3.0 View file →
@@ -17,10 +17,8 @@
17 17 use WP_Defender\Behavior\Scan\Gather_Fact;
18 18 use WP_Defender\Behavior\Scan\Malware_Scan;
19 19 use WP_Defender\Behavior\Scan\Core_Integrity;
20 20 use WP_Defender\Behavior\Scan\Plugin_Integrity;
21 -use WP_Defender\Behavior\Scan\Malware_Deep_Scan;
22 -use WP_Defender\Behavior\Scan\Malware_Quick_Scan;
23 21 use WP_Defender\Behavior\Scan\Known_Vulnerability;
24 22 use WP_Defender\Behavior\Scan\Abandoned_Plugin;
25 23 use WP_Defender\Model\Setting\Scan as Scan_Settings;
26 24 use WP_Defender\Helper\Analytics\Scan as Scan_Analytics;
@@ -61,9 +59,8 @@
61 59 *
62 60 * @var Known_Vulnerability
63 61 */
64 62 private $known_vulnerability;
65 -
66 63 /**
67 64 * Instance of Malware_Scan to handle malware scanning.
68 65 *
69 66 * @var Malware_Scan
@@ -74,9 +71,9 @@
74 71 * Instance of Gather_Fact to gather necessary information before scanning.
75 72 *
76 73 * @var Gather_Fact|null
77 74 */
78 - private ?Gather_Fact $gather_fact;
75 + private ?Gather_Fact $gather_fact = null;
79 76
80 77 /**
81 78 * Instance of Abandoned_Plugin to handle abandoned plugin checks.
82 79 *
@@ -91,13 +88,13 @@
91 88 */
92 89 protected string $lock_filename = 'scan.lock';
93 90
94 91 /**
95 - * Indicate whether the current installation is a pro version.
92 + * Is the Hub API key available?
96 93 *
97 94 * @var bool
98 95 */
99 - private $is_pro;
96 + private $is_apikey;
100 97
101 98 /**
102 99 * Constructs the Scan object and initializes behaviors.
103 100 */
@@ -104,10 +101,11 @@
104 101 public function __construct() {
105 102 $this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
106 103 $this->attach_behavior( Core_Integrity::class, Core_Integrity::class );
107 104 $this->attach_behavior( Plugin_Integrity::class, Plugin_Integrity::class );
108 - $this->is_pro = wd_di()->get( WPMUDEV::class )->is_pro();
109 - $this->settings = wd_di()->get( Scan_Settings::class );
105 +
106 + $this->is_apikey = wd_di()->get( WPMUDEV::class )->is_apikey_available();
107 + $this->settings = wd_di()->get( Scan_Settings::class );
110 108 }
111 109
112 110 /**
113 111 * Performs additional actions after an advanced scan.
@@ -141,11 +139,13 @@
141 139 if ( Scan_Model::STATUS_INIT === $scan->status ) {
142 140 // Get the first.
143 141 $this->log( 'Prepare facts for a scan', Scan_Controller::SCAN_LOG );
144 142 $task = Scan_Model::STEP_GATHER_INFO;
143 + $this->scan->status = $task;
145 144 $this->scan->percent = 0;
146 145 $this->scan->total_tasks = $runner->count();
147 146 $this->scan->save();
147 + $this->scan->enqueue_status_message( $this->scan->get_status_text() );
148 148 }
149 149 if (
150 150 in_array(
151 151 $this->scan->status,
@@ -181,8 +181,9 @@
181 181 $this->scan->status = $runner->key();
182 182 $this->scan->task_checkpoint = '';
183 183 $this->scan->date_end = gmdate( 'Y-m-d H:i:s' );
184 184 $this->scan->save();
185 + $this->scan->enqueue_status_message( $this->scan->get_status_text() );
185 186 // Queue for next run.
186 187 return false;
187 188 }
188 189 $this->log( 'All done!', Scan_Controller::SCAN_LOG );
@@ -239,13 +240,16 @@
239 240
240 241 if ( $this->settings->check_abandoned_plugin ) {
241 242 $tasks[ Scan_Model::STEP_ABANDONED_PLUGIN_CHECK ] = 'abandoned_plugin_check';
242 243 }
243 - if ( $this->is_pro ) {
244 + // Check if this is HC feature.
245 + if ( $this->is_apikey ) {
244 246 if ( $this->settings->check_known_vuln && $this->has_method( Scan_Model::STEP_VULN_CHECK ) ) {
245 247 $tasks[ Scan_Model::STEP_VULN_CHECK ] = 'vuln_check';
246 248 }
247 - if ( $this->settings->scan_malware && $this->has_method( Scan_Model::STEP_SUSPICIOUS_CHECK ) ) {
249 + // The division between PHP and JS files occurs when collecting files.
250 + if ( $this->settings->scan_malware && $this->has_method( Scan_Model::STEP_SUSPICIOUS_CHECK )
251 + ) {
248 252 $tasks[ Scan_Model::STEP_SUSPICIOUS_CHECK ] = 'suspicious_check';
249 253 }
250 254 }
251 255
@@ -261,9 +265,9 @@
261 265 */
262 266 private function task_handler( $task ) {
263 267 switch ( $task ) {
264 268 case 'gather_info':
265 - if ( empty( $this->gather_fact ) && class_exists( Gather_Fact::class ) ) {
269 + if ( ! ( $this->gather_fact instanceof Gather_Fact ) && class_exists( Gather_Fact::class ) ) {
266 270 $this->set_gather_fact(
267 271 wd_di()->make( Gather_Fact::class, array( 'scan' => $this->scan ) )
268 272 );
269 273 }
@@ -269,9 +273,9 @@
269 273 }
270 274
271 275 return $this->gather_info( $this->gather_fact );
272 276 case 'vuln_check':
273 - if ( empty( $this->known_vulnerability ) && class_exists( Known_Vulnerability::class ) ) {
277 + if ( ! ( $this->known_vulnerability instanceof Known_Vulnerability ) && class_exists( Known_Vulnerability::class ) ) {
274 278 $this->set_known_vulnerability(
275 279 wd_di()->make( Known_Vulnerability::class, array( 'scan' => $this->scan ) )
276 280 );
277 281 }
@@ -285,9 +289,9 @@
285 289 }
286 290
287 291 return $this->suspicious_check( $this->malware_scan );
288 292 case 'abandoned_plugin_check':
289 - if ( empty( $this->abandoned_plugin ) && class_exists( Abandoned_Plugin::class ) ) {
293 + if ( ! ( $this->abandoned_plugin instanceof Abandoned_Plugin ) && class_exists( Abandoned_Plugin::class ) ) {
290 294 $this->set_abandoned_plugin(
291 295 wd_di()->make( Abandoned_Plugin::class, array( 'scan' => $this->scan ) )
292 296 );
293 297 }
@@ -292,10 +296,14 @@
292 296 );
293 297 }
294 298
295 299 return $this->abandoned_plugin_check( $this->abandoned_plugin );
300 + case 'core_integrity_check':
301 + return $this->core_integrity_check();
302 + case 'plugin_integrity_check':
303 + return $this->plugin_integrity_check();
296 304 default:
297 - return $this->$task();
305 + return false;
298 306 }
299 307 }
300 308
301 309 /**
@@ -324,9 +332,9 @@
324 332 }
325 333 }
326 334
327 335 /**
328 - * A wrapper method for Malware_Scan class method vuln_check.
336 + * A wrapper method for Malware_Scan class method suspicious_check.
329 337 *
330 338 * @param Malware_Scan $malware_scan An instance of Malware_Scan.
331 339 *
332 340 * @return bool True if method Malware_Scan::suspicious_check not exists else bool value returned by that method.
@@ -332,10 +340,10 @@
332 340 * @return bool True if method Malware_Scan::suspicious_check not exists else bool value returned by that method.
333 341 */
334 342 private function suspicious_check( Malware_Scan $malware_scan ): bool {
335 343 if ( method_exists( $malware_scan, 'suspicious_check' ) ) {
336 - $quick_scan = wd_di()->get( Malware_Quick_Scan::class );
337 - $deep_scan = wd_di()->get( Malware_Deep_Scan::class );
344 + $quick_scan = wd_di()->get( \WP_Defender\Behavior\Scan\Malware_Quick_Scan::class );
345 + $deep_scan = wd_di()->get( \WP_Defender\Behavior\Scan\Malware_Deep_Scan::class );
338 346
339 347 return $malware_scan->suspicious_check( $quick_scan, $deep_scan );
340 348 }
341 349
@@ -387,9 +395,9 @@
387 395 if ( is_object( $scan ) ) {
388 396 $scan->delete();
389 397 }
390 398 $this->clean_up();
391 - $this->remove_lock();
399 + $this->remove_lock( $this->lock_filename );
392 400
393 401 $scan_analytics = wd_di()->get( Scan_Analytics::class );
394 402
395 403 $scan_analytics->track_feature(
@@ -428,9 +436,9 @@
428 436 public function clean_up() {
429 437 $this->delete_interim_data();
430 438
431 439 $models = Scan_Model::get_last_all();
432 - if ( ! empty( $models ) ) {
440 + if ( is_array( $models ) && array() !== $models ) {
433 441 // Remove the latest. Don't remove code to find the first value.
434 442 $current = array_shift( $models );
435 443 foreach ( $models as $model ) {
436 444 $model->delete();
@@ -454,8 +462,30 @@
454 462 return $count;
455 463 }
456 464
457 465 /**
466 + * Checks if any scan type is active.
467 + *
468 + * @return bool True if any scan type is active, false otherwise.
469 + */
470 + public function is_any_scan_type_active(): bool {
471 + $settings = $this->settings;
472 + $file_change_check = $settings->is_checked_any_file_change_types();
473 + // Check#1 the 'File change detection' type because only it's available with nested types.
474 + // Check#2 the Abandoned plugin type.
475 + $check_free_settings = $file_change_check || $settings->check_abandoned_plugin;
476 + // Indicates if the API key is available for HC features.
477 + if ( $this->is_apikey ) {
478 + // HC version. Check all parent types.
479 + return $check_free_settings || $settings->check_known_vuln || $settings->scan_malware;
480 + } else {
481 + // Without HC access.
482 + return $check_free_settings;
483 + }
484 + }
485 +
486 +
487 + /**
458 488 * Checks if any scan type is active based on the scan settings and the user's membership status.
459 489 *
460 490 * @param array $scan_settings The scan settings.
461 491 *
@@ -460,16 +490,16 @@
460 490 * @param array $scan_settings The scan settings.
461 491 *
462 492 * @return bool Returns true if any scan type is active, false otherwise.
463 493 */
464 - public function is_any_scan_active( $scan_settings ): bool {
465 - if ( empty( $scan_settings['integrity_check'] ) ) {
494 + public function check_scan_active_by( $scan_settings ): bool {
495 + if ( ! isset( $scan_settings['integrity_check'] ) || ! $scan_settings['integrity_check'] ) {
466 496 // Check the parent type.
467 497 $file_change_check = false;
468 498 } elseif (
469 499 $scan_settings['integrity_check']
470 - && empty( $scan_settings['check_core'] )
471 - && empty( $scan_settings['check_plugins'] )
500 + && ( ! isset( $scan_settings['check_core'] ) || ! $scan_settings['check_core'] )
501 + && ( ! isset( $scan_settings['check_plugins'] ) || ! $scan_settings['check_plugins'] )
472 502 ) {
473 503 // Check the parent and child types.
474 504 $file_change_check = false;
475 505 } else {
@@ -474,17 +504,19 @@
474 504 $file_change_check = false;
475 505 } else {
476 506 $file_change_check = true;
477 507 }
478 - // Similar to is_any_active(...) method from the controller.
479 - if ( $this->is_pro ) {
480 - // Pro version. Check all parent types.
481 - return $file_change_check || ! empty( $scan_settings['check_known_vuln'] )
482 - || ! empty( $scan_settings['scan_malware'] );
483 - } else {
484 - // Free version.
485 - return $file_change_check || ! empty( $scan_settings['check_abandoned_plugin'] );
508 +
509 + $check_free_settings = $file_change_check
510 + || ( isset( $scan_settings['check_abandoned_plugin'] ) && $scan_settings['check_abandoned_plugin'] );
511 + // For HC features.
512 + if ( $this->is_apikey ) {
513 + // Similar to is_any_scan_type_active() method.Check all parent types including HC features.
514 + return $check_free_settings || ( isset( $scan_settings['check_known_vuln'] ) && $scan_settings['check_known_vuln'] )
515 + || ( isset( $scan_settings['scan_malware'] ) && $scan_settings['scan_malware'] );
486 516 }
517 +
518 + return $check_free_settings;
487 519 }
488 520
489 521 /**
490 522 * Update the idle scan status due the time limit.
@@ -500,15 +532,17 @@
500 532 $ready_to_send = true;
501 533 }
502 534 $this->delete_interim_data();
503 535
504 - as_unschedule_all_actions( 'defender/async_scan' );
536 + if ( function_exists( 'as_unschedule_all_actions' ) ) {
537 + as_unschedule_all_actions( 'defender/async_scan' );
538 + }
505 539
506 540 $idle_scan->status = Scan_Model::STATUS_IDLE;
507 541 $idle_scan->task_checkpoint = 'time_limit';
508 542 $idle_scan->save();
509 543
510 - $this->remove_lock();
544 + $this->remove_lock( $this->lock_filename );
511 545 if ( $ready_to_send ) {
512 546 do_action( 'defender_notify', 'malware-notification', $idle_scan );
513 547 }
514 548 }
@@ -527,15 +561,17 @@
527 561 $ready_to_send = true;
528 562 }
529 563 $this->delete_interim_data();
530 564
531 - as_unschedule_all_actions( 'defender/async_scan' );
565 + if ( function_exists( 'as_unschedule_all_actions' ) ) {
566 + as_unschedule_all_actions( 'defender/async_scan' );
567 + }
532 568
533 569 $scan->status = Scan_Model::STATUS_IDLE;
534 570 $scan->task_checkpoint = 'checksum_issue';
535 571 $scan->save();
536 572
537 - $this->remove_lock();
573 + $this->remove_lock( $this->lock_filename );
538 574 if ( $ready_to_send ) {
539 575 do_action( 'defender_notify', 'malware-notification', $scan );
540 576 }
541 577 }
@@ -550,8 +586,9 @@
550 586 delete_site_option( Gather_Fact::CACHE_CONTENT );
551 587 delete_site_option( Malware_Scan::YARA_RULES );
552 588 delete_site_option( Core_Integrity::CACHE_CHECKSUMS );
553 589 delete_site_option( Plugin_Integrity::PLUGIN_SLUGS );
590 + delete_site_option( Plugin_Integrity::PLUGIN_SLUGS_CHECKPOINT );
554 591 delete_site_option( Plugin_Integrity::PLUGIN_PREMIUM_SLUGS );
555 592 delete_site_option( self::PLUGINS_ACTIONED );
556 593 $this->maybe_track_failed_checksum();
557 594 }
@@ -584,20 +621,20 @@
584 621 'WP_Plugins_List_Table',
585 622 array( 'screen' => get_current_screen() )
586 623 );
587 624 $bugs = $this->vulnerability_details[ $file ]['bugs'];
588 - if ( empty( $bugs ) ) {
625 + if ( ! is_array( $bugs ) || array() === $bugs ) {
589 626 return;
590 627 }
591 628 $last_fixed_in = '0';
592 629 // Check if there have been updates since the last scan.
593 630 $exist_update = true;
594 - if ( isset( $plugin_data['Version'] ) && ! empty( $plugin_data['Version'] ) ) {
631 + if ( isset( $plugin_data['Version'] ) && '' !== $plugin_data['Version'] ) {
595 632 // The current plugin version.
596 633 $current_version = $plugin_data['Version'];
597 634 foreach ( $bugs as $bug_details ) {
598 635 // If the fixed version is existed then get the latest one.
599 - if ( isset( $bug_details['fixed_in'] ) && ! empty( $bug_details['fixed_in'] )
636 + if ( isset( $bug_details['fixed_in'] ) && '' !== $bug_details['fixed_in']
600 637 && version_compare( $bug_details['fixed_in'], $last_fixed_in, '>' )
601 638 ) {
602 639 $last_fixed_in = $bug_details['fixed_in'];
603 640 }
@@ -609,9 +646,9 @@
609 646 // If there were updates, do not display notice.
610 647 if ( $exist_update ) {
611 648 return;
612 649 }
613 - if ( empty( $plugin_data['slug'] ) && isset( $this->vulnerability_details[ $file ]['base_slug'] ) ) {
650 + if ( ( ! isset( $plugin_data['slug'] ) || '' === $plugin_data['slug'] ) && isset( $this->vulnerability_details[ $file ]['base_slug'] ) ) {
614 651 $plugin_data['slug'] = $this->vulnerability_details[ $file ]['base_slug'];
615 652 }
616 653
617 654 if ( is_network_admin() || ! is_multisite() ) {
@@ -631,9 +668,9 @@
631 668 'notice-error'
632 669 );
633 670
634 671 $notice = sprintf(
635 - /* translators: %s - Plugin name. */
672 + /* translators: %s: Plugin name. */
636 673 esc_html__(
637 674 '%s has detected a vulnerability in this plugin that may cause harm to your site.',
638 675 'defender-security'
639 676 ),
@@ -648,9 +685,9 @@
648 685 $notice .= implode( '<br/>', $lines );
649 686 $notice .= '<hr/><span class="vulnerability-indent"></span>';
650 687 if ( '0' !== $last_fixed_in ) {
651 688 $notice .= sprintf(
652 - /* translators: %s - Version number. */
689 + /* translators: %s: Version number. */
653 690 esc_html__(
654 691 'The vulnerability has been fixed in version %s. We recommend that you update this plugin accordingly.',
655 692 'defender-security'
656 693 ),
@@ -663,15 +700,15 @@
663 700 );
664 701 }
665 702 } else {
666 703 $notice .= '<br/><span class="vulnerability-indent"></span>' . $bugs[0]['title'] . '<br/><span class="vulnerability-indent"></span>';
667 - $notice .= empty( $last_fixed_in )
704 + $notice .= '0' === $last_fixed_in
668 705 ? esc_html__(
669 706 'We recommend that you deactivate this plugin until the vulnerability has been fixed.',
670 707 'defender-security'
671 708 )
672 709 : sprintf(
673 - /* translators: 1: Version number. */
710 + /* translators: %s: Version number. */
674 711 esc_html__(
675 712 'The vulnerability has been fixed in version %s. We recommend that you update this plugin accordingly.',
676 713 'defender-security'
677 714 ),
@@ -695,9 +732,9 @@
695 732
696 733 $last = Scan_Model::get_last();
697 734 if ( is_object( $last ) && ! is_wp_error( $last ) ) {
698 735 $vulnerability_issues = $last->get_issues( Scan_Item::TYPE_VULNERABILITY );
699 - if ( empty( $vulnerability_issues ) ) {
736 + if ( ! is_array( $vulnerability_issues ) || array() === $vulnerability_issues ) {
700 737 return;
701 738 }
702 739
703 740 add_action( 'admin_print_styles-plugins.php', array( $this, 'show_plugin_admin_styles' ) );
@@ -726,12 +763,12 @@
726 763 */
727 764 public static function clear_logs() {
728 765 global $wpdb;
729 766
730 - $table_actions = ! empty( $wpdb->actionscheduler_actions ) ?
767 + $table_actions = isset( $wpdb->actionscheduler_actions ) && '' !== $wpdb->actionscheduler_actions ?
731 768 $wpdb->actionscheduler_actions :
732 769 $wpdb->prefix . 'actionscheduler_actions';
733 - $table_logs = ! empty( $wpdb->actionscheduler_logs ) ?
770 + $table_logs = isset( $wpdb->actionscheduler_logs ) && '' !== $wpdb->actionscheduler_logs ?
734 771 $wpdb->actionscheduler_logs :
735 772 $wpdb->prefix . 'actionscheduler_logs';
736 773
737 774 $table_count = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
@@ -760,9 +797,9 @@
760 797 $limit
761 798 )
762 799 );
763 800 while ( $action_ids ) {
764 - if ( empty( $action_ids ) ) {
801 + if ( ! is_array( $action_ids ) || array() === $action_ids ) {
765 802 break;
766 803 }
767 804
768 805 $query = "DELETE as_actions, as_logs FROM {$table_actions} as_actions LEFT JOIN {$table_logs} as_logs ON as_actions.action_id = as_logs.action_id WHERE as_actions.action_id IN ( " . implode(
@@ -822,9 +859,9 @@
822 859 return true;
823 860 }
824 861
825 862 /**
826 - * Gey intentions.
863 + * Get intentions.
827 864 *
828 865 * @since 4.11.0
829 866 * @return array
830 867 */
@@ -854,9 +891,9 @@
854 891 if ( $this->settings->integrity_check && $this->settings->check_plugins ) {
855 892 $is_plugin_used = true;
856 893 } elseif ( $this->settings->check_abandoned_plugin ) {
857 894 $is_plugin_used = true;
858 - } elseif ( $this->is_pro && ( $this->settings->check_known_vuln || $this->settings->scan_malware ) ) {
895 + } elseif ( $this->is_apikey && ( $this->settings->check_known_vuln || $this->settings->scan_malware ) ) {
859 896 $is_plugin_used = true;
860 897 }
861 898
862 899 if ( $is_plugin_used ) {
@@ -867,9 +904,10 @@
867 904 * @param array $slugs Slugs of excluded plugins.
868 905 *
869 906 * @since 3.1.0
870 907 */
871 - $excluded_slugs = (array) apply_filters( 'wd_scan_excluded_plugin_slugs', array() );
908 + $excluded_slugs = apply_filters( 'wd_scan_excluded_plugin_slugs', array() );
909 + $excluded_slugs = ! is_array( $excluded_slugs ) ? (array) $excluded_slugs : $excluded_slugs;
872 910
873 911 foreach ( $this->get_plugins() as $slug => $item ) {
874 912 if ( is_object( $model ) && $model->is_issue_ignored( $slug ) ) {
875 913 continue;
@@ -900,6 +938,29 @@
900 938 * @return bool
901 939 */
902 940 public static function are_actioned_plugins( $actioned_plugins ): bool {
903 941 return is_array( $actioned_plugins ) && array() !== $actioned_plugins;
942 + }
943 +
944 + /**
945 + * Get the lock filename.
946 + *
947 + * @return string
948 + */
949 + public function get_lock_filename(): string {
950 + return $this->lock_filename;
951 + }
952 +
953 + /**
954 + * Get the text for the emergency scan stop case.
955 + *
956 + * @return string
957 + */
958 + public static function get_emergency_scan_stop_text(): string {
959 + $text = __( 'Scan aborted. No scan types are enabled. Enable at least one scan type under Defender > Settings > Tools before running a scan.', 'defender-security' );
960 + if ( defender_is_wp_cli() ) {
961 + return $text;
962 + }
963 +
964 + return esc_html( $text );
904 965 }
905 966 }