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

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