PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.0.1
Defender Security – Malware Scanner, Login Security & Firewall v3.0.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 / malware-scan.php

malware-scan.php in Defender Security – Malware Scanner, Login Security & Firewall 3.0.1, at src/behavior/scan/malware-scan.php

240 lines 6.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WP_Defender\Behavior\Scan;
4
5 use Calotes\Component\Behavior;
6 use WP_Defender\Behavior\WPMUDEV;
7 use WP_Defender\Component\Timer;
8 use WP_Defender\Model\Scan_Item;
9 use WP_Defender\Traits\IO;
10 use WP_Defender\Model\Setting\Scan as Scan_Settings;
11 use WP_Defender\Traits\Plugin;
12 use WP_Defender\Traits\Theme;
13
14 class Malware_Scan extends Behavior {
15 use IO, Theme, Plugin;
16
17 public const YARA_RULES = 'defender_yara_rules';
18 /**
19 * Cache for file content.
20 * @var string
21 */
22 private $content;
23
24 /**
25 * Cache tokens.
26 * @var
27 */
28 private $tokens;
29
30 /**
31 * Backup memory.
32 * @var
33 */
34 private $memory;
35
36 /**
37 * @param object $scan_settings
38 *
39 * @return array
40 */
41 protected function get_additional_rules( $scan_settings ) {
42 $plugin_cache = false;
43 $plugin_slugs_changes = array();
44 $plugin_pro_slugs = array();
45 $plugin_all_slugs = array();
46 // Checked Plugin option.
47 if ( $scan_settings->integrity_check && $scan_settings->check_plugins ) {
48 $plugin_cache = true;
49 $arr = get_site_option( Plugin_Integrity::PLUGIN_SLUGS, false );
50 if ( is_array( $arr ) && ! empty( $arr ) ) {
51 $plugin_slugs_changes = $arr;
52 }
53 $plugin_slugs = get_site_option( Plugin_Integrity::PLUGIN_PREMIUM_SLUGS, false );
54 if ( is_array( $plugin_slugs ) && ! empty( $plugin_slugs ) ) {
55 $plugin_pro_slugs = $plugin_slugs;
56 }
57 $plugin_all_slugs = $this->get_plugin_slugs();
58 }
59
60 return array(
61 'plugin_change' => $plugin_cache,
62 // List of plugins with modifications.
63 'plugin_slugs_changes' => $plugin_slugs_changes,
64 // List of pro plugins.
65 'plugin_pro_slugs' => $plugin_pro_slugs,
66 'plugin_all_slugs' => $plugin_all_slugs,
67 );
68 }
69
70 /**
71 * @param string $file_path
72 * @param array $rules
73 *
74 * @return bool
75 */
76 protected function was_modificated_file( $file_path, $rules ) {
77 // Unchecked 'Plugin change file' option, so green light to display Suspicious checks.
78 if ( ! $rules['plugin_change'] ) {
79 return true;
80 }
81
82 $search_on_plugin = WP_PLUGIN_DIR . '/';
83 if ( false !== stripos( $file_path, $search_on_plugin ) ) {
84 /**
85 * Suspicious code in /plugins.
86 * Empty list of plugin slugs because there are premium plugins,
87 * not modifications on Free plugins,
88 * or not plugins on site.
89 * Should check separate custom files/dirs in the root too.
90 */
91 $rev_file = str_replace( $search_on_plugin, '', $file_path );
92 $matches = explode( '/', $rev_file );
93 $base_slug = array_shift( $matches );
94 // Custom files/dirs.
95 if ( ! in_array( $base_slug, $rules['plugin_all_slugs'], true ) ) {
96 return true;
97 }
98 if ( empty( $rules['plugin_slugs_changes'] ) && empty( $rules['plugin_pro_slugs'] ) ) {
99 // No modifications.
100 return false;
101 }
102 // Is it on premium plugins?
103 if ( in_array( $base_slug, $rules['plugin_pro_slugs'], true ) ) {
104 return true;
105 }
106 if ( in_array( $base_slug, (array) $rules['plugin_slugs_changes'], true ) ) {
107 // Modifications in this plugin.
108 return true;
109 }
110 // Modifications are not here.
111 return false;
112 }
113
114 // Other WP places.
115 return true;
116 }
117
118 public function suspicious_check() {
119 set_time_limit( 0 );
120 $this->prepare_emergency_shutdown();
121 $timer = new Timer();
122 $rules = $this->fetch_yara_rules();
123 $this->attach_behavior( Malware_Quick_Scan::class, Malware_Quick_Scan::class );
124 $this->attach_behavior( Malware_Deep_Scan::class, Malware_Deep_Scan::class );
125 $model = $this->owner->scan;
126 $pos = (int) $model->task_checkpoint;
127
128 $files = get_site_option( Gather_Fact::CACHE_CONTENT, array() );
129 if ( empty( $files ) ) {
130 return true;
131 }
132 $combinations = $this->get_additional_rules( new Scan_Settings() );
133 $files = new \ArrayIterator( $files );
134 $files->seek( $pos );
135 while ( $files->valid() ) {
136 if ( ! $timer->check() ) {
137 $this->log( 'Rage quit', 'malware_scan.log' );
138 $model->save();
139 break;
140 }
141 if ( $model->is_issue_ignored( $files->current() ) ) {
142 $this->log( sprintf( 'skip %s because of file is ignored', $files->current() ), 'malware_scan.log' );
143 $files->next();
144 continue;
145 }
146
147 [$result, $qs_detail] = $this->do_quick_scan( $files->current(), $rules );
148 if ( $result ) {
149 $this->log( sprintf( 'file %s suspicious', $files->current() ), 'malware_scan.log' );
150 $result = $this->do_deep_scan( $files->current(), $rules, $qs_detail );
151 /**
152 * Add new item if Suspicious code is found and:
153 * plugins/themes are premium,
154 * plugins/themes are on wp.org but the code doesn't match from the WP repo (there are differences in checksums),
155 * deactivated options of File change detection > Scan plugin OR theme file changes
156 */
157 if ( is_array( $result ) && $this->was_modificated_file( $files->current(), $combinations ) ) {
158 $result['file'] = $files->current();
159 $model->add_item( Scan_Item::TYPE_SUSPICIOUS, $result );
160 }
161 }
162 $files->next();
163 $model->task_checkpoint = $files->key();
164 $model->calculate_percent( $files->key() * 100 / $files->count(), 6 );
165 if ( 0 === $files->key() % 100 ) {
166 // We should update the model percent each 100 files so we have some progress ont he screen$pos * 100 / $core_files->count()
167 $model->save();
168 }
169 }
170
171 if ( ! $files->valid() ) {
172 $last = \WP_Defender\Model\Scan::get_last();
173 if ( is_object( $last ) ) {
174 $ignored_issues = $last->get_issues( Scan_Item::TYPE_SUSPICIOUS, Scan_Item::STATUS_IGNORE );
175 foreach ( $ignored_issues as $issue ) {
176 $this->owner->scan->add_item(
177 Scan_Item::TYPE_SUSPICIOUS,
178 $issue->raw_data,
179 Scan_Item::STATUS_IGNORE
180 );
181 }
182 }
183 $model->task_checkpoint = null;
184 }
185
186 $model->save();
187
188 return ! $files->valid();
189 }
190
191 /**
192 * We will use this for a safe switch when memory out happen.
193 */
194 public function prepare_emergency_shutdown() {
195 $this->memory = str_repeat( '*', 1024 * 1024 );
196
197 register_shutdown_function(
198 function () {
199 if ( \WP_Defender\Model\Scan::STATUS_FINISH === $this->owner->scan->status ) {
200 return;
201 }
202
203 $this->memory = null;
204 $err = error_get_last();
205 if (
206 ( ! is_null( $err ) )
207 && ( ! in_array( $err['type'], array( E_NOTICE, E_WARNING, E_DEPRECATED ), true ) )
208 ) {
209 $this->log( var_export( $err, true ), 'scan.log' );
210 $this->log( 'Something wrong happen, saving and quit.', 'scan.log' );
211 $this->owner->scan->status = \WP_Defender\Model\Scan::STATUS_ERROR;
212 ++$this->owner->scan->task_checkpoint;
213 $this->owner->scan->save();
214 }
215 }
216 );
217 }
218
219 /**
220 * Fetch yara rules from API.
221 *
222 * @return array|mixed
223 */
224 private function fetch_yara_rules() {
225 $rules = get_site_option( self::YARA_RULES, false );
226 if ( is_array( $rules ) ) {
227 return $rules;
228 }
229 $this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
230 $rules = $this->make_wpmu_request( WPMUDEV::API_SCAN_SIGNATURE );
231 if ( is_array( $rules ) ) {
232 update_site_option( self::YARA_RULES, $rules );
233
234 return $rules;
235 }
236
237 return array();
238 }
239 }
240