PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.6.0
Defender Security – Malware Scanner, Login Security & Firewall v5.6.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
defender-security / src / behavior / scan-item / class-malware-result.php

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

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