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 / traits / defender-hub-client.php

defender-hub-client.php in Defender Security – Malware Scanner, Login Security & Firewall 3.0.1, at src/traits/defender-hub-client.php

539 lines 17.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handle HUB based functionalities of WPMUDEV class.
4 *
5 * @package WP_Defender\Behavior
6 */
7
8 namespace WP_Defender\Traits;
9
10 use WP_Defender\Controller\Firewall;
11 use WP_Defender\Controller\Security_Headers;
12 use WP_Defender\Controller\Security_Tweaks;
13 use WP_Defender\Model\Audit_Log;
14 use WP_Defender\Model\Notification;
15 use WP_Defender\Model\Notification\Audit_Report;
16 use WP_Defender\Model\Notification\Firewall_Report;
17 use WP_Defender\Model\Notification\Malware_Report;
18 use WP_Defender\Model\Notification\Firewall_Notification;
19 use WP_Defender\Model\Scan;
20 use WP_Defender\Model\Setting\Scan as Scan_Settings;
21 use WP_Defender\Model\Setting\Audit_Logging;
22 use WP_Defender\Model\Setting\Login_Lockout;
23 use WP_Defender\Model\Setting\Mask_Login;
24 use WP_Defender\Model\Setting\Notfound_Lockout;
25 use WP_Defender\Model\Setting\Two_Fa;
26 use WP_Defender\Model\Setting\User_Agent_Lockout;
27
28 /**
29 * Traits to handle HUB based functionalities of WPMUDEV class.
30 */
31 trait Defender_Hub_Client {
32
33 /**
34 * @param $scenario
35 *
36 * @return string
37 */
38 public function get_endpoint( $scenario ) {
39 $base = defined( 'WPMUDEV_CUSTOM_API_SERVER' ) && WPMUDEV_CUSTOM_API_SERVER
40 ? WPMUDEV_CUSTOM_API_SERVER
41 : 'https://wpmudev.com/';
42 switch ( $scenario ) {
43 case self::API_SCAN_KNOWN_VULN:
44 return "{$base}api/defender/v1/vulnerabilities";
45 case self::API_SCAN_SIGNATURE:
46 return "{$base}api/defender/v1/yara-signatures";
47 case self::API_AUDIT:
48 // This is from another endpoint.
49 $base = defined( 'WPMUDEV_CUSTOM_AUDIT_SERVER' )
50 ? constant( 'WPMUDEV_CUSTOM_AUDIT_SERVER' )
51 : 'https://audit.wpmudev.org/';
52
53 return "{$base}logs";
54 case self::API_AUDIT_ADD:
55 $base = defined( 'WPMUDEV_CUSTOM_AUDIT_SERVER' )
56 ? constant( 'WPMUDEV_CUSTOM_AUDIT_SERVER' )
57 : 'https://audit.wpmudev.org/';
58
59 return "{$base}logs/add_multiple";
60 case self::API_BLACKLIST:
61 return "{$base}api/defender/v1/blacklist-monitoring?domain=" . network_site_url();
62 case self::API_WAF:
63 $site_id = $this->get_site_id();
64
65 return "{$base}api/hub/v1/sites/$site_id/modules/hosting";
66 case self::API_PACKAGE_CONFIGS:
67 return "{$base}api/hub/v1/package-configs";
68 case self::API_HUB_SYNC:
69 default:
70 return "{$base}api/defender/v1/scan-results";
71 }
72 }
73
74 /**
75 * Get WPMUDEV site id.
76 *
77 * @return bool
78 */
79 public function get_site_id() {
80 if ( $this->get_apikey() !== false ) {
81 return \WPMUDEV_Dashboard::$api->get_site_id();
82 }
83
84 return false;
85 }
86
87 /**
88 * @param string $scenario
89 * @param array $body
90 * @param array $args
91 * @param bool $recheck
92 *
93 * @return array|\WP_Error
94 */
95 public function make_wpmu_request( $scenario, $body = array(), $args = array(), $recheck = false ) {
96 $api_key = $this->get_apikey();
97 if ( false === $api_key ) {
98 $link_text = sprintf( '<a target="_blank" href="%s">%s</a>', 'https://wpmudev.com/project/wpmu-dev-dashboard/', __( 'here', 'wpdef' ) );
99 return new \WP_Error(
100 'dashboard_required',
101 sprintf(
102 /* translators: %s - wpmudev link */
103 esc_html__( 'WPMU DEV Dashboard will be required for this action. Please visit %s and install the WPMU DEV Dashboard.', 'wpdef' ),
104 $link_text
105 )
106 );
107 }
108 if ( ! isset( $body['domain'] ) ) {
109 $body['domain'] = network_site_url();
110 }
111 $headers = array(
112 'Authorization' => 'Basic ' . $api_key,
113 'apikey' => $api_key,
114 );
115
116 $args = array_merge(
117 $args,
118 array(
119 'body' => $body,
120 'headers' => $headers,
121 'timeout' => '30',
122 'sslverify' => apply_filters( 'https_ssl_verify', true ),
123 )
124 );
125 $request = wp_remote_request( $this->get_endpoint( $scenario ), $args );
126 if ( is_wp_error( $request ) ) {
127 if ( ! $recheck ) {
128 return $request;
129 }
130 // Sometimes a response comes with a curl error #52 so should delete Authorization header.
131 $args['headers'] = array( 'apikey' => $api_key );
132 $request = wp_remote_request( $this->get_endpoint( $scenario ), $args );
133 if ( is_wp_error( $request ) ) {
134 return $request;
135 }
136 }
137 $result = wp_remote_retrieve_body( $request );
138 $result = json_decode( $result, true );
139 if ( 200 !== wp_remote_retrieve_response_code( $request ) ) {
140 return new \WP_Error(
141 wp_remote_retrieve_response_code( $request ),
142 $result['message'] ?? wp_remote_retrieve_response_message( $request )
143 );
144 }
145
146 return $result;
147 }
148
149 /**
150 * This will build data relate to scan module, so we can push to hub.
151 *
152 * @since 2.5.0 remove 'theme_integrity'
153 * @since 2.4.7 add 'theme_integrity', 'plugin_integrity' args
154 *
155 * @return array
156 */
157 protected function build_scan_hub_data() {
158 $scan = Scan::get_last();
159 $scan_result = array(
160 'core_integrity' => 0,
161 // Leave for migration to 2.5.0.
162 'theme_integrity' => 0,
163 'plugin_integrity' => 0,
164 'vulnerability_db' => 0,
165 'file_suspicious' => 0,
166 'last_completed' => false,
167 'scan_items' => array(),
168 'num_issues' => 0,
169 'num_ignored_issues' => 0,
170 );
171 $total_issues = 0;
172 if ( is_object( $scan ) ) {
173 $data = $scan->prepare_issues();
174
175 $scan_result['core_integrity'] = $data['count_core'];
176 // Leave for migration to 2.5.0.
177 $scan_result['theme_integrity'] = 0;
178 $scan_result['plugin_integrity'] = $data['count_plugin'];
179 $scan_result['vulnerability_db'] = $data['count_vuln'];
180 $scan_result['file_suspicious'] = $data['count_malware'];
181 $scan_result['last_completed'] = $scan->date_end;
182 $scan_result['num_ignored_issues'] = count( $data['ignored'] );
183
184 if ( ! empty( $data['issues'] ) ) {
185 $total_issues = count( $data['issues'] );
186 foreach ( $data['issues'] as $key => $issue ) {
187 $scan_result['scan_items'][] = array(
188 'file' => $issue['full_path'] ?? $issue['file_name'],
189 'detail' => $issue['short_desc'],
190 );
191 }
192 }
193 $scan_result['num_issues'] = $total_issues + $scan_result['num_ignored_issues'];
194 }
195
196 $settings = new Scan_Settings();
197
198 return array(
199 'timestamp' => is_object( $scan ) ? strtotime( $scan->date_end ) : '',
200 'warning' => $total_issues,
201 'scan_result' => $scan_result,
202 'scan_schedule' => array(
203 // @since 2.7.0 change scheduled scan logic.
204 'is_activated' => $settings->scheduled_scanning,
205 // Example of frequency, day, time in build_notification_hub_data() method.
206 'time' => $settings->time,
207 'day' => $this->get_notification_day( $settings ),
208 'frequency' => $this->backward_frequency_compatibility( $settings->frequency ),
209 ),
210 );
211 }
212
213 public function backward_frequency_compatibility( $frequency ) {
214 switch ( $frequency ) {
215 case 'daily':
216 return 1;
217 case 'weekly':
218 return 7;
219 case 'monthly':
220 default:
221 return 30;
222 }
223 }
224
225 /**
226 * Build data for security tweaks.
227 *
228 * @return array
229 */
230 protected function build_security_tweaks_hub_data() {
231 $arr = wd_di()->get( Security_Tweaks::class )->data_frontend();
232 $data = array(
233 'cautions' => $arr['summary']['issues_count'],
234 'issues' => array(),
235 'ignore' => array(),
236 'fixed' => array(),
237 );
238 $types = array(
239 Security_Tweaks::STATUS_ISSUES,
240 Security_Tweaks::STATUS_IGNORE,
241 Security_Tweaks::STATUS_RESOLVE,
242 );
243 $view = '';
244 foreach ( $types as $type ) {
245 if ( 'ignore' === $type ) {
246 $view = '&view=ignored';
247 } elseif ( 'fixed' === $type ) {
248 $view = '&view=resolved';
249 }
250 foreach ( wd_di()->get( Security_Tweaks::class )->init_tweaks( $type, 'array' ) as $tweak ) {
251 $data[ $type ][] = array(
252 'label' => $tweak['title'],
253 'url' => network_admin_url( 'admin.php?page=wdf-hardener' . $view . '#' . $tweak['slug'] ),
254 );
255 }
256 }
257
258 return $data;
259 }
260
261 public function build_audit_hub_data() {
262 $date_from = ( new \DateTime( date( 'Y-m-d', strtotime( '-30 days' ) ) ) )->setTime(
263 0,
264 0,
265 0
266 )->getTimestamp();
267 $date_to = ( new \DateTime( date( 'Y-m-d' ) ) )->setTime( 23, 59, 59 )->getTimestamp();
268 $month_count = Audit_Log::count( $date_from, $date_to );
269 $last = Audit_Log::get_last();
270 if ( is_object( $last ) ) {
271 $last = gmdate( 'Y-m-d g:i a', $last->timestamp );
272 } else {
273 $last = 'n/a';
274 }
275
276 $settings = new Audit_Logging();
277
278 return array(
279 'month' => $month_count,
280 'last_event' => $last,
281 'enabled' => $settings->is_active(),
282 );
283 }
284
285 public function build_lockout_hub_data() {
286 $firewall = wd_di()->get( Firewall::class )->data_frontend();
287
288 return array(
289 'last_lockout' => $firewall['last_lockout'],
290 'lp' => wd_di()->get( Login_Lockout::class )->enabled,
291 'lp_week' => $firewall['login']['week'],
292 'nf' => wd_di()->get( Notfound_Lockout::class )->enabled,
293 'nf_week' => $firewall['nf']['week'],
294 'ua' => wd_di()->get( User_Agent_Lockout::class )->enabled,
295 'ua_week' => $firewall['ua']['week'],
296 );
297 }
298
299 public function build_2fa_hub_data() {
300 $settings = new Two_Fa();
301 $service = wd_di()->get( \WP_Defender\Component\Two_Fa::class );
302 $query = new \WP_User_Query(
303 array(
304 // Look over the network.
305 'blog_id' => 0,
306 'meta_key' => $service::DEFAULT_PROVIDER_USER_KEY,
307 'meta_value' => array_keys( $service->get_providers() ),
308 'meta_compare' => 'IN',
309 )
310 );
311 $active_users = array();
312 if ( $query->get_total() > 0 ) {
313 foreach ( $query->get_results() as $obj_user ) {
314 $active_users[] = array(
315 'display_name' => $obj_user->data->display_name,
316 );
317 }
318 }
319
320 return array(
321 'active' => $settings->enabled && count( $settings->user_roles ),
322 'enabled' => $settings->enabled,
323 'active_users' => $active_users,
324 );
325 }
326
327 public function build_mask_login_hub_data() {
328 $settings = new Mask_Login();
329
330 return array(
331 'active' => $settings->is_active(),
332 'masked_url' => $settings->mask_url,
333 );
334 }
335
336 public function build_recaptcha_hub_data() {
337 $settings = new \WP_Defender\Model\Setting\Recaptcha();
338
339 return array(
340 'active' => $settings->is_active(),
341 );
342 }
343
344 public function build_password_protection_hub_data() {
345 $settings = new \WP_Defender\Model\Setting\Password_Protection();
346
347 return array(
348 'active' => $settings->is_active(),
349 );
350 }
351
352 /**
353 * @param object $module_report
354 *
355 * @return string
356 */
357 private function get_notification_day( $module_report ) {
358 if ( ! is_object( $module_report ) ) {
359 return '';
360 }
361
362 if ( 'daily' === $module_report->frequency ) {
363 $day = '1';
364 } elseif ( 'weekly' === $module_report->frequency ) {
365 $day = $module_report->day;
366 } else {
367 // For 'monthly'.
368 $day = $module_report->day_n;
369 }
370
371 return $day;
372 }
373
374 /**
375 * Frequency format:
376 * if frequency is day, e.g.: 'frequency' => 1, 'day' => '1', 'time' => '20:30'
377 * if frequency is week, e.g.: 'frequency' => 7, 'day' => 'wednesday', 'time' => '14:00'
378 * if frequency is month, e.g.: 'frequency' => 30, 'day' => '4', 'time' => '4:30'
379 */
380 public function build_notification_hub_data() {
381 $audit_settings = new Audit_Logging();
382 $audit_report = new Audit_Report();
383 $firewall_report = new Firewall_Report();
384 $malware_report = new Malware_Report();
385 $scan_settings = new Scan_Settings();
386
387 return array(
388 'file_scanning' => array(
389 'active' => true,
390 // @since 2.7.0 move scheduled options to Scan settings, but we get status of Malware Scanning - Reporting here.
391 'enabled' => Notification::STATUS_ACTIVE === $malware_report->status,
392 // Report enabled bool value.
393 'frequency' => array(
394 'frequency' => $this->backward_frequency_compatibility( $scan_settings->frequency ),
395 'day' => $this->get_notification_day( $scan_settings ),
396 'time' => $scan_settings->time,
397 ),
398 ),
399 'audit_logging' => array(
400 'active' => $audit_settings->is_active(),
401 'enabled' => Notification::STATUS_ACTIVE === $audit_report->status,
402 'frequency' => array(
403 'frequency' => $this->backward_frequency_compatibility( $audit_report->frequency ),
404 'day' => $this->get_notification_day( $audit_report ),
405 'time' => $audit_report->time,
406 ),
407 ),
408 'ip_lockouts' => array(
409 // Always true as we have blacklist listening.
410 'active' => true,
411 'enabled' => Notification::STATUS_ACTIVE === $firewall_report->status,
412 // Report enabled bool value.
413 'frequency' => array(
414 'frequency' => $this->backward_frequency_compatibility( $firewall_report->frequency ),
415 'day' => $this->get_notification_day( $firewall_report ),
416 'time' => $firewall_report->time,
417 ),
418 ),
419 );
420 }
421
422 public function build_firewall_notification_hub_data() {
423 $firewall_notification = new Firewall_Notification();
424 // Todo: add ua_lockout to structure when Firewall - Notification will have an option for that.
425 if ( 'enabled' === $firewall_notification->status ) {
426 $login_lockout = $firewall_notification->configs['login_lockout'];
427 $nf_lockout = $firewall_notification->configs['nf_lockout'];
428 } else {
429 $login_lockout = false;
430 $nf_lockout = false;
431 }
432
433 return array(
434 'firewall' => array(
435 'login_lockout' => $login_lockout,
436 '404_lockout' => $nf_lockout,
437 ),
438 );
439 }
440
441 public function build_security_headers_hub_data() {
442 $security_headers = wd_di()->get( Security_Headers::class )->get_type_headers();
443
444 return array(
445 'active' => $security_headers['active'],
446 'inactive' => $security_headers['inactive'],
447 );
448 }
449
450 public function build_stats_to_hub() {
451 $scan_data = $this->build_scan_hub_data();
452 $tweaks_data = $this->build_security_tweaks_hub_data();
453 $audit_data = $this->build_audit_hub_data();
454 $firewall_data = $this->build_lockout_hub_data();
455 $two_fa = $this->build_2fa_hub_data();
456 $mask_login = $this->build_mask_login_hub_data();
457 $sec_headers = $this->build_security_headers_hub_data();
458 $recaptcha = $this->build_recaptcha_hub_data();
459 $pwned_password = $this->build_password_protection_hub_data();
460
461 $data = array(
462 // Domain name.
463 'domain' => network_home_url(),
464 // Last scan date.
465 'timestamp' => $scan_data['timestamp'],
466 // Scan issue count.
467 'warnings' => $scan_data['warning'],
468 // Security tweaks issue count.
469 'cautions' => $tweaks_data['cautions'],
470 'data_version' => gmdate( 'Ymd' ),
471 'scan_data' => json_encode(
472 array(
473 'scan_result' => $scan_data['scan_result'],
474 'hardener_result' => array(
475 'issues' => $tweaks_data[ Security_Tweaks::STATUS_ISSUES ],
476 'ignored' => $tweaks_data[ Security_Tweaks::STATUS_IGNORE ],
477 'resolved' => $tweaks_data[ Security_Tweaks::STATUS_RESOLVE ],
478 ),
479 'scan_schedule' => $scan_data['scan_schedule'],
480 'audit_status' => array(
481 'events_in_month' => $audit_data['month'],
482 'audit_enabled' => $audit_data['enabled'],
483 'last_event_date' => $audit_data['last_event'],
484 ),
485 'audit_page_url' => network_admin_url( 'admin.php?page=wdf-logging' ),
486 'labels' => array(
487 // Todo: maybe should it remove because Scan Settings model has label() method for that?
488 'parent_integrity' => esc_html__( 'File change detection', 'wpdef' ),
489 'core_integrity' => esc_html__( 'Scan core files', 'wpdef' ),
490 'plugin_integrity' => esc_html__( 'Scan plugin files', 'wpdef' ),
491 'vulnerability_db' => esc_html__( 'Known vulnerabilities', 'wpdef' ),
492 'file_suspicious' => esc_html__( 'Suspicious code', 'wpdef' ),
493 ),
494 'scan_page_url' => network_admin_url( 'admin.php?page=wdf-scan' ),
495 'hardener_page_url' => network_admin_url( 'admin.php?page=wdf-hardener' ),
496 'new_scan_url' => network_admin_url( 'admin.php?page=wdf-scan&wdf-action=new_scan' ),
497 'schedule_scans_url' => network_admin_url( 'admin.php?page=wdf-schedule-scan' ),
498 'settings_page_url' => network_admin_url( 'admin.php?page=wdf-settings' ),
499 'ip_lockout_page_url' => network_admin_url( 'admin.php?page=wdf-ip-lockout' ),
500 'last_lockout' => $firewall_data['last_lockout'],
501 'login_lockout_enabled' => $firewall_data['lp'],
502 'login_lockout' => $firewall_data['lp_week'],
503 'lockout_404_enabled' => $firewall_data['nf'],
504 'lockout_404' => $firewall_data['nf_week'],
505 'lockout_ua_enabled' => $firewall_data['ua'],
506 'lockout_ua' => $firewall_data['ua_week'],
507 'total_lockout' => (int) $firewall_data['lp_week'] + (int) $firewall_data['nf_week'] + (int) $firewall_data['ua_week'],
508 'advanced' => array(
509 // This is moved but still keep here for backward compatibility.
510 'multi_factors_auth' => array(
511 'active' => $two_fa['active'],
512 'enabled' => $two_fa['enabled'],
513 'active_users' => $two_fa['active_users'],
514 ),
515 'mask_login' => array(
516 'activate' => $mask_login['active'],
517 'masked_url' => $mask_login['masked_url'],
518 ),
519 'security_headers' => array(
520 'active' => $sec_headers['active'],
521 'inactive' => $sec_headers['inactive'],
522 ),
523 'google_recaptcha' => array(
524 'active' => $recaptcha['active'],
525 ),
526 'password_protection' => array(
527 'active' => $pwned_password['active'],
528 ),
529 ),
530 'reports' => $this->build_notification_hub_data(),
531 'notifications' => $this->build_firewall_notification_hub_data(),
532 )
533 ),
534 );
535
536 return $data;
537 }
538 }
539