文件操作 - WPScan.php
返回文件管理
返回主菜单
删除本文件
文件: /home/www/public/wp-content/plugins/ionos-security/inc/Controllers/WPScan.php
编辑文件内容
<?php namespace Ionos\Security\Controllers; use Ionos\Librarysecurity\Options; use Ionos\Security\Controllers\Settings\SettingsWPScan; use Ionos\Security\Controllers\WPScan\Ajax; use Ionos\Security\Helper; /** * This is the controller for WPScan. * It initializes the settings, admins the cronjobs and serves data about plugins. */ class WPScan { /** * Option key for the WPScan data. */ const SCAN_DATA_OPTION = 'ionos_security_wpscan'; /** * The option for the auth token to talk to the middleware. */ const AUTH_TOKEN_OPTION = 'ionos_security_wpscan_token'; /** * The cronjob hook for the WPScan. */ const CRON_HOOK = 'ionos_security_wpscan'; /** * The URL to the middleware to get the WPScan data. Trailing slash is important. */ const MIDDLEWARE_URL = 'https://webapps-vuln-scan.hosting.ionos.com/api/v1/'; /** * The list of pages where the vulnerability notice should not be displayed. */ const IGNORE_PAGE_LIST = [ 'dashboard', 'ionos_page_ionos_security' ]; /** * The summary of the WPScan data for all plugins and themes. * * @var array */ private $summary = null; /** * The array with the all notice strings that are used. * * @var array */ private $notice_strings_array = []; /** * The instance of WPScan. * * @var object */ private static $instance = null; /** * Constructor of WPScan. */ private function __construct() { } /** * Get Instance function for WPScan. */ public static function get_instance() { if ( null === static::$instance ) { $instance = new self(); } return $instance; } /** * Initializes the settings, cronjob actions, etc. */ public function init() { if ( empty( get_option( self::AUTH_TOKEN_OPTION ) ) ) { return; } add_action( 'admin_init', [ __CLASS__, 'init_settings_builder' ], 210 ); add_action( self::CRON_HOOK, [ $this, 'ask_middleware' ] ); // spread wp scan scheduled events over 24h equally. if ( ! wp_next_scheduled( self::CRON_HOOK ) ) { wp_schedule_event( time() + wp_rand( 0, 24 * 60 * 60 ), 'daily', self::CRON_HOOK ); } if ( wp_doing_cron() && self::get_option( 'wpscan_mail_notification' ) ) { add_action( 'update_option_' . self::SCAN_DATA_OPTION, [ $this, 'handle_wpscan_data_update' ], 10, 3 ); } $this->set_notice_strings(); // add custom ajax actions. add_action( 'wp_ajax_scan-plugin', [ Ajax::class, 'ajax_scan' ] ); add_action( 'wp_ajax_scan-theme', [ Ajax::class, 'ajax_scan' ] ); add_action( 'wp_dashboard_setup', [ $this, 'add_dashboard_widget' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'load_scripts' ] ); add_action( 'admin_notices', [ $this, 'admin_notice' ] ); add_action( 'after_plugin_row', [ $this, 'after_plugin_row' ], 10, 3 ); // check if wpscan data needs to be updated. done every wp admin request, can be optimized. add_action( 'admin_init', [ $this, 'check_scan_result' ] ); // get the current controller instance. do_action( 'ionos_security_wpscan_init', $this ); } /** * Globalize all of the used strings for the notices. * * @return void */ private function set_notice_strings() { $this->notice_strings_array = [ 'none' => __( 'The vulnerability scan did not find any issues.', 'ionos-security' ), 'medium' => __( 'The vulnerability scan has found issues.', 'ionos-security' ), 'medium_activation' => __( 'Activation is not recommended.', 'ionos-security' ), 'high' => __( 'The vulnerability scan has found critical issues.', 'ionos-security' ), 'high_activation' => __( 'Activation is not possible.', 'ionos-security' ), 'addition' => __( 'Get here more information how to proceed.', 'ionos-security' ), 'link' => admin_url( 'admin.php?page=ionos_security&tab=wpscan' ), ]; } /** * Renders the update notice in the plugin. * * @see https://www.itsupportguides.com/knowledge-base/wordpress/using-wordpress-after_plugin_row-php-action/ * * @param string $plugin_file the plugin file. * @param string $plugin_data the plugin data. * @param string $status the plugin status. * * @return void */ public function after_plugin_row( $plugin_file, $plugin_data, $status ) { $plugin = $this->get_plugin( $plugin_file ); // Checks severity type the plugin. $severity = $plugin['severity'] ?? ''; // According to severity sets notice_class to 'notice-error' or 'notice-warning'. $notice_class = ( $severity === 'high' ) ? 'notice-error' : ( ( $severity === 'medium' ) ? 'notice-warning' : '' ); // According to severity sets text for notification. if ( $severity === 'high' || $severity === 'medium' ) { $notice_text = $this->notice_strings_array[ $severity ] ?? ''; printf( '<tr class="plugin-update-tr %s wpscan-notice"><td colspan="4" class="plugin-update colspanchange"><div class="update-message notice inline %s notice-alt">%s <a href="%s">%s</a></div></td></tr>', esc_attr( $this->get_plugin( $plugin_file )['status'] ), esc_attr( $notice_class ), esc_html( $notice_text ), esc_url( $this->notice_strings_array['link'] ), esc_html( $this->notice_strings_array['addition'] ) ); } } /** * Load the scripts for the WPScan page. * * @param string $hook_suffix the suffix of the hook. */ public function load_scripts( $hook_suffix ) { global $current_screen; $assets = include_once IONOS_SECURITY_DIR . '/build/global.asset.php'; wp_enqueue_script( 'ionos-security', IONOS_SECURITY_URL . 'build/global.js', $assets['dependencies'], $assets['version'], true ); wp_enqueue_style( 'ionos-security', IONOS_SECURITY_URL . 'build/global.css', [], $assets['version'] ); // Load the plugin scan scripts and styles. // Only load the plugin scan scripts and styles on the plugin install page. if ( $hook_suffix === 'plugin-install.php' ) { $assets = include_once IONOS_SECURITY_DIR . '/build/plugin-scan.asset.php'; wp_enqueue_script( 'ionos-security__plugin-scan', IONOS_SECURITY_URL . 'build/plugin-scan.js', $assets['dependencies'], $assets['version'], true ); wp_set_script_translations( 'ionos-security__plugin-scan', 'ionos-security', IONOS_SECURITY_DIR . '/languages' ); wp_enqueue_style( 'ionos-security__plugin-scan', IONOS_SECURITY_URL . 'build/plugin-scan.css', [], $assets['version'] ); wp_add_inline_script( 'ionos-security__plugin-scan', sprintf( 'const adminUrl=%s; window.wpScan=%s;', wp_json_encode( \admin_url( 'admin.php?page=ionos_security&tab=wpscan' ) ), wp_json_encode( $this->get_summary()['plugins'] ) ), 'before' ); } $assets = include_once IONOS_SECURITY_DIR . '/build/wpscan.asset.php'; wp_enqueue_style( 'ionos-security__settings-page', IONOS_SECURITY_URL . 'build/wpscan.css', [], $assets['version'] ); if ( $this->is_wpscan_settings_page() ) { add_thickbox(); wp_enqueue_script( 'plugin-install' ); wp_enqueue_script( 'ionos-security__settings-page', plugins_url( 'build/wpscan.js', IONOS_SECURITY_DIR . '/ionos-security.php' ), $assets['dependencies'], $assets['version'], true ); wp_set_script_translations( 'ionos-security__settings-page', 'ionos-security', IONOS_SECURITY_DIR . '/languages' ); wp_add_inline_script( 'ionos-security__settings-page', sprintf( 'wp.data.dispatch("ionos/wpscan").__initialize(%s);', wp_json_encode( [ 'summary' => $this->get_summary(), 'lastScan' => $this->get_last_scan(), ] ) ) ); } if ( $current_screen->id === 'themes' ) { if ( file_exists( IONOS_SECURITY_DIR . '/build/wpscan-themes.asset.php' ) ) { $assets = include_once IONOS_SECURITY_DIR . '/build/wpscan-themes.asset.php'; wp_enqueue_style( 'wpscan-themes', plugins_url( 'build/wpscan-themes.css', IONOS_SECURITY_DIR . '/ionos-security.php' ), [], $assets['version'] ); wp_enqueue_script( 'wpscan-themes', plugins_url( 'build/wpscan-themes.js', IONOS_SECURITY_DIR . '/ionos-security.php' ), [], $assets['version'], true ); wp_add_inline_script( 'wpscan-themes', sprintf( 'const adminUrl=%s; window.wpScan=%s;', wp_json_encode( \admin_url( 'admin.php?page=ionos_security&tab=wpscan' ) ), wp_json_encode( $this->get_summary()['themes'] ) ), 'before' ); } } if ( $current_screen->id === 'theme-install' ) { $assets = include_once IONOS_SECURITY_DIR . '/build/wpscan-theme-install.asset.php'; wp_enqueue_style( 'wpscan-theme-install', plugins_url( 'build/wpscan-theme-install.css', IONOS_SECURITY_DIR . '/ionos-security.php' ), [], $assets['version'] ); wp_enqueue_script( 'wpscan-theme-install', plugins_url( 'build/wpscan-theme-install.js', IONOS_SECURITY_DIR . '/ionos-security.php' ), [], $assets['version'], true ); wp_set_script_translations( 'wpscan-theme-install', 'ionos-security', IONOS_SECURITY_DIR . '/languages' ); wp_localize_script( 'wpscan-theme-install', 'horst', $this->get_summary()['themes'] ); wp_add_inline_script( 'wpscan-theme-install', sprintf( 'const adminUrl=%s; window.wpScan=%s;', wp_json_encode( \admin_url( 'admin.php?page=ionos_security&tab=wpscan' ) ), wp_json_encode( $this->get_summary()['themes'] ) ), 'before' ); } } /** * Check if the current page is the WPScan settings page. * * @return bool True if the current page is the WPScan settings page. */ public function is_wpscan_settings_page() { global $current_screen; if ( $current_screen->id !== Helper::get_settings_page() ) { return false; } if ( isset( $_GET['tab'] ) && $_GET['tab'] !== 'wpscan' ) { return false; } return true; } /** * Load the settings builder scripts. */ public static function init_settings_builder() { // avoid doing stuff not needed for ajax (...heartbeats). if ( wp_doing_ajax() ) { return; } $settings = new SettingsWPScan(); $settings->init( new self() ); } /** * Adds the WPScan dashboard widget. */ public function add_dashboard_widget() { wp_add_dashboard_widget( 'ionos-wpscan', __( 'Security', 'ionos-security' ), [ $this, 'dashboard_widget_content' ] ); } /** * Renders the WPScan dashboard widget. * * @param string|bool $data_object If template of dashboard widget should be applied. Is empty when coming from WP-Dashboard. */ public function dashboard_widget_content( $data_object ) { $summary = $this->get_summary(); if ( empty( $summary ) ) { return; } $summary['issues']['high'] = $summary['issues']['high'] ?? 0; $summary['issues']['medium'] = $summary['issues']['medium'] ?? 0; $args = [ 'summary' => $summary, 'last_scan' => $this->get_last_scan(), 'css_class' => ( $summary['issues']['high'] > 0 ) ? 'high' : ( ( $summary['issues']['medium'] > 0 ) ? 'medium' : 'none' ), 'high' => $summary['issues']['high'], 'medium' => $summary['issues']['medium'], 'report_url' => admin_url( 'admin.php?page=ionos_security&tab=wpscan' ), 'img_url' => IONOS_SECURITY_URL . 'assets/img/', ]; if ( $data_object === '' ) { load_template( IONOS_SECURITY_DIR . '/inc/Views/WPScan/dashboard.php', true, $args ); } else { return $args; } } /** * Returns a summary of the WPScan results. * This calls get_plugin() for each plugin. * * @return array Data for all plugins and their scores. Empty array if no data is available. */ public function get_summary() { // load plugin.php because not available in rest or cron request. if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( $this->summary !== null ) { return $this->summary; } if ( empty( \get_option( self::SCAN_DATA_OPTION ) ) ) { return []; } $data = [ 'themes' => [], 'plugins' => [], 'issues' => [], ]; $installed_plugins = \array_keys( \get_plugins() ); foreach ( $installed_plugins as $plugin ) { $plugin_data = $this->get_plugin( $plugin ); if ( empty( $plugin_data ) ) { continue; } $plugin_data['nonce'] = $this->get_nonce( $plugin_data ); \array_push( $data['plugins'], $plugin_data ); $data['issues'][ $plugin_data['severity'] ] = ( $data['issues'][ $plugin_data['severity'] ] ?? 0 ) + 1; } $installed_themes = wp_get_themes(); foreach ( array_keys( $installed_themes ) as $theme ) { $theme_data = $this->get_theme( $theme ); if ( empty( $theme_data ) ) { continue; } $theme_data['nonce'] = \wp_create_nonce( 'updates' ); \array_push( $data['themes'], $theme_data ); $data['issues'][ $theme_data['severity'] ] = ( $data['issues'][ $theme_data['severity'] ] ?? 0 ) + 1; } $this->summary = $data; return $data; } /** * Returns a nonce for the plugin. * * @param array $plugin_data The plugin data. * * @return string The nonce. */ private function get_nonce( $plugin_data ) { $nonce = 'updates'; if ( isset( $plugin_data['requested_version'], $plugin_data['latest_version'] ) && $plugin_data['requested_version'] === $plugin_data['latest_version'] ) { $nonce = 'deactivates'; } return \wp_create_nonce( $nonce ); } /** * Returns the last scan data. * * @return string The last scan data. */ public function get_last_scan() { $scan_data = get_option( self::SCAN_DATA_OPTION ); if ( $scan_data === false || ! isset( $scan_data['time'] ) ) { return __( 'unknown', 'ionos-security' ); } return human_time_diff( $scan_data['time'], time() ); } /** * Returns WPScan data for a specific plugin. * * @param string $file The plugin file for the plugin to get the data for. * * @return array Data for a plugin with score and details. Empty array if no data is available. */ public function get_plugin( $file ) { if ( empty( get_option( self::SCAN_DATA_OPTION ) ) ) { return []; } $plugin_data = get_plugin_data( WP_PLUGIN_DIR . "/$file" ); $slug = dirname( plugin_basename( WP_PLUGIN_DIR . "/$file" ) ); $plugin_name = $plugin_data['Name'] ?? $slug; $data = $this->read_plugin( $slug ); if ( empty( $data ) ) { return []; } // Only show info for the plugin, if data for that version is available. if ( $data['requested_version'] !== $plugin_data['Version'] ) { return []; } $score = $data['score'] ?? -1; $severity = $this->get_severity_by_score( $score ); $latest_version = $data['latest_version'] ?? __( 'unknown', 'ionos-security' ); $requested_version = $data['requested_version'] ?? __( 'unknown', 'ionos-security' ); $state = ( true === \is_plugin_active( $file ) ) ? 'active' : 'inactive'; $data = [ 'slug' => $slug, 'path' => $file, 'name' => $plugin_name, 'score' => $score, 'severity' => $severity, 'latest_version' => $latest_version, 'requested_version' => $requested_version, 'details' => '', 'status' => $state, ]; return $data; } /** * Returns WPScan data for a specific theme. * * @param string $name The name of the Theme. * * @return array Data for a theme with score and details. Empty array if no data is available. */ public function get_theme( $name ) { $data = get_option( self::SCAN_DATA_OPTION ); if ( $data === false || ! isset( $data['result'] ) ) { return false; } $data = $data['result']; if ( empty( $data ) ) { return []; } if ( json_last_error() !== JSON_ERROR_NONE ) { error_log( 'WPScan data is not valid JSON.' ); return false; } $installed_theme = wp_get_theme( $name ); $installed_theme_version = $installed_theme->get( 'Version' ); foreach ( $data['themes'] as $theme ) { if ( $theme['slug'] !== $name ) { continue; } if ( $theme['requested_version'] !== $installed_theme_version ) { return []; } $score = $this->get_score_by_extension( $theme ); $severity = $this->get_severity_by_score( $score ); $latest_version = $theme['latest_version'] ?? __( 'unknown', 'ionos-security' ); $requested_version = $theme['requested_version'] ?? __( 'unknown', 'ionos-security' ); $data = [ 'slug' => $name, 'name' => $installed_theme->get( 'Name' ), 'score' => $score, 'severity' => $severity, 'latest_version' => $latest_version, 'requested_version' => $requested_version, ]; return $data; } return []; } /** * Get the score of the extension. * Extension is a generic term for plugins and themes. * * @param array $extension The extension to get the score for. * * @return int|mixed */ private function get_score_by_extension( $extension ) { $score = strtolower( $extension['state'] ) === 'unknown' ? -1 : 0; return ! empty( $extension['vulnerabilities'] ) ? max( array_column( $extension['vulnerabilities'], 'score' ) ) : $score; } /** * Get the severity by the score. * * @param int $score The score of the plugin. * * @return string The severity of the plugin. */ private function get_severity_by_score( $score ) { switch ( true ) { case $score === - 1: $severity = 'unknown'; break; case $score === 0: $severity = 'none'; break; case $score < 7: $severity = 'medium'; break; default: $severity = 'high'; } return $severity; } /** * Renders the admin notice */ public function admin_notice() { global $current_screen; if ( ! isset( $current_screen->id ) || in_array( $current_screen->id, self::IGNORE_PAGE_LIST, true ) ) { return; } $summary = $this->get_summary(); if ( empty( $summary ) ) { return; } $summary['issues']['high'] = $summary['issues']['high'] ?? 0; $summary['issues']['medium'] = $summary['issues']['medium'] ?? 0; if ( $summary['issues']['high'] + $summary['issues']['medium'] === 0 ) { return; } $notice_class = ( $summary['issues']['high'] > 0 ) ? 'notice-error' : ( ( $summary['issues']['medium'] > 0 ) ? 'notice-warning' : '' ); $issue_num = $summary['issues']['high'] + $summary['issues']['medium']; $notice = sprintf( // translators: %s: issue count. _n( '%d issue found', '%d issues found', $issue_num, 'ionos-security' ), $issue_num ); $button = sprintf( ' <a href="%s">%s</a>', esc_url( admin_url( 'admin.php?page=ionos_security&tab=wpscan' ) ), esc_html__( 'More information.', 'ionos-security' ) ); printf( '<div class="ionos-wp-scan notice %s notice-alt"><p>%s: %s %s</p></div>', esc_attr( $notice_class ), esc_html__( 'Vulnerability Scan', 'ionos-security' ), esc_html( $notice ), $button // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } /** * Checks if the WPScan data is complete. * * @param array $data WPScan data. * * @return bool True if the data is complete, false otherwise. */ private function is_wpscan_result_complete( $data ) { $extensions = array_merge( $data['plugins'], $data['themes'] ); foreach ( $extensions as $extension ) { if ( strToLower( $extension['state'] ) === 'unknown' ) { return false; } } return true; } /** * Ask the middleware for the WPScan data. * and stores it as option. * * @param string $user_agent The user agent to use for the request to the middleware. */ public function ask_middleware( $user_agent = 'Security-Plugin' ) { if ( wp_doing_cron() ) { $user_agent = 'Security-Plugin Cron'; } // never spam the middleware. $scan_data = get_option( self::SCAN_DATA_OPTION ); if ( $scan_data !== false && isset( $scan_data['start_time'] ) && ( time() - $scan_data['start_time'] ) < 5 * 60 ) { return; } $scan_data['start_time'] = time(); update_option( self::SCAN_DATA_OPTION, $scan_data ); $token = get_option( self::AUTH_TOKEN_OPTION ); if ( empty( $token ) ) { error_log( 'No WPScan token set.' ); return; } $curl = curl_init(); curl_setopt_array( $curl, [ CURLOPT_URL => self::MIDDLEWARE_URL . 'vulnerabilities', CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => $this->get_my_info(), CURLOPT_HTTPHEADER => [ 'Accept: */json', 'Authorization: API-Key ' . $token, 'Content-Type: application/json', 'User-Agent: ' . $user_agent, ], ] ); $response = curl_exec( $curl ); $httpcode = curl_getinfo( $curl, CURLINFO_HTTP_CODE ); $err = curl_error( $curl ); curl_close( $curl ); $result = json_decode( $response, true ); if ( $err || $httpcode !== 200 || $result === null ) { error_log( 'Could not retrieve WPScan data' ); return; } $scan_data = [ 'result' => $result, 'time' => time(), 'scanned_versions' => $this->get_stringified_versions(), ]; update_option( self::SCAN_DATA_OPTION, $scan_data ); if ( ! $this->is_wpscan_result_complete( $result ) ) { wp_schedule_single_event( time() + ( HOUR_IN_SECONDS / 2 ), self::CRON_HOOK ); } } /** * Gathers information about the plugins and themes, that are installed. * * @return string JSON encoded information about the installed plugins and themes. */ private function get_my_info() { // see: https://developer.wordpress.org/reference/functions/get_plugins/#more-information . if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $info = [ 'coreVersion' => get_bloginfo( 'version' ), 'plugins' => [], 'themes' => [], ]; $installed_plugins = array_keys( get_plugins() ); foreach ( $installed_plugins as $plugin ) { $version = get_plugin_data( WP_PLUGIN_DIR . "/$plugin" )['Version']; if ( ! empty( $version ) ) { $info['plugins'][] = [ 'slug' => dirname( plugin_basename( WP_PLUGIN_DIR . "/$plugin" ) ), 'version' => $version, ]; } } $installed_themes = wp_get_themes(); foreach ( $installed_themes as $theme ) { $version = $theme->get( 'Version' ); if ( ! empty( $version ) ) { $info['themes'][] = [ 'slug' => $theme->get_stylesheet(), 'version' => $version, ]; } } return json_encode( $info ); } /** * Get the plugin data from the transient. * * @param string $slug The plugin slug. * * @return array|bool The plugin data or false. */ private function read_plugin( $slug ) { $data = get_option( self::SCAN_DATA_OPTION ); if ( $data === false || ! isset( $data['result'] ) ) { return false; } $data = $data['result']; if ( empty( $data ) ) { return false; } foreach ( $data['plugins'] as $plugin ) { if ( $plugin['slug'] === $slug ) { // There are no vulnerabilities for the plugin. // So the score is 0. if ( empty( $plugin['vulnerabilities'] ) ) { $plugin['score'] = 0; return $plugin; } // Find the highest score for the plugin. $highest_score = 0; foreach ( $plugin['vulnerabilities'] as $vulnerability ) { if ( $vulnerability['score'] > $highest_score ) { $highest_score = $vulnerability['score']; } } $plugin['score'] = $highest_score; return $plugin; } } return false; } /** * Stringify Vulnerabilities for easier diff. * loops the scan data for core, plugins and themes and extracts a string from all vulnerabilities. * * @param array $data the scan data array. * * @return array contains a unique string for each vulnerability. */ private function get_stringified_vulnerabilities( $data ) { $stringified_vulnerabilities = array_map( function ( $v ) { return 'core/' . $v['title'] . '/' . $v['score']; }, $data['core']['vulnerabilities'] ); foreach ( [ 'plugins', 'themes' ] as $extension_type ) { foreach ( $data[ $extension_type ] as $extension_data ) { array_push( $stringified_vulnerabilities, ...array_map( function ( $v ) use ( $extension_type, $extension_data ) { return $extension_type . '/' . $extension_data['slug'] . '/' . $v['title'] . '/' . $v['score']; }, $extension_data['vulnerabilities'] ) ); } } return $stringified_vulnerabilities; } /** * Compare old and new wpscan data for incoming plugin vulnerabilities. * Trigger action if something is found. * * @param string $old_data the last wp scan data. * @param string $new_data the current wp scan data. */ public function handle_wpscan_data_update( $old_data, $new_data ) { if ( empty( $old_data ) ) { return; } $old_stringified = $this->get_stringified_vulnerabilities( $old_data['result'] ); $new_stringified = $this->get_stringified_vulnerabilities( $new_data['result'] ); $new_vulnerability_found = ! empty( array_diff( $new_stringified, $old_stringified ) ); if ( $new_vulnerability_found ) { $this->send_vulnerability_email(); } } /** * Get simplified and stringified core, plugins, themes for easier diff. * * @return array * */ private function get_stringified_versions() { $stringified_software = [ 'core/' . get_bloginfo( 'version' ), ]; foreach ( wp_get_themes() as $slug => $data ) { $stringified_software[] = $slug . '/' . $data->get( 'Version' ); } foreach ( get_plugins() as $file => $data ) { $stringified_software[] = $file . '/' . $data['Version']; } return $stringified_software; } /** * Validate and if necessary refresh WP Scan Result Cache. * Done only on wpscan_settings */ public function check_scan_result() { if ( wp_doing_ajax() ) { return; } $scan_data = get_option( self::SCAN_DATA_OPTION ); if ( $scan_data === false ) { $this->ask_middleware( 'Security-Plugin Missing Data' ); return; } // if cron fails, this is the safe version to get data always. if ( ! isset( $scan_data['time'] ) || ( time() - $scan_data['time'] ) > DAY_IN_SECONDS ) { $this->ask_middleware( 'Security-Plugin Timeout Update' ); return; } if ( ! isset( $scan_data['scanned_versions'] ) ) { return; } $new_software = array_diff( $this->get_stringified_versions(), $scan_data['scanned_versions'] ); if ( ! empty( $new_software ) ) { $this->ask_middleware( 'Security-Plugin Software Update' ); } } /** * Sends an email if a vulnerability is found to the main admin user. * * @return void */ public function send_vulnerability_email() { $to = get_option( 'admin_email' ); $subject = __( 'Important Security Notification: Vulnerability detected in your WordPress website', 'ionos-security' ); $message = $this->get_mail_content(); $headers = [ 'Content-Type: text/html; charset=UTF-8' ]; wp_mail( $to, $subject, $message, $headers ); } /** * Merges the options passed as parameter with the existing options and updates them in database. * * @since 1.0.0 * * @param array $options The options to update. * * @return bool */ public static function set_options( $options ) { $merged_options = array_merge( self::get_options(), $options ); return update_option( 'ionos-security', $merged_options ); } /** * Gets plugins options. * * @since 1.0.0 * * @return array Array of option values */ public static function get_options() { return get_option( 'ionos-security', [] ); } /** * Gets a specific option. * * @since 1.0.0 * * @param string $option The option key. * * @return mixed|null */ public static function get_option( $option ) { $options = self::get_options(); if ( isset( $options[ $option ] ) ) { return $options[ $option ]; } return null; } /** * Gets options managed by the wpscan feature. * * @since 1.0.0 * * @return array Array of option values prefixed with wpscan_. */ public static function get_feature_options() { return array_filter( self::get_options(), /** * Filters options by prefix. * * @param string $option The option key. * * @return bool True if the option key starts with wpscan_, false otherwise. */ function ( $option_key ) { return strpos( $option_key, 'wpscan_' ) !== false; }, ARRAY_FILTER_USE_KEY ); } /** * Retuns the content of the mail. * * @return string */ private function get_mail_content() { $vulnerable_plugins = $this->get_extension_name_list_with_vulnerabilities(); $mail = '<p>' . __( 'Dear user,<br />We want to inform you that our recent vulnerability scan has detected one or more issues that require your attention:', 'ionos-security' ) . '</p>'; $mail .= '<p>'; foreach ( $vulnerable_plugins as $plugin ) { $mail .= '-' . $plugin . '<br>'; } $mail .= '</p>'; $mail .= '<p>' . __( 'To ensure the safety and security of your website, we recommend reviewing the findings of the scan and taking appropriate actions. For more detailed information about the specific vulnerabilities identified, please visit the following link to the Security plugin:', 'ionos-security' ); $mail .= '<a href="' . admin_url( 'admin.php?page=ionos_security&tab=wpscan' ) . '">' . admin_url() . '</a></p>'; $mail .= '<p>' . \sprintf( // Translators: %1$s and %2$s is a placeholder for link. __( 'Turn off this notification %1$shere%2$s.', 'ionos-security' ), '<a href="' . admin_url( 'admin.php?page=ionos_security&tab=wpscan' ) . '">', '</a>' ) . '</p>'; $mail .= \sprintf( // Translators: %s is the tenant name. __( 'Your %s security plugin team', 'ionos-security' ), \strtoupper( Options::get_tenant_name() ) ); return $mail; } /** * Collects all extensions with vulnerabilities. * * @return array */ private function get_extension_name_list_with_vulnerabilities() { $summary = $this->get_summary(); $vulnerable_items = []; foreach ( $summary as $items ) { foreach ( $items as $item ) { if ( isset( $item['score'] ) && (int) $item['score'] > 0 ) { $vulnerable_items[] = $item['name']; } } } return $vulnerable_items; } }
修改文件时间
将文件时间修改为当前时间的前一年
删除文件