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/behavior/scan/class-plugin-integrity.php +49 -26 5.6.1 → 6.3.0 View file →
@@ -28,11 +28,12 @@
28 28
29 29 use IO;
30 30 use Plugin;
31 31
32 - public const URL_PLUGIN_VCS = 'https://downloads.wordpress.org/plugin-checksums/';
33 - public const PLUGIN_SLUGS = 'wd_plugin_slugs_changes';
34 - public const PLUGIN_PREMIUM_SLUGS = 'wd_plugin_premium_slugs';
32 + public const URL_PLUGIN_VCS = 'https://downloads.wordpress.org/plugin-checksums/';
33 + public const PLUGIN_SLUGS = 'wd_plugin_slugs_changes';
34 + public const PLUGIN_SLUGS_CHECKPOINT = 'wd_plugin_slugs_changes_checkpoint';
35 + public const PLUGIN_PREMIUM_SLUGS = 'wd_plugin_premium_slugs';
35 36 /**
36 37 * List of premium plugin slugs.
37 38 *
38 39 * @var array
@@ -45,10 +46,10 @@
45 46 * @param string $slug The slug to check.
46 47 *
47 48 * @return bool Returns true if the slug is valid, false otherwise.
48 49 */
49 - private function is_valid_wporg_slug( $slug ): bool {
50 - return ! empty( $slug ) && '.' !== $slug;
50 + private function is_valid_wporg_slug( string $slug ): bool {
51 + return '' !== $slug && '.' !== $slug;
51 52 }
52 53
53 54 /**
54 55 * Plucks a specific field from an array of objects or arrays and returns a new array with the plucked values.
@@ -53,23 +54,21 @@
53 54 /**
54 55 * Plucks a specific field from an array of objects or arrays and returns a new array with the plucked values.
55 56 *
56 57 * @param array $collection The array to pluck values from.
57 - * @param string $field The field to pluck from each element in the array.
58 58 * @param string $prefix Optional. A prefix to add to each key in the new array. Default is an empty string.
59 59 *
60 60 * @return array The new array with the plucked values.
61 61 */
62 - private function pluck( $collection, $field, $prefix = '' ): array {
62 + private function pluck( array $collection, $prefix = '' ): array {
63 63 $new_list = array();
64 64
65 65 foreach ( $collection as $key => $value ) {
66 66 $prefix_key = defender_replace_line( $prefix . $key );
67 -
68 - if ( is_object( $value ) ) {
69 - $new_list[ $prefix_key ] = $value->$field;
70 - } else {
71 - $new_list[ $prefix_key ] = $value[ $field ];
67 + if ( is_object( $value ) && property_exists( $value, 'md5' ) ) {
68 + $new_list[ $prefix_key ] = $value->md5;
69 + } elseif ( is_array( $value ) && isset( $value['md5'] ) ) {
70 + $new_list[ $prefix_key ] = $value['md5'];
72 71 }
73 72 }
74 73
75 74 return $new_list;
@@ -83,9 +82,8 @@
83 82 * @return bool True if it's a WPMUDEV pro plugin, false otherwise.
84 83 */
85 84 private function is_wpmudev_pro_plugin( $plugin_file ): bool {
86 85 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_file );
87 - // @phpstan-ignore-next-line - WDP ID is a custom header used by WPMUDEV plugins.
88 86 return array_key_exists( 'WDP ID', $plugin_data ) && '' !== $plugin_data['WDP ID'];
89 87 }
90 88
91 89 /**
@@ -97,9 +95,9 @@
97 95 * @param string $plugin_file Plugin main file path.
98 96 *
99 97 * @return array
100 98 */
101 - private function get_plugin_hash( $slug, $version, $plugin_file = '' ): array {
99 + private function get_plugin_hash( string $slug, $version, $plugin_file = '' ): array {
102 100 if ( ! $this->is_valid_wporg_slug( $slug ) ) {
103 101 $this->premium_slugs[] = $slug;
104 102
105 103 return array();
@@ -136,9 +134,9 @@
136 134 }
137 135
138 136 $data = json_decode( $body, true );
139 137
140 - if ( ! $data || empty( $data['files'] ) ) {
138 + if ( ! isset( $data['files'] ) || ! is_array( $data['files'] ) || array() === $data['files'] ) {
141 139 return array();
142 140 }
143 141
144 142 if ( ! $this->is_likely_wporg_slug( $slug ) ) {
@@ -146,9 +144,9 @@
146 144
147 145 return array();
148 146 }
149 147
150 - return $this->pluck( $data['files'], 'md5', $slug . DIRECTORY_SEPARATOR );
148 + return $this->pluck( $data['files'], $slug . DIRECTORY_SEPARATOR );
151 149 }
152 150
153 151 /**
154 152 * Fetch the checksums.
@@ -162,9 +160,9 @@
162 160 if ( Scan_Component::are_actioned_plugins( $actioned_plugins ) ) {
163 161 foreach ( $actioned_plugins as $slug => $plugin_data ) {
164 162 $plugin_hashes = $this->get_plugin_hash( $slug, $plugin_data['Version'], $plugin_data['Slug'] );
165 163
166 - if ( ! empty( $plugin_hashes ) ) {
164 + if ( array() !== $plugin_hashes ) {
167 165 $all_plugin_hashes = array_merge( $all_plugin_hashes, $plugin_hashes );
168 166 }
169 167 }
170 168 } else {
@@ -172,9 +170,9 @@
172 170 foreach ( $this->get_plugins() as $plugin_file => $plugin ) {
173 171 $slug = $this->get_plugin_slug_by( $plugin_file );
174 172 $plugin_hashes = $this->get_plugin_hash( $slug, $plugin['Version'], $plugin_file );
175 173
176 - if ( ! empty( $plugin_hashes ) ) {
174 + if ( array() !== $plugin_hashes ) {
177 175 $all_plugin_hashes = array_merge( $all_plugin_hashes, $plugin_hashes );
178 176 }
179 177 }
180 178 }
@@ -201,9 +199,14 @@
201 199 $settings->filesize
202 200 );
203 201
204 202 $plugin_files = $plugins->get_dir_tree();
205 - $plugin_files = array_filter( $plugin_files );
203 + $plugin_files = array_filter(
204 + $plugin_files,
205 + function ( $value ) {
206 + return is_string( $value ) && '' !== trim( $value );
207 + }
208 + );
206 209
207 210 $plugin_files = new ArrayIterator( $plugin_files );
208 211 $checksums = $this->plugin_checksum();
209 212 $timer = new Timer();
@@ -209,8 +212,11 @@
209 212 $timer = new Timer();
210 213 $model = $this->owner->scan;
211 214 $pos = (int) $model->task_checkpoint;
212 215 $plugin_files->seek( $pos );
216 + if ( 0 === $pos ) {
217 + delete_site_option( self::PLUGIN_SLUGS_CHECKPOINT );
218 + }
213 219 $slugs_of_edited_plugins = array();
214 220 $integration_smush = wd_di()->get( Smush::class );
215 221 $exist_smush_images = $integration_smush->exist_image_table();
216 222 while ( $plugin_files->valid() ) {
@@ -301,16 +307,15 @@
301 307 // We will check if we have any ignore issue from last scan, so we can bring it here.
302 308 $last = Scan::get_last();
303 309 if ( is_object( $last ) ) {
304 310 $ignored_issues = $last->get_issues( Scan_Item::TYPE_PLUGIN_CHECK, Scan_Item::STATUS_IGNORE );
305 - foreach ( $ignored_issues as $issue ) {
306 - $model->add_item( Scan_Item::TYPE_PLUGIN_CHECK, $issue->raw_data, Scan_Item::STATUS_IGNORE );
307 - }
311 + $model->carry_forward_ignored_issues( Scan_Item::TYPE_PLUGIN_CHECK, $ignored_issues, 'file' );
308 312 }
309 313 // Done, reset this, so we can use later.
310 314 $model->task_checkpoint = '';
311 315 }
312 316 $model->save();
317 +
313 318 /**
314 319 * Reduce false positive reports. Check it only if enabled 'Suspicious code' option.
315 320 *
316 321 * @since 2.4.10
@@ -315,17 +320,35 @@
315 320 *
316 321 * @since 2.4.10
317 322 */
318 323 if ( ( new Scan_Settings() )->scan_malware ) {
319 - if ( ! empty( $slugs_of_edited_plugins ) ) {
320 - update_site_option( self::PLUGIN_SLUGS, array_unique( $slugs_of_edited_plugins ) );
321 - }
322 - if ( ! empty( $this->premium_slugs ) ) {
324 + $this->persist_changed_plugin_slugs( $slugs_of_edited_plugins, ! $plugin_files->valid() );
325 + if ( array() !== $this->premium_slugs ) {
323 326 update_site_option( self::PLUGIN_PREMIUM_SLUGS, $this->premium_slugs );
324 327 }
325 328 }
326 329
327 330 return ! $plugin_files->valid();
331 + }
332 +
333 + /**
334 + * Persist changed plugin slugs across checkpoint scan batches.
335 + *
336 + * @param array $batch_slugs Slugs found in the current batch.
337 + * @param bool $is_complete Whether the plugin integrity task has completed.
338 + *
339 + * @return void
340 + */
341 + private function persist_changed_plugin_slugs( array $batch_slugs, bool $is_complete ): void {
342 + $checkpoint_slugs = (array) get_site_option( self::PLUGIN_SLUGS_CHECKPOINT, array() );
343 + $changed_slugs = array_values( array_unique( array_merge( $checkpoint_slugs, $batch_slugs ) ) );
344 +
345 + if ( $is_complete ) {
346 + update_site_option( self::PLUGIN_SLUGS, $changed_slugs );
347 + delete_site_option( self::PLUGIN_SLUGS_CHECKPOINT );
348 + } else {
349 + update_site_option( self::PLUGIN_SLUGS_CHECKPOINT, $changed_slugs );
350 + }
328 351 }
329 352
330 353 /**
331 354 * Checks if given file is a valid image.