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

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

433 lines 12.4 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 ( isset( $bug['fixed_in'] ) && '' !== $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 $issue_name = '<b>' . $this->get_issue_name( $this->owner->raw_data ) . '</b>';
100 $res = $scan->ignore_issue( $this->owner->id );
101 if ( ! $res ) {
102 return array(
103 'type_notice' => 'error',
104 'message' => $this->get_failed_ignore_result( $issue_name ),
105 );
106 }
107
108 return array(
109 'message' => sprintf(
110 /* translators: %s: Scan issue name. */
111 esc_html__( 'You’ve successfully ignored the security issue related to %s.', 'defender-security' ),
112 $issue_name
113 ),
114 );
115 }
116
117 /**
118 * Allow the suspicious file for the current scan.
119 *
120 * @return array An array with a message indicating successful restoration.
121 */
122 public function unignore(): array {
123 $scan = Scan::get_last();
124 $issue_name = '<b>' . $this->get_issue_name( $this->owner->raw_data ) . '</b>';
125 $res = $scan->unignore_issue( $this->owner->id );
126 if ( ! $res ) {
127 return array(
128 'type_notice' => 'error',
129 'message' => $this->get_failed_restore_result( $issue_name ),
130 );
131 }
132
133 return array(
134 'message' => sprintf(
135 /* translators: %s: Scan issue name. */
136 esc_html__( 'You’ve successfully restored the security issue related to %s.', 'defender-security' ),
137 $issue_name
138 ),
139 );
140 }
141
142 /**
143 * Resolves the vulnerability issue based on the type of vulnerability.
144 *
145 * @return array|bool|WP_Error The resolved issue or an error if the issue type is not found.
146 */
147 public function resolve() {
148 $data = $this->owner->raw_data;
149 // Redirect for WordPress-type.
150 if ( 'wp_core' === $data['type'] ) {
151 return array( 'url' => network_admin_url( 'update-core.php' ) );
152 } elseif ( 'plugin' === $data['type'] ) {
153 return $this->upgrade_plugin( $data['slug'] );
154 } elseif ( 'theme' === $data['type'] ) {
155 return $this->upgrade_theme( $data['base_slug'] );
156 }
157
158 // If type does not match.
159 return new WP_Error(
160 Error_Code::INVALID,
161 esc_html__( 'Please try again! We could not find the issue type.', 'defender-security' )
162 );
163 }
164
165 /**
166 * Upgrades the theme based on the provided slug.
167 *
168 * @param mixed $slug The slug of the theme to upgrade.
169 *
170 * @return array|WP_Error An array with a message if the upgrade is successful or a WP_Error on failure.
171 */
172 private function upgrade_theme( $slug ) {
173 $skin = new Silent_Skin();
174 $upgrader = new Theme_Upgrader( $skin );
175 $ret = $upgrader->upgrade( $slug );
176
177 if ( true === $ret ) {
178 $model = Scan::get_last();
179 $model->remove_issue( $this->owner->id );
180
181 do_action( 'wpdef_fixed_scan_issue', 'vulnerability', 'resolve' );
182
183 return array( 'message' => esc_html__( 'This item has been resolved.', 'defender-security' ) );
184 }
185
186 // This is WP error.
187 if ( is_wp_error( $ret ) ) {
188 return $ret;
189 }
190
191 // Sometimes it returns false because of it could not complete the update process.
192 return new WP_Error(
193 Error_Code::INVALID,
194 esc_html__( "We couldn't update your theme. Please try updating with another method.", 'defender-security' )
195 );
196 }
197
198 /**
199 * Upgrade a plugin based on the provided slug.
200 *
201 * @param mixed $slug The slug of the plugin to upgrade.
202 *
203 * @return array An array with information about the upgrade status.
204 * @since 2.8.1
205 */
206 private function upgrade_plugin( $slug ): array {
207 $skin = new Plugin_Skin();
208 $upgrader = new Plugin_Upgrader( $skin );
209 $result = $upgrader->bulk_upgrade( array( $slug ) );
210
211 if ( is_wp_error( $skin->result ) ) {
212 return array(
213 'type_notice' => 'error',
214 'message' => $skin->result->get_error_message(),
215 );
216 } elseif ( $skin->get_errors()->has_errors() ) {
217 return array(
218 'type_notice' => 'error',
219 'message' => $skin->get_error_messages(),
220 );
221 } elseif ( is_array( $result ) && isset( $result[ $slug ] ) && array() !== $result[ $slug ] ) {
222 $model = Scan::get_last();
223 $model->remove_issue( $this->owner->id );
224
225 do_action( 'wpdef_fixed_scan_issue', 'vulnerability', 'resolve' );
226
227 return array( 'message' => esc_html__( 'This item has been resolved.', 'defender-security' ) );
228 } elseif ( false === $result ) {
229 return array(
230 'type_notice' => 'error',
231 'message' => esc_html__(
232 'Unable to connect to the filesystem. Please confirm your credentials.',
233 'defender-security'
234 ),
235 );
236 }
237
238 return array(
239 'type_notice' => 'info',
240 'message' => esc_html__( 'There is no update available for this plugin.', 'defender-security' ),
241 );
242 }
243
244 /**
245 * Removes a vulnerability at the specified path.
246 *
247 * @param string $path The path of the vulnerability to remove.
248 *
249 * @return bool Returns true on successful removal, false otherwise.
250 */
251 private function remove_vulnerability( string $path ): bool {
252 if ( is_dir( $path ) ) {
253 return $this->delete_dir( $path );
254 } else {
255 // Sometimes a plugin consists of one file.
256 wp_delete_file( $path );
257 return true;
258 }
259 }
260
261 /**
262 * Removes a plugin based on the provided data, checking if it is active and has the necessary permissions.
263 *
264 * @param array $data An array containing information about the plugin to be removed.
265 *
266 * @return array An array with the status of the removal process and any related messages.
267 */
268 private function remove_plugin( array $data ): array {
269 $active = $this->is_active_plugin( $data['slug'] );
270 if ( $active ) {
271 return array(
272 'type_notice' => 'error',
273 'message' => esc_html__( 'This plugin cannot be removed because it is active.', 'defender-security' ),
274 );
275 }
276
277 $abs_path = $this->get_abs_plugin_path_by_slug( $data['base_slug'] );
278 if ( file_exists( $abs_path ) && ! $this->remove_vulnerability( $abs_path ) ) {
279 return array(
280 'type_notice' => 'error',
281 'message' => esc_html__(
282 'Defender does not have enough permission to remove this plugin.',
283 'defender-security'
284 ),
285 );
286 }
287
288 $message = sprintf(
289 /* translators: %s: Plugin name. */
290 esc_html__( '%s plugin', 'defender-security' ),
291 '<b>' . $data['name'] . '</b>'
292 );
293 $this->log( $message . 'is deleted', Scan_Controller::SCAN_LOG );
294 $model = Scan::get_last();
295 $model->remove_issue( $this->owner->id );
296
297 do_action( 'wpdef_fixed_scan_issue', 'vulnerability', 'delete' );
298
299 return array(
300 'collect_type' => true,
301 'message' => $message,
302 );
303 }
304
305 /**
306 * Removes a theme from the system.
307 *
308 * @param array $data The data of the theme to be removed.
309 *
310 * @return array The result of the removal operation.
311 */
312 private function remove_theme( array $data ): array {
313 $active = $this->is_active_theme( $data['base_slug'] );
314 if ( $active ) {
315 return array(
316 'type_notice' => 'error',
317 'message' => esc_html__( 'This theme cannot be removed because it is active.', 'defender-security' ),
318 );
319 }
320
321 $abs_path = $this->get_path_of_themes_dir() . $data['base_slug'];
322 if ( file_exists( $abs_path ) && ! $this->remove_vulnerability( $abs_path ) ) {
323 return array(
324 'type_notice' => 'error',
325 'message' => esc_html__(
326 'Defender does not have enough permission to remove this theme.',
327 'defender-security'
328 ),
329 );
330 }
331
332 $message = sprintf(
333 /* translators: %s: Plugin theme. */
334 esc_html__( '%s theme', 'defender-security' ),
335 '<b>' . $data['name'] . '</b>'
336 );
337 $this->log( $message . 'is deleted', Scan_Controller::SCAN_LOG );
338 $model = Scan::get_last();
339 $model->remove_issue( $this->owner->id );
340
341 do_action( 'wpdef_fixed_scan_issue', 'vulnerability', 'delete' );
342
343 return array(
344 'collect_type' => true,
345 'message' => $message,
346 );
347 }
348
349 /**
350 * Retrieves the vulnerability body text based on the provided bug data.
351 *
352 * @param array $bug An array containing information about the vulnerability.
353 *
354 * @return string The vulnerability body text.
355 */
356 protected function get_vulnerability_body( array $bug ): string {
357 $text = '#' . $bug['title'] . PHP_EOL;
358 $text .= '-' . esc_html__( 'Vulnerability type:', 'defender-security' ) . ' ' . $bug['vuln_type'] . PHP_EOL;
359 if ( ! isset( $bug['fixed_in'] ) || '' === $bug['fixed_in'] ) {
360 $text .= '-' . esc_html__( 'No Update Available', 'defender-security' ) . PHP_EOL;
361 } else {
362 $text .= '-' . esc_html__(
363 'This bug has been fixed in version:',
364 'defender-security'
365 ) . ' ' . $bug['fixed_in'] . PHP_EOL;
366 }
367
368 return $text;
369 }
370
371 /**
372 * Returns the vulnerability details as a string.
373 *
374 * @param array $data The data containing the vulnerability details.
375 *
376 * @return string The vulnerability details as a string.
377 */
378 public function get_detail_as_string( array $data ): string {
379 $strings = array();
380 foreach ( $data['bugs'] as $bug ) {
381 $strings[] = $this->get_vulnerability_body( $bug );
382 }
383
384 return implode( PHP_EOL, $strings );
385 }
386
387 /**
388 * Returns the details of vulnerabilities as an array.
389 *
390 * @param array $data The data containing information about vulnerabilities.
391 *
392 * @return array An array with vulnerability details including score and detailed description.
393 */
394 public function get_details_as_array( array $data ): array {
395 $arr = array();
396 foreach ( $data['bugs'] as $bug ) {
397 $text = $this->get_vulnerability_body( $bug );
398 $arr[] = array(
399 'score' => $bug['cvss_score'],
400 'detail' => str_replace( PHP_EOL, '<br/>', $text ),
401 );
402 }
403
404 return $arr;
405 }
406
407 /**
408 * Delete inactive plugin or theme.
409 *
410 * @return array|WP_Error
411 */
412 public function delete() {
413 $data = $this->owner->raw_data;
414 // WP core, plugin or theme.
415 if ( 'wp_core' === $data['type'] ) {
416 return array(
417 'type_notice' => 'error',
418 'message' => esc_html__( 'WordPress core cannot be removed.', 'defender-security' ),
419 );
420 } elseif ( 'plugin' === $data['type'] ) {
421 return $this->remove_plugin( $data );
422 } elseif ( 'theme' === $data['type'] ) {
423 return $this->remove_theme( $data );
424 }
425
426 // Sometimes it returns false because of it could not complete the remove process.
427 return new WP_Error(
428 Error_Code::INVALID,
429 esc_html__( "We couldn't remove this item.", 'defender-security' )
430 );
431 }
432 }
433