PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.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-core-integrity.php

class-core-integrity.php in Defender Security – Malware Scanner, Login Security & Firewall 6.3.0, at src/behavior/scan-item/class-core-integrity.php

308 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles core integrity scan.
4 *
5 * @package WP_Defender\Behavior\Scan_Item
6 */
7
8 namespace WP_Defender\Behavior\Scan_Item;
9
10 use WP_Error;
11 use Calotes\Base\File;
12 use WP_Defender\Traits\IO;
13 use WP_Defender\Model\Scan;
14 use Calotes\Component\Behavior;
15 use WP_Defender\Traits\Formats;
16 use WP_Defender\Model\Scan_Item;
17 use WP_Defender\Traits\File_Operations;
18 use WP_Defender\Controller\Scan as Scan_Controller;
19 use WP_Defender\Behavior\Scan\Core_Integrity as Core_Integrity_Scan;
20 use WP_Filesystem_Base;
21
22 /**
23 * This class represents a behavior related to core integrity in the WP_Defender plugin.
24 */
25 class Core_Integrity extends Behavior {
26
27 use Formats;
28 use IO;
29 use File_Operations;
30
31 /**
32 * Return general data so we can output on frontend.
33 *
34 * @return array
35 */
36 public function to_array(): array {
37 $data = $this->owner->raw_data;
38 $file = $data['file'];
39
40 list( $file_created_at, $file_size, $deleted ) = $this->get_file_meta( $file );
41
42 return array(
43 'id' => $this->owner->id,
44 'type' => Scan_Item::TYPE_INTEGRITY,
45 'file_name' => pathinfo( $file, PATHINFO_BASENAME ),
46 'full_path' => $file,
47 'date_added' => $file_created_at,
48 'size' => $file_size,
49 'scenario' => $data['type'],
50 'deleted' => $deleted,
51 'short_desc' => $this->get_short_description(),
52 );
53 }
54
55 /**
56 * We will get the origin code by looking into svn repo.
57 *
58 * @return false|string|WP_Error
59 */
60 private function get_origin_code() {
61 global $wp_filesystem;
62 // Initialize the WP filesystem, no more using 'file-put-contents' function.
63 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
64 require_once ABSPATH . '/wp-admin/includes/file.php';
65 WP_Filesystem();
66 }
67 global $wp_version;
68 $data = $this->owner->raw_data;
69 $file = wp_normalize_path( $data['file'] );
70 $relative_path = str_replace( wp_normalize_path( ABSPATH ), '', $file );
71 $source_file_url = "https://core.svn.wordpress.org/tags/$wp_version/" . $relative_path;
72 $ds = DIRECTORY_SEPARATOR;
73 if ( ! function_exists( 'download_url' ) ) {
74 require_once ABSPATH . 'wp-admin' . $ds . 'includes' . $ds . 'file.php';
75 }
76 $tmp = download_url( $source_file_url );
77 if ( is_wp_error( $tmp ) ) {
78 return $tmp;
79 }
80 $content = $wp_filesystem->get_contents( $tmp );
81 wp_delete_file( $tmp );
82 $trusted_checksum = $this->get_trusted_checksum( $relative_path );
83
84 if ( ! is_string( $content ) || ! is_string( $trusted_checksum ) ) {
85 return new WP_Error(
86 'defender_core_checksum_unavailable',
87 esc_html__( 'The trusted WordPress core checksum is unavailable.', 'defender-security' )
88 );
89 }
90
91 if ( ! hash_equals( strtolower( $trusted_checksum ), md5( $content ) ) ) {
92 return new WP_Error(
93 'defender_core_checksum_mismatch',
94 esc_html__( 'The downloaded WordPress core file failed checksum verification.', 'defender-security' )
95 );
96 }
97
98 return $content;
99 }
100
101 /**
102 * Get the trusted WordPress checksum for a core file.
103 *
104 * New scan items persist the checksum used to identify the modification.
105 * The temporary scan cache and the WordPress checksum API are fallbacks for
106 * scan items created before that checksum was stored with the issue.
107 *
108 * @param string $relative_path Core file path relative to ABSPATH.
109 * @return string|null
110 */
111 private function get_trusted_checksum( $relative_path ) {
112 $data = $this->owner->raw_data;
113 if ( isset( $data['checksum'] ) && is_string( $data['checksum'] ) ) {
114 return $data['checksum'];
115 }
116
117 $checksums = get_site_option( Core_Integrity_Scan::CACHE_CHECKSUMS, array() );
118 if ( is_array( $checksums ) && isset( $checksums[ $relative_path ] ) && is_string( $checksums[ $relative_path ] ) ) {
119 return $checksums[ $relative_path ];
120 }
121
122 if ( ! function_exists( 'get_core_checksums' ) ) {
123 require_once ABSPATH . 'wp-admin/includes/update.php';
124 }
125
126 global $wp_version, $wp_local_package;
127 $locale = isset( $wp_local_package ) && '' !== $wp_local_package ? $wp_local_package : 'en_US';
128 $checksums = get_core_checksums( $wp_version, $locale );
129 if ( isset( $checksums[ $wp_version ] ) && is_array( $checksums[ $wp_version ] ) ) {
130 $checksums = $checksums[ $wp_version ];
131 }
132
133 return is_array( $checksums ) && isset( $checksums[ $relative_path ] ) && is_string( $checksums[ $relative_path ] )
134 ? $checksums[ $relative_path ]
135 : null;
136 }
137
138 /**
139 * Restore the file with its origin content.
140 *
141 * @return void|array|WP_Error
142 */
143 public function resolve() {
144 global $wp_filesystem;
145 // Initialize the WP filesystem, no more using 'file-put-contents' function.
146 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
147 require_once ABSPATH . '/wp-admin/includes/file.php';
148 WP_Filesystem();
149 }
150
151 $data = $this->owner->raw_data;
152 if ( 'modified' !== $data['type'] ) {
153 // Should not be here unless case changed.
154 return;
155 }
156
157 $origin = $this->get_origin_code();
158 if ( false === $origin || is_wp_error( $origin ) ) {
159 return;
160 }
161
162 $path = $data['file'];
163 $ret = $wp_filesystem->put_contents( $path, $origin );
164 if ( $ret ) {
165 $scan = Scan::get_last();
166 $scan->remove_issue( $this->owner->id );
167 $this->log( sprintf( '%s is deleted', $path ), Scan_Controller::SCAN_LOG );
168
169 do_action( 'wpdef_fixed_scan_issue', 'core_integrity', 'resolve' );
170
171 return array( 'message' => esc_html__( 'This item has been resolved.', 'defender-security' ) );
172 } else {
173 return new WP_Error(
174 'defender_permissions_denied',
175 esc_html__(
176 'Permissions Denied. Defender does not have the needed permissions to edit the file. Please change file permissions to 640 or contact your hosting provider so they could change them for you.',
177 'defender-security'
178 )
179 );
180 }
181 }
182
183 /**
184 * Ignore the suspicious file.
185 *
186 * @return array
187 */
188 public function ignore(): array {
189 $scan = Scan::get_last();
190 $data = $this->owner->raw_data;
191 $issue_name = '<b>' . pathinfo( $data['file'], PATHINFO_BASENAME ) . '</b>';
192 $res = $scan->ignore_issue( $this->owner->id );
193 if ( ! $res ) {
194 return array(
195 'type_notice' => 'error',
196 'message' => $this->get_failed_ignore_result( $issue_name ),
197 );
198 }
199
200 return array(
201 'message' => sprintf(
202 /* translators: %s: Scan issue name. */
203 esc_html__( 'You’ve successfully ignored the security issue related to %s.', 'defender-security' ),
204 $issue_name
205 ),
206 );
207 }
208
209 /**
210 * Restore the ignored suspicious file.
211 *
212 * @return array
213 */
214 public function unignore(): array {
215 $scan = Scan::get_last();
216 $scan->unignore_issue( $this->owner->id );
217
218 return array( 'message' => esc_html__( 'The suspicious file has been successfully restored.', 'defender-security' ) );
219 }
220
221 /**
222 * Delete the file or whole folder.
223 *
224 * @return array|WP_Error
225 */
226 public function delete() {
227 $data = $this->owner->raw_data;
228 $scan = Scan::get_last();
229
230 // Check if the file exists and is readable. If not, remove it from the scan result and log the result.
231 $file = $data['file'];
232 if ( ! file_exists( $file ) || ! is_readable( $file ) ) {
233 $scan->remove_issue( $this->owner->id );
234 $this->log(
235 sprintf( '%s is not readable and will be removed from the scan result', $file ),
236 Scan_Controller::SCAN_LOG
237 );
238
239 return array( 'message' => esc_html__( 'This item has been deleted.', 'defender-security' ) );
240 } elseif ( 'unversion' === $data['type'] && $this->delete_infected_file( $file ) ) {
241 return $this->after_delete( $file, $scan, 'core_integrity' );
242 } elseif ( 'dir' === $data['type'] && $this->delete_dir( $file ) ) {
243 return $this->after_delete( $file, $scan, 'core_integrity' );
244 }
245
246 return $this->get_permission_error( $file );
247 }
248
249 /**
250 * Return the source code depending on the type of the issue:
251 * If it is unversion, return full source,
252 * if it is dir, we return a list of files,
253 * if it is modified, we will return the current code & origin.
254 *
255 * @return array
256 */
257 public function pull_src(): array {
258 global $wp_filesystem;
259 // Initialize the WP filesystem, no more using 'file-put-contents' function.
260 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
261 require_once ABSPATH . '/wp-admin/includes/file.php';
262 WP_Filesystem();
263 }
264
265 $data = $this->owner->raw_data;
266 if ( ! file_exists( $data['file'] ) && ! is_dir( $data['file'] ) ) {
267 return array(
268 'code' => '',
269 'origin' => '',
270 );
271 }
272 switch ( $data['type'] ) {
273 case 'unversion':
274 return array( 'code' => $wp_filesystem->get_contents( $data['file'] ) );
275 case 'dir':
276 $dir_tree = new File( $data['file'], true, true, array(), array(), false );
277
278 return array( 'code' => implode( PHP_EOL, $dir_tree->get_dir_tree() ) );
279 case 'modified':
280 default:
281 $origin = $this->get_origin_code();
282
283 return array(
284 'code' => $wp_filesystem->get_contents( $data['file'] ),
285 'origin' => is_string( $origin ) ? $origin : '',
286 );
287 }
288 }
289
290 /**
291 * Retrieves a short description based on the type of data.
292 *
293 * @return string The short description.
294 */
295 private function get_short_description(): string {
296 $data = $this->owner->raw_data;
297 if ( 'unversion' === $data['type'] ) {
298 return esc_html__( 'Unknown file in WordPress core', 'defender-security' );
299 } elseif ( 'dir' === $data['type'] ) {
300 return esc_html__( 'This directory does not belong to WordPress core', 'defender-security' );
301 } elseif ( 'modified' === $data['type'] ) {
302 return esc_html__( 'This WordPress core file appears modified', 'defender-security' );
303 }
304
305 return '';
306 }
307 }
308