integrity_check && $scan_settings->check_plugins ) { $plugin_cache = true; $arr = get_site_option( Plugin_Integrity::PLUGIN_SLUGS, false ); if ( is_array( $arr ) && ! empty( $arr ) ) { $plugin_slugs_changes = $arr; } $plugin_slugs = get_site_option( Plugin_Integrity::PLUGIN_PREMIUM_SLUGS, false ); if ( is_array( $plugin_slugs ) && ! empty( $plugin_slugs ) ) { $plugin_pro_slugs = $plugin_slugs; } $plugin_all_slugs = $this->get_plugin_slugs(); } return array( 'plugin_change' => $plugin_cache, // List of plugins with modifications. 'plugin_slugs_changes' => $plugin_slugs_changes, // List of pro plugins. 'plugin_pro_slugs' => $plugin_pro_slugs, 'plugin_all_slugs' => $plugin_all_slugs, ); } /** * @param string $file_path * @param array $rules * * @return bool */ protected function was_modificated_file( $file_path, $rules ) { // Unchecked 'Plugin change file' option, so green light to display Suspicious checks. if ( ! $rules['plugin_change'] ) { return true; } $search_on_plugin = WP_PLUGIN_DIR . '/'; if ( false !== stripos( $file_path, $search_on_plugin ) ) { /** * Suspicious code in /plugins. * Empty list of plugin slugs because there are premium plugins, * not modifications on Free plugins, * or not plugins on site. * Should check separate custom files/dirs in the root too. */ $rev_file = str_replace( $search_on_plugin, '', $file_path ); $matches = explode( '/', $rev_file ); $base_slug = array_shift( $matches ); // Custom files/dirs. if ( ! in_array( $base_slug, $rules['plugin_all_slugs'], true ) ) { return true; } if ( empty( $rules['plugin_slugs_changes'] ) && empty( $rules['plugin_pro_slugs'] ) ) { // No modifications. return false; } // Is it on premium plugins? if ( in_array( $base_slug, $rules['plugin_pro_slugs'], true ) ) { return true; } if ( in_array( $base_slug, (array) $rules['plugin_slugs_changes'], true ) ) { // Modifications in this plugin. return true; } // Modifications are not here. return false; } // Other WP places. return true; } public function suspicious_check() { set_time_limit( 0 ); $this->prepare_emergency_shutdown(); $timer = new Timer(); $rules = $this->fetch_yara_rules(); $this->attach_behavior( Malware_Quick_Scan::class, Malware_Quick_Scan::class ); $this->attach_behavior( Malware_Deep_Scan::class, Malware_Deep_Scan::class ); $model = $this->owner->scan; $pos = (int) $model->task_checkpoint; $files = get_site_option( Gather_Fact::CACHE_CONTENT, array() ); if ( empty( $files ) ) { return true; } $combinations = $this->get_additional_rules( new Scan_Settings() ); $files = new \ArrayIterator( $files ); $files->seek( $pos ); while ( $files->valid() ) { if ( ! $timer->check() ) { $this->log( 'Rage quit', 'malware_scan.log' ); $model->save(); break; } if ( $model->is_issue_ignored( $files->current() ) ) { $this->log( sprintf( 'skip %s because of file is ignored', $files->current() ), 'malware_scan.log' ); $files->next(); continue; } [$result, $qs_detail] = $this->do_quick_scan( $files->current(), $rules ); if ( $result ) { $this->log( sprintf( 'file %s suspicious', $files->current() ), 'malware_scan.log' ); $result = $this->do_deep_scan( $files->current(), $rules, $qs_detail ); /** * Add new item if Suspicious code is found and: * plugins/themes are premium, * plugins/themes are on wp.org but the code doesn't match from the WP repo (there are differences in checksums), * deactivated options of File change detection > Scan plugin OR theme file changes */ if ( is_array( $result ) && $this->was_modificated_file( $files->current(), $combinations ) ) { $result['file'] = $files->current(); $model->add_item( Scan_Item::TYPE_SUSPICIOUS, $result ); } } $files->next(); $model->task_checkpoint = $files->key(); $model->calculate_percent( $files->key() * 100 / $files->count(), 6 ); if ( 0 === $files->key() % 100 ) { // We should update the model percent each 100 files so we have some progress ont he screen$pos * 100 / $core_files->count() $model->save(); } } if ( ! $files->valid() ) { $last = \WP_Defender\Model\Scan::get_last(); if ( is_object( $last ) ) { $ignored_issues = $last->get_issues( Scan_Item::TYPE_SUSPICIOUS, Scan_Item::STATUS_IGNORE ); foreach ( $ignored_issues as $issue ) { $this->owner->scan->add_item( Scan_Item::TYPE_SUSPICIOUS, $issue->raw_data, Scan_Item::STATUS_IGNORE ); } } $model->task_checkpoint = null; } $model->save(); return ! $files->valid(); } /** * We will use this for a safe switch when memory out happen. */ public function prepare_emergency_shutdown() { $this->memory = str_repeat( '*', 1024 * 1024 ); register_shutdown_function( function () { if ( \WP_Defender\Model\Scan::STATUS_FINISH === $this->owner->scan->status ) { return; } $this->memory = null; $err = error_get_last(); if ( ( ! is_null( $err ) ) && ( ! in_array( $err['type'], array( E_NOTICE, E_WARNING, E_DEPRECATED ), true ) ) ) { $this->log( var_export( $err, true ), 'scan.log' ); $this->log( 'Something wrong happen, saving and quit.', 'scan.log' ); $this->owner->scan->status = \WP_Defender\Model\Scan::STATUS_ERROR; ++$this->owner->scan->task_checkpoint; $this->owner->scan->save(); } } ); } /** * Fetch yara rules from API. * * @return array|mixed */ private function fetch_yara_rules() { $rules = get_site_option( self::YARA_RULES, false ); if ( is_array( $rules ) ) { return $rules; } $this->attach_behavior( WPMUDEV::class, WPMUDEV::class ); $rules = $this->make_wpmu_request( WPMUDEV::API_SCAN_SIGNATURE ); if ( is_array( $rules ) ) { update_site_option( self::YARA_RULES, $rules ); return $rules; } return array(); } }