PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.7.2
Defender Security – Malware Scanner, Login Security & Firewall v5.7.2
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 / behavior / scan-item / class-vuln-result.php

class-vuln-result.php in Defender Security – Malware Scanner, Login Security & Firewall 5.7.2, at src/behavior/scan-item/class-vuln-result.php

427 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Represents a vulnerability result.
4 *
5 * @package WP_Defender\Behavior\Scan_Item
6 */
7
8 namespace WP_Defender\Behavior\Scan_Item;
9
10 use WP_Error;
11 use Theme_Upgrader;
12 use Plugin_Upgrader;
13 use WP_Defender\Traits\IO;
14 use WP_Defender\Model\Scan;
15 use WP_Defender\Traits\Theme;
16 use WP_Defender\Traits\Plugin;
17 use Calotes\Component\Behavior;
18 use WP_Defender\Traits\Formats;
19 use WP_Defender\Model\Scan_Item;
20 use WP_Defender\Component\Error_Code;
21 use WP_Defender\Component\Security_Tweaks\WP_Version;
22 use WP_Defender\Controller\Scan as Scan_Controller;
23
24 /**
25 * Represents a vulnerability result.
26 */
27 class Vuln_Result extends Behavior {
28
29 use Formats;
30 use IO;
31 use Plugin;
32 use Theme;
33
34 /**
35 * Checks if upgrade is possible based on the presence of fixed version in vulnerabilities.
36 *
37 * @param array $bugs The array of vulnerabilities to check.
38 *
39 * @return string The upgrade status ('disabled' or 'enabled').
40 */
41 protected function upgrade_possible( array $bugs ): string {
42 $upgrade = 'disabled';
43 foreach ( $bugs as $bug ) {
44 if ( ! empty( $bug['fixed_in'] ) ) {
45 $upgrade = 'enabled';
46 break;
47 }
48 }
49
50 return $upgrade;
51 }
52
53 /**
54 * Converts the scan item information into an array for output.
55 *
56 * @return array
57 */
58 public function to_array(): array {
59 $data = $this->owner->raw_data;
60 if ( isset( $data['name'], $data['version'], $data['bugs'] ) ) {
61 if ( 'wp_core' === $data['type'] ) {
62 // Check if the current WP version is the latest.
63 $upgrade = ( new WP_Version() )->check() ? 'disabled' : 'enabled';
64 } elseif ( in_array( $data['type'], array( 'plugin', 'theme' ), true ) ) {
65 $upgrade = $this->upgrade_possible( $data['bugs'] );
66 } else {
67 $upgrade = 'disabled';
68 }
69
70 return array(
71 'id' => $this->owner->id,
72 'type' => Scan_Item::TYPE_VULNERABILITY,
73 'file_name' => $data['name'],
74 'short_desc' => sprintf(
75 /* translators: %s: Version of WP core, plugin or theme. */
76 esc_html__( 'Vulnerability found in %s.', 'defender-security' ),
77 $data['version']
78 ),
79 'details' => isset( $data['new_structure'] )
80 ? $this->get_details_as_array( $data )
81 : $this->get_detail_as_string( $data ),
82 // Need for all scan items for WP-CLI command. Full path = base slug for this item.
83 'full_path' => $data['slug'],
84 'new_structure' => isset( $data['new_structure'] ) ? 'yes' : 'no',
85 'upgrade' => $upgrade,
86 );
87 }
88
89 return array();
90 }
91
92 /**
93 * Ignore the suspicious file for the current scan.
94 *
95 * @return array An array with a message indicating successful ignore.
96 */
97 public function ignore(): array {
98 $scan = Scan::get_last();
99 $data = $this->owner->raw_data;
100 $issue_name = '<b>' . $data['name'] . '</b>';
101 $res = $scan->ignore_issue( $this->owner->id );
102 if ( ! $res ) {
103 return array(
104 'type_notice' => 'error',
105 'message' => sprintf(
106 /* translators: %s: Scan issue name. */
107 esc_html__( '%s is already marked as Ignored. It can’t be added to the Ignored list again.', 'defender-security' ),
108 $issue_name
109 ),
110 );
111 }
112
113 return array(
114 'message' => sprintf(
115 /* translators: %s: Scan issue name. */
116 esc_html__( 'You’ve successfully ignored the security issue related to %s.', 'defender-security' ),
117 $issue_name
118 ),
119 );
120 }
121
122 /**
123 * Allow the suspicious file for the current scan.
124 *
125 * @return array An array with a message indicating successful restoration.
126 */
127 public function unignore(): array {
128 $scan = Scan::get_last();
129 $scan->unignore_issue( $this->owner->id );
130
131 return array(
132 'message' => esc_html__( 'The suspicious file has been successfully restored.', 'defender-security' ),
133 );
134 }
135
136 /**
137 * Resolves the vulnerability issue based on the type of vulnerability.
138 *
139 * @return array|bool|WP_Error The resolved issue or an error if the issue type is not found.
140 */
141 public function resolve() {
142 $data = $this->owner->raw_data;
143 // Redirect for WordPress-type.
144 if ( 'wp_core' === $data['type'] ) {
145 return array( 'url' => network_admin_url( 'update-core.php' ) );
146 } elseif ( 'plugin' === $data['type'] ) {
147 return $this->upgrade_plugin( $data['slug'] );
148 } elseif ( 'theme' === $data['type'] ) {
149 return $this->upgrade_theme( $data['base_slug'] );
150 }
151
152 // If type does not match.
153 return new WP_Error(
154 Error_Code::INVALID,
155 esc_html__( 'Please try again! We could not find the issue type.', 'defender-security' )
156 );
157 }
158
159 /**
160 * Upgrades the theme based on the provided slug.
161 *
162 * @param mixed $slug The slug of the theme to upgrade.
163 *
164 * @return array|WP_Error An array with a message if the upgrade is successful or a WP_Error on failure.
165 */
166 private function upgrade_theme( $slug ) {
167 $skin = new Silent_Skin();
168 $upgrader = new Theme_Upgrader( $skin );
169 $ret = $upgrader->upgrade( $slug );
170
171 if ( true === $ret ) {
172 $model = Scan::get_last();
173 $model->remove_issue( $this->owner->id );
174
175 do_action( 'wpdef_fixed_scan_issue', 'vulnerability', 'resolve' );
176
177 return array( 'message' => esc_html__( 'This item has been resolved.', 'defender-security' ) );
178 }
179
180 // This is WP error.
181 if ( is_wp_error( $ret ) ) {
182 return $ret;
183 }
184
185 // Sometimes it returns false because of it could not complete the update process.
186 return new WP_Error(
187 Error_Code::INVALID,
188 esc_html__( "We couldn't update your theme. Please try updating with another method.", 'defender-security' )
189 );
190 }
191
192 /**
193 * Upgrade a plugin based on the provided slug.
194 *
195 * @param mixed $slug The slug of the plugin to upgrade.
196 *
197 * @return array An array with information about the upgrade status.
198 * @since 2.8.1
199 */
200 private function upgrade_plugin( $slug ): array {
201 $skin = new Plugin_Skin();
202 $upgrader = new Plugin_Upgrader( $skin );
203 $result = $upgrader->bulk_upgrade( array( $slug ) );
204
205 if ( is_wp_error( $skin->result ) ) {
206 return array(
207 'type_notice' => 'error',
208 'message' => $skin->result->get_error_message(),
209 );
210 } elseif ( $skin->get_errors()->has_errors() ) {
211 return array(
212 'type_notice' => 'error',
213 'message' => $skin->get_error_messages(),
214 );
215 } elseif ( is_array( $result ) && ! empty( $result[ $slug ] ) ) {
216 $model = Scan::get_last();
217 $model->remove_issue( $this->owner->id );
218
219 do_action( 'wpdef_fixed_scan_issue', 'vulnerability', 'resolve' );
220
221 return array( 'message' => esc_html__( 'This item has been resolved.', 'defender-security' ) );
222 } elseif ( false === $result ) {
223 return array(
224 'type_notice' => 'error',
225 'message' => esc_html__(
226 'Unable to connect to the filesystem. Please confirm your credentials.',
227 'defender-security'
228 ),
229 );
230 }
231
232 return array(
233 'type_notice' => 'info',
234 'message' => esc_html__( 'There is no update available for this plugin.', 'defender-security' ),
235 );
236 }
237
238 /**
239 * Removes a vulnerability at the specified path.
240 *
241 * @param string $path The path of the vulnerability to remove.
242 *
243 * @return bool Returns true on successful removal, false otherwise.
244 */
245 private function remove_vulnerability( string $path ): bool {
246 if ( is_dir( $path ) ) {
247 return $this->delete_dir( $path );
248 } else {
249 // Sometimes a plugin consists of one file.
250 wp_delete_file( $path );
251 return true;
252 }
253 }
254
255 /**
256 * Removes a plugin based on the provided data, checking if it is active and has the necessary permissions.
257 *
258 * @param array $data An array containing information about the plugin to be removed.
259 *
260 * @return array An array with the status of the removal process and any related messages.
261 */
262 private function remove_plugin( array $data ): array {
263 $active = $this->is_active_plugin( $data['slug'] );
264 if ( $active ) {
265 return array(
266 'type_notice' => 'error',
267 'message' => esc_html__( 'This plugin cannot be removed because it is active.', 'defender-security' ),
268 );
269 }
270
271 $abs_path = $this->get_abs_plugin_path_by_slug( $data['base_slug'] );
272 if ( file_exists( $abs_path ) && ! $this->remove_vulnerability( $abs_path ) ) {
273 return array(
274 'type_notice' => 'error',
275 'message' => esc_html__(
276 'Defender does not have enough permission to remove this plugin.',
277 'defender-security'
278 ),
279 );
280 }
281
282 $message = sprintf(
283 /* translators: %s: Plugin name. */
284 esc_html__( '%s plugin', 'defender-security' ),
285 '<b>' . $data['name'] . '</b>'
286 );
287 $this->log( $message . 'is deleted', Scan_Controller::SCAN_LOG );
288 $model = Scan::get_last();
289 $model->remove_issue( $this->owner->id );
290
291 do_action( 'wpdef_fixed_scan_issue', 'vulnerability', 'delete' );
292
293 return array(
294 'collect_type' => true,
295 'message' => $message,
296 );
297 }
298
299 /**
300 * Removes a theme from the system.
301 *
302 * @param array $data The data of the theme to be removed.
303 *
304 * @return array The result of the removal operation.
305 */
306 private function remove_theme( array $data ): array {
307 $active = $this->is_active_theme( $data['base_slug'] );
308 if ( $active ) {
309 return array(
310 'type_notice' => 'error',
311 'message' => esc_html__( 'This theme cannot be removed because it is active.', 'defender-security' ),
312 );
313 }
314
315 $abs_path = $this->get_path_of_themes_dir() . $data['base_slug'];
316 if ( file_exists( $abs_path ) && ! $this->remove_vulnerability( $abs_path ) ) {
317 return array(
318 'type_notice' => 'error',
319 'message' => esc_html__(
320 'Defender does not have enough permission to remove this theme.',
321 'defender-security'
322 ),
323 );
324 }
325
326 $message = sprintf(
327 /* translators: %s: Plugin theme. */
328 esc_html__( '%s theme', 'defender-security' ),
329 '<b>' . $data['name'] . '</b>'
330 );
331 $this->log( $message . 'is deleted', Scan_Controller::SCAN_LOG );
332 $model = Scan::get_last();
333 $model->remove_issue( $this->owner->id );
334
335 do_action( 'wpdef_fixed_scan_issue', 'vulnerability', 'delete' );
336
337 return array(
338 'collect_type' => true,
339 'message' => $message,
340 );
341 }
342
343 /**
344 * Retrieves the vulnerability body text based on the provided bug data.
345 *
346 * @param array $bug An array containing information about the vulnerability.
347 *
348 * @return string The vulnerability body text.
349 */
350 protected function get_vulnerability_body( array $bug ): string {
351 $text = '#' . $bug['title'] . PHP_EOL;
352 $text .= '-' . esc_html__( 'Vulnerability type:', 'defender-security' ) . ' ' . $bug['vuln_type'] . PHP_EOL;
353 if ( empty( $bug['fixed_in'] ) ) {
354 $text .= '-' . esc_html__( 'No Update Available', 'defender-security' ) . PHP_EOL;
355 } else {
356 $text .= '-' . esc_html__(
357 'This bug has been fixed in version:',
358 'defender-security'
359 ) . ' ' . $bug['fixed_in'] . PHP_EOL;
360 }
361
362 return $text;
363 }
364
365 /**
366 * Returns the vulnerability details as a string.
367 *
368 * @param array $data The data containing the vulnerability details.
369 *
370 * @return string The vulnerability details as a string.
371 */
372 public function get_detail_as_string( array $data ): string {
373 $strings = array();
374 foreach ( $data['bugs'] as $bug ) {
375 $strings[] = $this->get_vulnerability_body( $bug );
376 }
377
378 return implode( PHP_EOL, $strings );
379 }
380
381 /**
382 * Returns the details of vulnerabilities as an array.
383 *
384 * @param array $data The data containing information about vulnerabilities.
385 *
386 * @return array An array with vulnerability details including score and detailed description.
387 */
388 public function get_details_as_array( array $data ): array {
389 $arr = array();
390 foreach ( $data['bugs'] as $bug ) {
391 $text = $this->get_vulnerability_body( $bug );
392 $arr[] = array(
393 'score' => $bug['cvss_score'],
394 'detail' => str_replace( PHP_EOL, '<br/>', $text ),
395 );
396 }
397
398 return $arr;
399 }
400
401 /**
402 * Delete inactive plugin or theme.
403 *
404 * @return array|WP_Error
405 */
406 public function delete() {
407 $data = $this->owner->raw_data;
408 // WP core, plugin or theme.
409 if ( 'wp_core' === $data['type'] ) {
410 return array(
411 'type_notice' => 'error',
412 'message' => esc_html__( 'WordPress core cannot be removed.', 'defender-security' ),
413 );
414 } elseif ( 'plugin' === $data['type'] ) {
415 return $this->remove_plugin( $data );
416 } elseif ( 'theme' === $data['type'] ) {
417 return $this->remove_theme( $data );
418 }
419
420 // Sometimes it returns false because of it could not complete the remove process.
421 return new WP_Error(
422 Error_Code::INVALID,
423 esc_html__( "We couldn't remove this item.", 'defender-security' )
424 );
425 }
426 }
427