PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.2.4
Defender Security – Malware Scanner, Login Security & Firewall v6.2.4
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 6.2.4, at src/behavior/scan-item/class-malware-result.php

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