PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.3.1
Defender Security – Malware Scanner, Login Security & Firewall v5.3.1
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 / class-core-integrity.php

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

223 lines 6.1 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
6 */
7
8 namespace WP_Defender\Behavior\Scan;
9
10 use ArrayIterator;
11 use WP_Defender\Traits\IO;
12 use WP_Defender\Model\Scan;
13 use Calotes\Component\Behavior;
14 use WP_Defender\Model\Scan_Item;
15 use WP_Defender\Component\Timer;
16 use WP_Defender\Helper\Analytics\Scan as Scan_Analytics;
17 use WP_Defender\Controller\Scan as Scan_Controller;
18
19 /**
20 * Handles core integrity scan.
21 */
22 class Core_Integrity extends Behavior {
23
24 use IO;
25
26 public const CACHE_CHECKSUMS = 'wd_cache_checksums', ISSUE_CHECKSUMS = 'wd_issue_checksums';
27
28 /**
29 * Check that the folder is empty.
30 *
31 * @param string $path The path to check.
32 *
33 * @return bool
34 */
35 protected function is_dir_empty( $path ) {
36 $rfiles = scandir( $path );
37 foreach ( $rfiles as $rfile ) {
38 if ( ! in_array( $rfile, array( '.', '..' ), true ) ) {
39 return false;
40 }
41 }
42
43 return true;
44 }
45
46 /**
47 * Check if the core file is on touch.
48 *
49 * @return bool
50 */
51 public function core_integrity_check(): bool {
52 $checksums = $this->get_checksum();
53 if ( false === $checksums ) {
54 return false;
55 }
56 $core_files = get_site_option( Gather_Fact::CACHE_CORE, array() );
57 $core_files = new ArrayIterator( $core_files );
58 $timer = new Timer();
59 $model = $this->owner->scan;
60 $pos = (int) $model->task_checkpoint;
61 if ( $pos > 0 ) {
62 $core_files->seek( $pos );
63 }
64 $this->log( sprintf( 'current pos %s', $pos ), Scan_Controller::SCAN_LOG );
65 while ( $core_files->valid() ) {
66 if ( ! $timer->check() ) {
67
68 $reason = 'break out cause too long';
69
70 /**
71 * Retrieves an instance of the Scan_Analytics class.
72 *
73 * @var Scan_Analytics $scan_analytics
74 */
75 $scan_analytics = wd_di()->get( Scan_Analytics::class );
76
77 $scan_analytics->track_feature(
78 $scan_analytics::EVENT_SCAN_FAILED,
79 array(
80 $scan_analytics::EVENT_SCAN_FAILED_PROP => $scan_analytics::EVENT_SCAN_FAILED_ERROR,
81 'Error_Reason' => $reason,
82 )
83 );
84
85 $this->log( $reason, Scan_Controller::SCAN_LOG );
86 break;
87 }
88
89 if ( $model->is_issue_whitelisted( $core_files->current() ) ) {
90 // This is whitelisted, so do nothing.
91 $this->log( sprintf( 'skip %s because of file is whitelisted', $core_files->current() ), Scan_Controller::SCAN_LOG );
92 $core_files->next();
93 continue;
94 }
95
96 if ( $model->is_issue_ignored( $core_files->current() ) ) {
97 // This is ignored, so do nothing.
98 $this->log( sprintf( 'skip %s because of file is ignored', $core_files->current() ), Scan_Controller::SCAN_LOG );
99 $core_files->next();
100 continue;
101 }
102
103 $file = $core_files->current();
104 // Get relative so we can compare.
105 $abs_path = defender_replace_line( ABSPATH );
106 $rev_file = str_replace( $abs_path, '', $file );
107 // Remove directory separator on the left.
108 $rev_file = ltrim( $rev_file, DIRECTORY_SEPARATOR );
109
110 if ( isset( $checksums[ $rev_file ] ) ) {
111 if ( ! $this->compare_hashes( $file, $checksums[ $rev_file ] ) ) {
112 $this->log( sprintf( 'modified %s', $file ), Scan_Controller::SCAN_LOG );
113 $model->add_item(
114 Scan_Item::TYPE_INTEGRITY,
115 array(
116 'file' => $file,
117 'type' => 'modified',
118 )
119 );
120 }
121 } else {
122 if ( is_dir( $file ) ) {
123 if ( $this->is_dir_empty( $core_files->current() ) ) {
124 $this->log(
125 sprintf( 'skip %s because of non-WP directory is empty', $core_files->current() ),
126 Scan_Controller::SCAN_LOG
127 );
128 $core_files->next();
129 continue;
130 }
131 $item_type = 'dir';
132 } else {
133 $item_type = 'unversion';
134 }
135
136 $model->add_item(
137 Scan_Item::TYPE_INTEGRITY,
138 array(
139 'file' => $file,
140 'type' => $item_type,
141 )
142 );
143 }
144 $model->calculate_percent( $core_files->key() * 100 / $core_files->count(), 2 );
145 if ( 0 === $core_files->key() % 100 ) {
146 // We should update the model percent each 100 files, so we have some progress ont he screen$pos * 100 / $core_files->count().
147 $model->save();
148 }
149 $core_files->next();
150 }
151 if ( $core_files->valid() ) {
152 // Save the current progress and quit.
153 $model->task_checkpoint = $core_files->key();
154 } else {
155 // We will check if we have any ignore issue from last scan, so we can bring it here.
156 $last = Scan::get_last();
157 if ( is_object( $last ) ) {
158 $ignored_issues = $last->get_issues( Scan_Item::TYPE_INTEGRITY, Scan_Item::STATUS_IGNORE );
159 foreach ( $ignored_issues as $issue ) {
160 $model->add_item( Scan_Item::TYPE_INTEGRITY, $issue->raw_data, Scan_Item::STATUS_IGNORE );
161 }
162 }
163 // Done, reset this, so we can use it later.
164 $model->task_checkpoint = '';
165 }
166 $model->save();
167
168 return ! $core_files->valid();
169 }
170
171 /**
172 * Fetch the checksums.
173 *
174 * @return bool|array
175 */
176 protected function get_checksum() {
177 $cache = get_site_option( self::CACHE_CHECKSUMS, false );
178 if ( is_array( $cache ) ) {
179 return $cache;
180 }
181 $this->log( 'Fetch checksums, should only once', Scan_Controller::SCAN_LOG );
182
183 global $wp_version, $wp_local_package;
184
185 if ( ! function_exists( 'get_core_checksums' ) ) {
186 include_once ABSPATH . 'wp-admin/includes/update.php';
187 }
188 $checksums = get_core_checksums( $wp_version, empty( $wp_local_package ) ? 'en_US' : $wp_local_package );
189 if ( false === $checksums ) {
190 $this->log( 'Error from fetching checksums from wp.org', Scan_Controller::SCAN_LOG );
191 $scan = $this->owner->scan;
192 $scan->status = Scan::STATUS_IDLE;
193 $scan->save();
194
195 update_site_option( self::ISSUE_CHECKSUMS, time() );
196 // We can't send to MP event now because the Scan process continues to ping wp.org. This can cause a event's surge.
197 return false;
198 }
199
200 if ( isset( $checksums[ $wp_version ] ) ) {
201 /**
202 * Sometimes the API returns the format [$wp_version]=>'...' when locale not right.
203 * Use this as fail-safe.
204 */
205 $checksums = $checksums[ $wp_version ];
206 }
207 if ( is_array( $checksums ) ) {
208 foreach ( $checksums as $key => $checksum ) {
209 $formatted_key = defender_replace_line( $key );
210
211 if ( $key !== $formatted_key ) {
212 $checksums[ $formatted_key ] = $checksum;
213 unset( $checksums[ $key ] );
214 }
215 }
216 }
217
218 update_site_option( self::CACHE_CHECKSUMS, $checksums );
219
220 return $checksums;
221 }
222 }
223