Server IP : 104.21.14.48 / Your IP : 18.216.156.12 [ Web Server : Apache System : Linux b70eb322-3aee-0c53-7c82-0db91281f2c6.secureserver.net 6.1.90-1.el9.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 2 12:09:22 EDT 2024 x86_64 User : root ( 0) PHP Version : 8.0.30.2 Disable Function : NONE Domains : 0 Domains MySQL : ON | cURL : ON | WGET : ON | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/wp-content/plugins/defender-security/src/traits/ |
Upload File : |
<?php /** * Handle HUB based functionalities of WPMUDEV class. * * @package WP_Defender\Traits */ namespace WP_Defender\Traits; use WP_Error; use DateTime; use Exception; use WP_User_Query; use WPMUDEV_Dashboard; use WP_Defender\Model\Scan; use WP_Defender\Model\Audit_Log; use WP_Defender\Model\Notification; use WP_Defender\Controller\Firewall; use WP_Defender\Component\Quarantine; use WP_Defender\Model\Setting\Two_Fa; use WP_Defender\Model\Setting\Recaptcha; use WP_Defender\Model\Setting\Mask_Login; use WP_Defender\Controller\Security_Tweaks; use WP_Defender\Controller\Security_Headers; use WP_Defender\Model\Setting\Audit_Logging; use WP_Defender\Model\Setting\Login_Lockout; use WP_Defender\Model\Setting\Notfound_Lockout; use WP_Defender\Model\Notification\Audit_Report; use WP_Defender\Model\Setting\Global_Ip_Lockout; use WP_Defender\Model\Setting\User_Agent_Lockout; use WP_Defender\Model\Notification\Malware_Report; use WP_Defender\Model\Setting\Password_Protection; use WP_Defender\Model\Notification\Firewall_Report; use WP_Defender\Model\Setting\Scan as Scan_Settings; use WP_Defender\Model\Notification\Firewall_Notification; trait Defender_Hub_Client { use IP; /** * Get API base URL. * * @return string * @since 3.4.0 */ public function get_api_base_url(): string { return defined( 'WPMUDEV_CUSTOM_API_SERVER' ) && WPMUDEV_CUSTOM_API_SERVER ? WPMUDEV_CUSTOM_API_SERVER : 'https://wpmudev.com/'; } /** * Retrieves the endpoint URL based on the given scenario. * * @param string $scenario The scenario identifier. * * @return string The endpoint URL. */ public function get_endpoint( $scenario ): string { $base = $this->get_api_base_url(); switch ( $scenario ) { case self::API_SCAN_KNOWN_VULN: return "{$base}api/defender/v1/vulnerabilities"; case self::API_SCAN_SIGNATURE: return "{$base}api/defender/v1/yara-signatures"; case self::API_AUDIT: // This is from another endpoint. $base = defined( 'WPMUDEV_CUSTOM_AUDIT_SERVER' ) ? constant( 'WPMUDEV_CUSTOM_AUDIT_SERVER' ) : 'https://audit.wpmudev.org/'; return "{$base}logs"; case self::API_AUDIT_ADD: $base = defined( 'WPMUDEV_CUSTOM_AUDIT_SERVER' ) ? constant( 'WPMUDEV_CUSTOM_AUDIT_SERVER' ) : 'https://audit.wpmudev.org/'; return "{$base}logs/add_multiple"; case self::API_BLACKLIST: return "{$base}api/defender/v1/blacklist-monitoring?domain=" . network_site_url(); case self::API_WAF: $site_id = $this->get_site_id(); return "{$base}api/hub/v1/sites/$site_id/modules/hosting"; case self::API_GLOBAL_IP_LIST: return "{$base}api/hub/v1/global-ip-list"; case self::API_PACKAGE_CONFIGS: return "{$base}api/hub/v1/package-configs"; case self::API_IP_BLOCKLIST_SUBMIT_LOGS: return "{$base}api/blocklist/v1/logs"; case self::API_HUB_SYNC: default: return "{$base}api/defender/v1/scan-results"; } } /** * Get WPMUDEV site id. * * @return int|bool */ public function get_site_id() { if ( false !== $this->get_apikey() ) { return (int) WPMUDEV_Dashboard::$api->get_site_id(); } return false; } /** * HUB API remote request method. * * @param string $scenario The scenario for the API request. * @param array $body The body of the API request. Default is an empty array. * @param array $args The arguments for the API request. Default is an empty array. * @param bool $recheck Whether to recheck the API request. Default is false. * * @return array|WP_Error The response body of the API request or a WP_Error object. * @throws Exception If the Dash plugin authentication API key is missing. */ private function hub_api_request( string $scenario, array $body = array(), array $args = array(), bool $recheck = false ) { $dash_api = WPMUDEV_Dashboard::$api; if ( ! $dash_api->has_key() ) { throw new Exception( esc_html__( 'Dash plugin authentication API key missing.', 'defender-security' ) ); } $api_key = $dash_api->get_key(); $body['domain'] ??= network_site_url(); $headers = array( 'Authorization' => 'Basic ' . $api_key, 'apikey' => $api_key, ); $args = array_merge( $args, array( 'body' => $body, 'headers' => $headers, 'timeout' => '30', 'sslverify' => apply_filters( 'https_ssl_verify', true ), ) ); $request = wp_remote_request( $this->get_endpoint( $scenario ), $args ); if ( is_wp_error( $request ) ) { if ( ! $recheck ) { return $request; } // Sometimes a response comes with a curl error #52 so should delete Authorization header. $args['headers'] = array( 'apikey' => $api_key ); $request = wp_remote_request( $this->get_endpoint( $scenario ), $args ); if ( is_wp_error( $request ) ) { return $request; } } $result = wp_remote_retrieve_body( $request ); $result = json_decode( $result, true ); if ( 200 !== wp_remote_retrieve_response_code( $request ) ) { return new WP_Error( wp_remote_retrieve_response_code( $request ), $result['message'] ?? wp_remote_retrieve_response_message( $request ) ); } return $result; } /** * Makes a request to the WPMU API. * * @param string $scenario The scenario for the request. * @param array $body The body of the request. Default is an empty array. * @param array $args Additional arguments for the request. Default is an empty array. * @param bool $recheck Whether to recheck the request. Default is false. * * @return array|WP_Error The response from the API request. */ public function make_wpmu_request( string $scenario, array $body = array(), array $args = array(), bool $recheck = false ) { $api_key = $this->get_apikey(); if ( false === $api_key ) { $link_text = sprintf( '<a target="_blank" href="%s">%s</a>', 'https://wpmudev.com/project/wpmu-dev-dashboard/', esc_html__( 'here', 'defender-security' ) ); return new WP_Error( 'dashboard_required', sprintf( /* translators: %s - wpmudev link */ esc_html__( 'WPMU DEV Dashboard will be required for this action. Please visit %s and install the WPMU DEV Dashboard.', 'defender-security' ), $link_text ) ); } return $this->hub_api_request( $scenario, $body, $args, $recheck ); } /** * Makes a request to the WPMU API for free and higher account holding users. * * @param string $scenario The scenario for the request. * @param array $body The body of the request. Default is an empty array. * @param array $args Additional arguments for the request. Default is an empty array. * @param bool $recheck Whether to recheck the request. Default is false. * * @return array|WP_Error The response from the API request. * @throws Exception If the Dash plugin authentication API key is missing. * @throws Exception If permission is denied for the API call. */ public function make_wpmu_free_request( string $scenario, array $body = array(), array $args = array(), bool $recheck = false ) { if ( $this->can_wpmu_free_request() === false ) { throw new Exception( esc_html__( 'Permission denied API call.', 'defender-security' ) ); } $dash_api = WPMUDEV_Dashboard::$api; if ( ! $dash_api->has_key() ) { throw new Exception( esc_html__( 'Dash plugin authentication API key missing.', 'defender-security' ) ); } return $this->hub_api_request( $scenario, $body, $args, $recheck ); } /** * Check if the current request can be made as a free API request. * * @return bool True if the request is allowed, false otherwise. * @throws Exception If the WPMU DEV Dashboard plugin is missing. */ public function can_wpmu_free_request() { if ( ! class_exists( 'WPMUDEV_Dashboard' ) ) { throw new Exception( esc_html__( 'WPMU DEV Dashboard plugin is missing.', 'defender-security' ) ); } WPMUDEV_Dashboard::instance(); $membership_status = WPMUDEV_Dashboard::$api->get_membership_status(); if ( in_array( $membership_status, array( 'single', 'unit' ), true ) ) { return $this->is_member(); } return in_array( $membership_status, array( 'free', 'full' ), true ); } /** * This will build data relate to scan module, so we can push to hub. * * @return array * @since 2.4.7 add 'plugin_integrity' args */ protected function build_scan_hub_data(): array { $scan = Scan::get_last(); $scan_result = array( 'core_integrity' => 0, 'plugin_integrity' => 0, 'vulnerability_db' => 0, 'file_suspicious' => 0, 'last_completed' => false, 'scan_items' => array(), 'num_issues' => 0, 'num_ignored_issues' => 0, ); $total_issues = 0; if ( is_object( $scan ) ) { $data = $scan->prepare_issues( 10, 1 ); $scan_result['core_integrity'] = $data['count_core']; $scan_result['plugin_integrity'] = $data['count_plugin']; $scan_result['vulnerability_db'] = $data['count_vuln']; $scan_result['file_suspicious'] = $data['count_malware']; $scan_result['last_completed'] = $scan->date_end; $scan_result['num_ignored_issues'] = $data['count_ignored']; if ( ! empty( $data['issues'] ) ) { $total_issues = $data['count_issues']; foreach ( $data['issues'] as $issue ) { $scan_result['scan_items'][] = array( 'file' => $issue['full_path'] ?? $issue['file_name'], 'detail' => $issue['short_desc'], ); } } $scan_result['num_issues'] = $total_issues + $data['count_ignored']; } $settings = new Scan_Settings(); return array( 'timestamp' => is_object( $scan ) ? strtotime( $scan->date_start ) : '', 'warning' => $total_issues, 'scan_result' => $scan_result, 'scan_schedule' => array( // @since 2.7.0 change scheduled scan logic. 'is_activated' => $settings->scheduled_scanning, // Example of frequency, day, time in build_notification_hub_data() method. 'time' => $settings->time, 'day' => $this->get_notification_day( $settings ), 'frequency' => $this->backward_frequency_compatibility( $settings->frequency ), ), ); } /** * Converts a string frequency to an integer value for backward compatibility. * * @param string $frequency The frequency to convert. Must be one of 'daily', 'weekly', or 'monthly'. * * @return int The converted integer value. Returns 1 for 'daily', 7 for 'weekly', and 30 for 'monthly' (default). */ public function backward_frequency_compatibility( string $frequency ): int { switch ( $frequency ) { case 'daily': return 1; case 'weekly': return 7; case 'monthly': default: return 30; } } /** * Build data for security tweaks. * * @return array */ protected function build_security_tweaks_hub_data(): array { $arr = wd_di()->get( Security_Tweaks::class )->data_frontend(); $data = array( 'cautions' => $arr['summary']['issues_count'], 'issues' => array(), 'ignore' => array(), 'fixed' => array(), ); $types = array( Security_Tweaks::STATUS_ISSUES, Security_Tweaks::STATUS_IGNORE, Security_Tweaks::STATUS_RESOLVE, ); $view = ''; foreach ( $types as $type ) { if ( 'ignore' === $type ) { $view = '&view=ignored'; } elseif ( 'fixed' === $type ) { $view = '&view=resolved'; } foreach ( wd_di()->get( Security_Tweaks::class )->init_tweaks( $type, 'array' ) as $tweak ) { $data[ $type ][] = array( 'label' => $tweak['title'], 'url' => network_admin_url( 'admin.php?page=wdf-hardener' . $view . '#' . $tweak['slug'] ), ); } } return $data; } /** * Builds an array of audit data to be sent to the hub. * * @return array An array containing the number of audit log entries, the timestamp of the * last audit log entry, and a boolean indicating if audit logging is enabled. */ public function build_audit_hub_data(): array { $date_from = ( new DateTime( wp_date( 'Y-m-d', strtotime( '-30 days' ) ) ) )->setTime( 0, 0, 0 )->getTimestamp(); $date_to = ( new DateTime( wp_date( 'Y-m-d' ) ) )->setTime( 23, 59, 59 )->getTimestamp(); $month_count = Audit_Log::count( $date_from, $date_to ); $last = Audit_Log::get_last(); if ( is_object( $last ) ) { $last = wp_date( 'Y-m-d g:i a', $last->timestamp ); } else { $last = 'n/a'; } $settings = new Audit_Logging(); return array( 'month' => $month_count, 'last_event' => $last, 'enabled' => $settings->is_active(), ); } /** * Builds an array of lockout data to be sent to the hub. * * @return array */ public function build_lockout_hub_data(): array { $firewall = wd_di()->get( Firewall::class )->data_frontend(); return array( 'last_lockout' => $firewall['last_lockout'], 'lp' => wd_di()->get( Login_Lockout::class )->enabled, 'lp_week' => $firewall['login']['week'], 'nf' => wd_di()->get( Notfound_Lockout::class )->enabled, 'nf_week' => $firewall['nf']['week'], 'ua' => wd_di()->get( User_Agent_Lockout::class )->enabled, 'ua_week' => $firewall['ua']['week'], 'global_ip_list_enabled' => wd_di()->get( Global_Ip_Lockout::class )->enabled, ); } /** * Builds an array of 2fa data to be sent to the hub. * * @return array */ public function build_2fa_hub_data(): array { $settings = new Two_Fa(); $service = wd_di()->get( \WP_Defender\Component\Two_Fa::class ); $query = new WP_User_Query( array( // Look over the network. 'blog_id' => 0, 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query array( 'key' => $service::DEFAULT_PROVIDER_USER_KEY, 'value' => array_keys( $service->get_providers() ), 'compare' => 'IN', ), ), ) ); $active_users = array(); if ( $query->get_total() > 0 ) { foreach ( $query->get_results() as $obj_user ) { $active_users[] = array( 'display_name' => $obj_user->data->display_name, ); } } return array( 'active' => $settings->enabled && count( $settings->user_roles ), 'enabled' => $settings->enabled, 'active_users' => $active_users, ); } /** * Builds an array of mask login data to be sent to the hub. * * @return array */ public function build_mask_login_hub_data(): array { $settings = new Mask_Login(); return array( 'active' => $settings->is_active(), 'masked_url' => $settings->mask_url, ); } /** * Builds an array of security headers data to be sent to the hub. * * @return array */ public function build_recaptcha_hub_data(): array { $settings = new Recaptcha(); return array( 'active' => $settings->is_active(), ); } /** * Builds an array of password protection data to be sent to the hub. * * @return array */ public function build_password_protection_hub_data(): array { $settings = new Password_Protection(); return array( 'active' => $settings->is_active(), ); } /** * Get the notification day. * * @param object $module_report The report object. * * @return string */ private function get_notification_day( $module_report ): string { if ( ! is_object( $module_report ) ) { return ''; } if ( 'daily' === $module_report->frequency ) { $day = '1'; } elseif ( 'weekly' === $module_report->frequency ) { $day = $module_report->day; } else { // For "monthly" frequency. $day = $module_report->day_n; } return $day; } /** * Frequency format: * if frequency is day, e.g.: 'frequency' => 1, 'day' => '1', 'time' => '20:30' * if frequency is week, e.g.: 'frequency' => 7, 'day' => 'wednesday', 'time' => '14:00' * if frequency is month, e.g.: 'frequency' => 30, 'day' => '4', 'time' => '4:30' * * @return array */ public function build_notification_hub_data(): array { $audit_settings = new Audit_Logging(); $audit_report = new Audit_Report(); $firewall_report = new Firewall_Report(); $malware_report = new Malware_Report(); $scan_settings = new Scan_Settings(); return array( 'file_scanning' => array( 'active' => true, // @since 2.7.0 move scheduled options to Scan settings, but we get status of Malware Scanning - Reporting here. 'enabled' => Notification::STATUS_ACTIVE === $malware_report->status, // Report enabled bool value. 'frequency' => array( 'frequency' => $this->backward_frequency_compatibility( $scan_settings->frequency ), 'day' => $this->get_notification_day( $scan_settings ), 'time' => $scan_settings->time, ), ), 'audit_logging' => array( 'active' => $audit_settings->is_active(), 'enabled' => Notification::STATUS_ACTIVE === $audit_report->status, 'frequency' => array( 'frequency' => $this->backward_frequency_compatibility( $audit_report->frequency ), 'day' => $this->get_notification_day( $audit_report ), 'time' => $audit_report->time, ), ), 'ip_lockouts' => array( // Always true as we have blacklist listening. 'active' => true, 'enabled' => Notification::STATUS_ACTIVE === $firewall_report->status, // Report enabled bool value. 'frequency' => array( 'frequency' => $this->backward_frequency_compatibility( $firewall_report->frequency ), 'day' => $this->get_notification_day( $firewall_report ), 'time' => $firewall_report->time, ), ), ); } /** * Build data for firewall notification. * * @return array */ public function build_firewall_notification_hub_data(): array { $firewall_notification = new Firewall_Notification(); if ( 'enabled' === $firewall_notification->status ) { $login_lockout = $firewall_notification->configs['login_lockout']; $nf_lockout = $firewall_notification->configs['nf_lockout']; $ua_lockout = $firewall_notification->configs['ua_lockout'] ?? false; } else { $login_lockout = false; $nf_lockout = false; $ua_lockout = false; } return array( 'firewall' => array( 'login_lockout' => $login_lockout, '404_lockout' => $nf_lockout, 'ua_lockout' => $ua_lockout, ), ); } /** * Build security headers hub data. * * @return array */ public function build_security_headers_hub_data(): array { $security_headers = wd_di()->get( Security_Headers::class )->get_type_headers(); return array( 'active' => $security_headers['active'], 'inactive' => $security_headers['inactive'], ); } /** * Build data to be sent to the hub. * * @return array */ public function build_stats_to_hub(): array { $scan_data = $this->build_scan_hub_data(); $tweaks_data = $this->build_security_tweaks_hub_data(); $audit_data = $this->build_audit_hub_data(); $firewall_data = $this->build_lockout_hub_data(); $two_fa = $this->build_2fa_hub_data(); $mask_login = $this->build_mask_login_hub_data(); $sec_headers = $this->build_security_headers_hub_data(); $recaptcha = $this->build_recaptcha_hub_data(); $pwned_password = $this->build_password_protection_hub_data(); $quarantined_files = $this->build_quarantined_files_hub_data(); return array( // Domain name. 'domain' => network_home_url(), // Last scan date. 'timestamp' => $scan_data['timestamp'], // Scan issue count. 'warnings' => $scan_data['warning'], // Security tweaks issue count. 'cautions' => $tweaks_data['cautions'], 'data_version' => wp_date( 'Ymd' ), 'scan_data' => wp_json_encode( array( 'scan_result' => $scan_data['scan_result'], 'hardener_result' => array( 'issues' => $tweaks_data[ Security_Tweaks::STATUS_ISSUES ], 'ignored' => $tweaks_data[ Security_Tweaks::STATUS_IGNORE ], 'resolved' => $tweaks_data[ Security_Tweaks::STATUS_RESOLVE ], ), 'scan_schedule' => $scan_data['scan_schedule'], 'audit_status' => array( 'events_in_month' => $audit_data['month'], 'audit_enabled' => $audit_data['enabled'], 'last_event_date' => $audit_data['last_event'], ), 'audit_page_url' => network_admin_url( 'admin.php?page=wdf-logging' ), 'labels' => array( // Todo: maybe should it remove because Scan Settings model has label() method for that? 'parent_integrity' => esc_html__( 'File change detection', 'defender-security' ), 'core_integrity' => esc_html__( 'Scan core files', 'defender-security' ), 'plugin_integrity' => esc_html__( 'Scan plugin files', 'defender-security' ), 'vulnerability_db' => esc_html__( 'Known vulnerabilities', 'defender-security' ), 'file_suspicious' => esc_html__( 'Suspicious code', 'defender-security' ), ), 'scan_page_url' => network_admin_url( 'admin.php?page=wdf-scan' ), 'hardener_page_url' => network_admin_url( 'admin.php?page=wdf-hardener' ), 'new_scan_url' => network_admin_url( 'admin.php?page=wdf-scan&wdf-action=new_scan' ), 'schedule_scans_url' => network_admin_url( 'admin.php?page=wdf-schedule-scan' ), 'settings_page_url' => network_admin_url( 'admin.php?page=wdf-settings' ), 'ip_lockout_page_url' => network_admin_url( 'admin.php?page=wdf-ip-lockout' ), 'last_lockout' => $firewall_data['last_lockout'], 'login_lockout_enabled' => $firewall_data['lp'], 'login_lockout' => $firewall_data['lp_week'], 'lockout_404_enabled' => $firewall_data['nf'], 'lockout_404' => $firewall_data['nf_week'], 'lockout_ua_enabled' => $firewall_data['ua'], 'lockout_ua' => $firewall_data['ua_week'], 'total_lockout' => (int) $firewall_data['lp_week'] + (int) $firewall_data['nf_week'] + (int) $firewall_data['ua_week'], 'global_ip_list_enabled' => $firewall_data['global_ip_list_enabled'], 'advanced' => array( // This is moved but still keep here for backward compatibility. 'multi_factors_auth' => array( 'active' => $two_fa['active'], 'enabled' => $two_fa['enabled'], 'active_users' => $two_fa['active_users'], ), 'mask_login' => array( 'activate' => $mask_login['active'], 'masked_url' => $mask_login['masked_url'], ), 'security_headers' => array( 'active' => $sec_headers['active'], 'inactive' => $sec_headers['inactive'], ), 'google_recaptcha' => array( 'active' => $recaptcha['active'], ), 'password_protection' => array( 'active' => $pwned_password['active'], ), ), 'reports' => $this->build_notification_hub_data(), 'notifications' => $this->build_firewall_notification_hub_data(), 'quarantined_files' => $quarantined_files, ) ), ); } /** * Checks whether we're on WPMU DEV Hosting. * * @return bool */ public function is_wpmu_hosting(): bool { return isset( $_SERVER['WPMUDEV_HOSTED'] ) && ! empty( $_SERVER['WPMUDEV_HOSTED'] ); } /** * Checks whether we're on The Free HUB. * * @return bool */ protected function is_tfh_account(): bool { return class_exists( 'WPMUDEV_Dashboard' ) && is_object( WPMUDEV_Dashboard::$api ) && method_exists( WPMUDEV_Dashboard::$api, 'get_membership_status' ) && 'free' === WPMUDEV_Dashboard::$api->get_membership_status(); } /** * Check if WPMUDEV Hosted site is connected to The Free HUB. * * @return bool * @since 3.3.0 */ public function is_hosted_site_connected_to_tfh(): bool { return $this->is_tfh_account() && $this->is_wpmu_hosting(); } /** * Check if a site from 3rd party hosting is connected to The Free HUB. * * @return bool * @since 3.6.0 */ public function is_another_hosted_site_connected_to_tfh(): bool { return $this->is_tfh_account() && ! $this->is_wpmu_hosting(); } /** * Build notification hub data. * * @return array */ protected function build_quarantined_files_hub_data(): array { if ( ! class_exists( 'WP_Defender\Component\Quarantine' ) ) { return array(); } return wd_di()->get( Quarantine::class )->hub_list(); } /** * Schedule Hub Synchronization event. */ public function schedule_hub_sync(): void { if ( ! wp_next_scheduled( 'defender_hub_sync' ) ) { wp_schedule_single_event( time(), 'defender_hub_sync' ); } } }