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-malware-result.php

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

296 lines 8.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 Malware Result behavior.
4 *
5 * @package WP_Defender\Behavior\Scan_Item
6 */
7
8 namespace WP_Defender\Behavior\Scan_Item;
9
10 use WP_Error;
11 use WP_Defender\Traits\IO;
12 use WP_Defender\Model\Scan;
13 use WP_Defender\Traits\Theme;
14 use WP_Defender\Traits\Plugin;
15 use Calotes\Component\Behavior;
16 use WP_Defender\Traits\Formats;
17 use WP_Defender\Model\Scan_Item;
18 use WP_Defender\Component\Error_Code;
19 use WP_Defender\Traits\File_Operations;
20 use WP_Defender\Helper\File as File_Helper;
21 use WP_Defender\Component\Quarantine as Quarantine_Component;
22 use WP_Filesystem_Base;
23
24 /**
25 * Represents a Malware Result behavior.
26 */
27 class Malware_Result extends Behavior {
28
29 use Formats;
30 use IO;
31 use Plugin;
32 use Theme;
33 use File_Operations;
34
35 private const REMOTE_FILE_IDENTICAL = 'REMOTE_FILE_IDENTICAL';
36 private const REMOTE_FILE_DIFFER = 'REMOTE_FILE_DIFFER';
37 private const REMOTE_FILE_NOT_EXISTS = 'REMOTE_FILE_NOT_EXISTS';
38 private const REPAIR_NOT_IMPLEMENTED = 'REPAIR_NOT_IMPLEMENTED';
39
40 /**
41 * Generates an array representation of the scan item.
42 *
43 * @return array
44 */
45 public function to_array() {
46 $file = $this->owner->raw_data['file'];
47 list( $file_size, $file_created_at, $delete ) = $this->get_file_meta( $file );
48 $is_quarantinable = $delete ? false : $this->is_quarantinable( $file );
49
50 $quarantine_data = class_exists( 'WP_Defender\Component\Quarantine' ) ?
51 wd_di()->get( Quarantine_Component::class )->scan_item_data( $this->owner ) :
52 array( 'is_quarantinable' => $is_quarantinable );
53
54 return array_merge(
55 array(
56 'id' => $this->owner->id,
57 'type' => Scan_Item::TYPE_SUSPICIOUS,
58 'file_name' => pathinfo( $file, PATHINFO_BASENAME ),
59 'full_path' => $file,
60 'date_added' => $file_created_at,
61 'size' => $file_size,
62 'deleted' => $delete,
63 'short_desc' => esc_html__( 'Suspicious function found', 'defender-security' ),
64 ),
65 $quarantine_data
66 );
67 }
68
69 /**
70 * Ignores the scan item and marks it as successfully ignored.
71 *
72 * @return array An array with a message indicating the successful ignore action.
73 */
74 public function ignore(): array {
75 $scan = Scan::get_last();
76 $data = $this->owner->raw_data;
77 $issue_name = '<b>' . pathinfo( $data['file'], PATHINFO_BASENAME ) . '</b>';
78 $res = $scan->ignore_issue( $this->owner->id );
79 if ( ! $res ) {
80 return array(
81 'type_notice' => 'error',
82 'message' => sprintf(
83 /* translators: %s: Scan issue name. */
84 esc_html__( '%s is already marked as Ignored. It can’t be added to the Ignored list again.', 'defender-security' ),
85 $issue_name
86 ),
87 );
88 }
89
90 return array(
91 'message' => sprintf(
92 /* translators: %s: Scan issue name. */
93 esc_html__( 'You’ve successfully ignored the security issue related to %s.', 'defender-security' ),
94 $issue_name
95 ),
96 );
97 }
98
99 /**
100 * Restores the ignored suspicious file.
101 *
102 * @return array An array with a message indicating the successful restoration action.
103 */
104 public function unignore(): array {
105 $scan = Scan::get_last();
106 $scan->unignore_issue( $this->owner->id );
107
108 return array( 'message' => esc_html__( 'The suspicious file has been successfully restored.', 'defender-security' ) );
109 }
110
111 /**
112 * Delete the file, or whole folder.
113 *
114 * @return array|WP_Error
115 */
116 public function delete() {
117 $data = $this->owner->raw_data;
118 $scan = Scan::get_last();
119 $path = $data['file'];
120 if ( ! file_exists( $path ) || ! is_readable( $path ) ) {
121 $scan->remove_issue( $this->owner->id );
122 $this->log(
123 sprintf( '%s is not readable and will be removed from the scan result', $path ),
124 \WP_Defender\Controller\Scan::SCAN_LOG
125 );
126
127 return array( 'message' => esc_html__( 'This item has been deleted.', 'defender-security' ) );
128 }
129 /**
130 * If the path inside theme or plugin folder, then we remove the whole plugin/theme for prevent fatal error.
131 * If the path is to wp-config, we can't delete it.
132 */
133 if ( defender_wp_config_path() === $path ) {
134 return new WP_Error(
135 Error_Code::INVALID,
136 esc_html__( 'wp-config.php can\'t be removed. Please remove the suspicious code manually.', 'defender-security' )
137 );
138 }
139 // If it's not relative path, which mean it is something lay on wp-content folder, we can delete it.
140 if ( ! $this->delete_infected_file( $path ) ) {
141 return $this->get_permission_error( $path );
142 }
143
144 return $this->after_delete( $path, $scan, 'malware' );
145 }
146
147 /**
148 * Return the source code depending on the type of the issue.
149 *
150 * @return void|array
151 */
152 public function pull_src() {
153 global $wp_filesystem;
154 // Initialize the WP filesystem, no more using 'file-put-contents' function.
155 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
156 require_once ABSPATH . '/wp-admin/includes/file.php';
157 WP_Filesystem();
158 }
159 $data = $this->owner->raw_data;
160
161 $file_path = wp_normalize_path( $data['file'] );
162
163 $active = false;
164 $type_src = '';
165 if ( ! file_exists( $file_path ) ) {
166 return array(
167 'origin' => '',
168 'code' => '',
169 );
170 } elseif ( false !== stripos( $file_path, wp_normalize_path( WP_PLUGIN_DIR ) ) ) {
171 $active = $this->is_active_plugin( $file_path );
172 $type_src = 'plugin';
173 } elseif ( false !== stripos( $file_path, wp_normalize_path( $this->get_path_of_themes_dir() ) ) ) {
174 $active = $this->is_active_theme( $file_path );
175 $type_src = 'theme';
176 }
177
178 $markers = array();
179 foreach ( $data as $item ) {
180 if ( isset( $item['catches'] ) ) {
181 foreach ( $item['catches'] as $catch ) {
182 $markers = array_merge( $markers, $catch['mapper'] );
183 }
184 }
185 }
186
187 $success_data = array(
188 'origin' => $wp_filesystem->get_contents( $file_path ),
189 'mapper' => $markers,
190 'active' => $active,
191 'type_src' => $type_src,
192 );
193
194 if ( 'plugin' === $type_src ) {
195 $directory_name = $this->get_plugin_directory_name( $file_path );
196 $plugin_headers = $this->get_plugin_headers( $file_path );
197 if ( empty( $plugin_headers ) ) {
198 return $success_data;
199 }
200
201 $plugin_headers = reset( $plugin_headers );
202
203 $success_data['local_file_state'] = $this->get_local_file_state(
204 $file_path,
205 $directory_name,
206 $plugin_headers
207 );
208
209 $success_data['directory_name'] = $directory_name;
210 $success_data['author'] = $plugin_headers['Author'] ?? '';
211 $success_data['author_uri'] = $plugin_headers['AuthorURI'] ?? '';
212 }
213
214 wp_send_json_success( $success_data );
215 }
216
217 /**
218 * Get local file state which used to handle repair or delete in front end.
219 *
220 * @param string $file_path File full path.
221 * @param string $directory_name Plugin directory name.
222 * @param array $plugin_headers Plugin headers.
223 *
224 * @return string Get local file state.
225 */
226 private function get_local_file_state( $file_path, $directory_name, $plugin_headers ): string {
227 $file_relative_path = $this->get_plugin_relative_path( $file_path );
228
229 $source_file_url = $this->get_file_url(
230 $directory_name,
231 $plugin_headers['Version'],
232 $file_relative_path
233 );
234
235 /**
236 * Retrieves an instance of the File_Helper class.
237 *
238 * @var File_Helper $file_helper
239 */
240 $file_helper = wd_di()->get( File_Helper::class );
241
242 $is_identical = $file_helper->is_identical_content(
243 $file_path,
244 $source_file_url
245 );
246
247 if (
248 is_wp_error( $is_identical ) &&
249 'http_404' === $is_identical->get_error_code()
250 ) {
251 // If file is not exists in remote then it may not be repaired.
252 $local_file_state = self::REMOTE_FILE_NOT_EXISTS;
253 } elseif ( false === $is_identical ) {
254 $local_file_state = self::REMOTE_FILE_DIFFER;
255 } else {
256 $local_file_state = self::REMOTE_FILE_IDENTICAL;
257 }
258
259 return $local_file_state;
260 }
261
262 /**
263 * A wrapper method for WP_Defender\Behavior\Scan_Item\Plugin_Integrity::resolve().
264 */
265 public function resolve() {
266 $file_path = $this->owner->raw_data['file'];
267
268 $directory_name = $this->get_plugin_directory_name( $file_path );
269 $plugin_headers = $this->get_plugin_headers( $file_path );
270 $plugin_headers = reset( $plugin_headers );
271
272 $local_file_state = $this->get_local_file_state(
273 $file_path,
274 $directory_name,
275 $plugin_headers
276 );
277
278 if ( 'REMOTE_FILE_DIFFER' !== $local_file_state ) {
279 return;
280 }
281
282 /**
283 * Retrieves an instance of the Plugin_Integrity class.
284 *
285 * @var Plugin_Integrity $plugin_integrity
286 */
287 $plugin_integrity = wd_di()->get( Plugin_Integrity::class );
288
289 $this->owner->raw_data['type'] = 'modified';
290
291 $plugin_integrity->owner = $this->owner;
292
293 return $plugin_integrity->resolve();
294 }
295 }
296