Server IP : 172.67.165.3 / Your IP :
3.131.38.255 [
Web Server : LiteSpeed System : Linux altar63.supremepanel63.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 User : abranoticias ( 1103) PHP Version : 8.0.30 Disable Function : NONE Domains : 1 Domains MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /proc/178069/cwd/wp-content/plugins/wp-malware-removal/ |
Upload File : |
<?php /** * Malcure Malware Scanner & Security Hardening * * @package Malcure Malware Scanner & Security Hardening * @author Malcure * @copyright 2024 malcure.com * @license MIT * * @wordpress-plugin * Plugin Name: Malcure Malware Scanner & Security Hardening * Description: Ultra-precision, comphrensive malware scanner and security hardening to protect your site and find viruses, infections & other security threats & vulnerabilities. Detects over 50,000+ security threats & vulnerabilities. Do not forget to report bugs and share your reviews. * Version: 16.5 * Author: Malcure * Author URI: https://malcure.com * Text Domain: wp-malware-removal * License: MIT * License URI: https://opensource.org/licenses/MIT * Plugin URI: https://malcure.com/?p=116 */ /* ███╗ ███╗ █████╗ ██╗ ██████╗██╗ ██╗██████╗ ███████╗ ███╗ ███╗ █████╗ ██╗ ██╗ ██╗ █████╗ ██████╗ ███████╗ ███████╗ ██████╗ █████╗ ███╗ ██╗███╗ ██╗███████╗██████╗ ████╗ ████║██╔══██╗██║ ██╔════╝██║ ██║██╔══██╗██╔════╝ ████╗ ████║██╔══██╗██║ ██║ ██║██╔══██╗██╔══██╗██╔════╝ ██╔════╝██╔════╝██╔══██╗████╗ ██║████╗ ██║██╔════╝██╔══██╗ ██╔████╔██║███████║██║ ██║ ██║ ██║██████╔╝█████╗ ██╔████╔██║███████║██║ ██║ █╗ ██║███████║██████╔╝█████╗ ███████╗██║ ███████║██╔██╗ ██║██╔██╗ ██║█████╗ ██████╔╝ ██║╚██╔╝██║██╔══██║██║ ██║ ██║ ██║██╔══██╗██╔══╝ ██║╚██╔╝██║██╔══██║██║ ██║███╗██║██╔══██║██╔══██╗██╔══╝ ╚════██║██║ ██╔══██║██║╚██╗██║██║╚██╗██║██╔══╝ ██╔══██╗ ██║ ╚═╝ ██║██║ ██║███████╗╚██████╗╚██████╔╝██║ ██║███████╗ ██║ ╚═╝ ██║██║ ██║███████╗╚███╔███╔╝██║ ██║██║ ██║███████╗ ███████║╚██████╗██║ ██║██║ ╚████║██║ ╚████║███████╗██║ ██║ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ██████╗ ██╗ ██╗ ███████╗██╗ ██╗██╗██╗ ██╗ ██╔══██╗╚██╗ ██╔╝ ██╔════╝██║ ██║██║██║ ██║ █████╗ ██████╔╝ ╚████╔╝ ███████╗███████║██║██║ ██║ ╚════╝ ██╔══██╗ ╚██╔╝ ╚════██║██╔══██║██║╚██╗ ██╔╝ ██████╔╝ ██║ ███████║██║ ██║██║ ╚████╔╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝ */ if ( ! defined( 'ABSPATH' ) ) { exit; } define( 'WPMR_PLUGIN_DIR_URL', trailingslashit( plugin_dir_url( __FILE__ ) ) ); define( 'WPMR_SERVER', 'https://wp-malware-removal.com/' ); define( 'WPMR_PLUGIN', __FILE__ ); define( 'WPMR_PLUGIN_DIR', trailingslashit( __DIR__ ) ); if ( ! defined( 'MALCURE_API' ) ) { define( 'MALCURE_API', 'https://malcure.com' ); } if ( file_exists( WPMR_PLUGIN_DIR . 'inc/pro.php' ) ) { include_once WPMR_PLUGIN_DIR . 'inc/pro.php'; } final class WPMR_Init { private $definitions; private $timeout = 30; public $dir; public $url; private $mem = 256; private $maxsize = 11534336; // Ensure this is larger than the largest file inside WordPress private $cap = 'activate_plugins'; private $page_hooks = array(); public $plugin_data = false; // else php-8.2 warning private function __construct() { } static function get_instance() { static $instance = null; if ( is_null( $instance ) ) { $instance = new self(); $instance->init(); } return $instance; } function init() { $this->dir = trailingslashit( plugin_dir_path( $this->normalise_path( __FILE__ ) ) ); $this->url = trailingslashit( plugin_dir_url( __FILE__ ) ); register_deactivation_hook( WPMR_PLUGIN, array( $this, 'deactivate' ) ); if ( ! wp_next_scheduled( 'wpmr_daily' ) ) { wp_schedule_event( time(), 'daily', 'wpmr_daily' ); } if ( ! wp_next_scheduled( 'wpmr_hourly' ) ) { wp_schedule_event( time(), 'hourly', 'wpmr_hourly' ); } add_action( 'wpmr_daily', array( $this, 'check_definitions' ) ); add_action( 'wpmr_hourly', array( $this, 'automate_routines' ) ); add_action( 'upgrader_process_complete', array( $this, 'delete_core_checksums' ), 9999, 2 ); add_action( 'init', array( $this, 'set_plugin_data' ) ); add_action( 'admin_init', array( $this, 'save_gsc_profile' ) ); add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'network_admin_notices', array( $this, 'admin_notice' ) ); add_action( 'admin_notices', array( $this, 'admin_notice' ) ); add_filter( 'plugin_action_links_' . plugin_basename( WPMR_PLUGIN ), array( $this, 'plugin_action_links' ) ); add_filter( 'plugin_row_meta', array( $this, 'plugin_meta_links' ), 10, 2 ); add_action( 'admin_enqueue_scripts', array( $this, 'wpmr_admin_styles' ) ); add_action( 'wp_ajax_wpmr_ajax_request', array( $this, 'wpmr_ajax_request' ) ); add_action( 'wp_ajax_nopriv_wpmr_ajax_request', array( $this, '__return_false' ) ); add_action( 'wp_ajax_wpmr_get_stats', array( $this, 'wpmr_get_stats' ) ); add_action( 'wp_ajax_nopriv_wpmr_get_stats', '__return_false' ); add_action( 'wp_ajax_wpmr_init_scan', array( $this, 'wpmr_init_scan' ) ); add_action( 'wp_ajax_wpmr_scan_db', array( $this, 'wpmr_scan_db' ) ); add_action( 'wp_ajax_wpmr_scan_files', array( $this, 'wpmr_scan_files' ) ); add_action( 'wp_ajax_wpmr_clean_file', array( $this, 'wpmr_clean_file' ) ); add_action( 'wp_ajax_wpmr_delete_file', array( $this, 'wpmr_delete_file' ) ); add_action( 'wp_ajax_wpmr_whitelist_file', array( $this, 'wpmr_whitelist_file' ) ); add_action( 'wp_ajax_wpmr_unwhitelist_file', array( $this, 'wpmr_unwhitelist_file' ) ); add_action( 'wp_ajax_wpmr_inspect_file', array( $this, 'wpmr_inspect_file' ) ); add_action( 'wp_ajax_wpmr_clear_infection_stats', array( $this, 'wpmr_clear_infection_stats' ) ); add_action( 'wp_ajax_wpmr_update_sigs', array( $this, 'update_definitions' ) ); add_action( 'wp_ajax_wpmr_reset', array( $this, 'reset' ) ); add_action( 'wp_ajax_nopriv_wpmr_reset', '__return_false' ); add_action( 'wp_ajax_wpmr_web_register', array( $this, 'wpmr_web_register' ) ); add_action( 'wp_ajax_wpmr_def_auto_update_enabled', array( $this, 'update_wpmr_def_auto_update' ) ); add_action( 'wp_ajax_nopriv_wpmr_inspect_file', '__return_false' ); add_action( 'wp_ajax_nopriv_wpmr_clear_infection_stats', '__return_false' ); add_action( 'wp_ajax_nopriv_wpmr_clean_file', '__return_false' ); add_action( 'wp_ajax_nopriv_wpmr_delete_file', '__return_false' ); add_action( 'wp_ajax_nopriv_wpmr_whitelist_file', '__return_false' ); add_action( 'wp_ajax_nopriv_wpmr_unwhitelist_file', '__return_false' ); add_action( 'wp_ajax_nopriv_wpmr_check_files', '__return_false' ); add_action( 'wp_ajax_nopriv_wpmr_update_sigs', '__return_false' ); add_action( 'wp_ajax_nopriv_wpmr_web_register', '__return_false' ); add_action( 'wp_ajax_nopriv_wpmr_def_auto_update_enabled', '__return_false' ); add_action( 'admin_footer', array( $this, 'scripts' ) ); add_action( 'network_admin_menu', array( $this, 'add_admin_pages' ) ); add_action( 'admin_menu', array( $this, 'add_admin_pages' ) ); add_action( 'add_meta_boxes', array( $this, 'remove_metaboxes' ) ); add_action( 'check_ajax_referer', array( $this, 'prevent_meta_box_order' ) ); add_filter( 'hidden_meta_boxes', array( $this, 'no_hidden_meta_boxes' ), 10, 3 ); add_filter( 'get_user_metadata', array( $this, 'malcure_prevent_meta_box_order_retrieval' ), 10, 5 ); add_action( 'admin_head', array( $this, 'wpmr_add_admin_inline_styles' ) ); add_action( 'in_plugin_update_message-' . basename( WPMR_PLUGIN_DIR ) . '/' . basename( WPMR_PLUGIN ), array( $this, 'plugin_update_message' ), 10, 2 ); add_action( 'plugins_loaded', array( $this, 'waf' ), -1 ); add_action( 'wp_dashboard_setup', array( $this, 'dashboard_widget' ), 1 ); add_filter( 'postbox_classes_toplevel_page_wpmr_wpmr_updates_box', array( $this, 'prompt_register' ) ); add_filter( 'admin_body_class', array( $this, 'admin_body_classes' ) ); add_filter( 'serve_checksums', array( $this, 'map_core_checksums' ), 10 ); // order is important. Always before we serve checksum_cache add_filter( 'serve_checksums', array( $this, 'get_cached_checksums' ), 11 ); add_filter( 'serve_checksums', array( $this, 'whitelist' ), 9999 ); add_filter( 'wpmr_skip_dir', array( $this, 'wpmr_skip_dir' ) ); add_action( 'wpmr_diagnostics_row', array( $this, 'malcure_user_sessions' ) ); // Events // Updates add_action( 'automatic_updates_complete', array( $this, 'log_automatic_update' ) ); add_action( 'upgrader_process_complete', array( $this, 'log_update_event' ), 10, 2 ); // Switching add_action( 'activated_plugin', array( $this, 'log_plugin_toggle' ), 10, 2 ); add_action( 'deactivated_plugin', array( $this, 'log_plugin_toggle' ), 10, 2 ); add_action( 'switch_theme', array( $this, 'log_theme_activation' ) ); // Deletion add_action( 'delete_plugin', array( $this, 'log_plugin_deletion' ) ); add_action( 'deleted_theme', array( $this, 'log_theme_deletion' ) ); // Files add_action( 'edit_file', array( $this, 'log_file_edit' ), 10, 2 ); add_filter( 'wp_handle_upload', array( $this, 'log_file_upload' ) ); add_action( 'add_attachment', array( $this, 'log_add_attachment' ) ); // Users add_action( 'user_register', array( $this, 'log_user_creation' ) ); add_action( 'profile_update', array( $this, 'log_user_update' ), 10, 2 ); add_action( 'set_user_role', array( $this, 'log_user_role_change' ), 10, 2 ); add_action( 'login_form_resetpass', array( $this, 'log_password_reset_attempt' ) ); add_action( 'delete_user', array( $this, 'log_user_deletion' ) ); add_action( 'add_user_to_blog', array( $this, 'log_add_user_to_blog' ), 10, 3 ); // User attempts add_action( 'wp_login_failed', array( $this, 'log_failed_login' ) ); add_action( 'retrieve_password', array( $this, 'log_password_reset_request' ) ); add_action( 'wp_login', array( $this, 'log_successful_login' ), 10, 2 ); // XMLRPC add_action( 'xmlrpc_publish_post', array( $this, 'log_xmlrpc_publish_post' ) ); // Scanning add_action( 'wpmr_scan_init', array( $this, 'log_malware_scan_start' ) ); } function malcure_prevent_meta_box_order_retrieval( $value, $user_id, $meta_key, $single, $meta_type ) { if ( 'user' === $meta_type ) { if ( strpos( $meta_key, 'wpmr' ) !== false ) { // Check if 'wpmr' exists in the meta key return ''; // Prevent metadata retrieval } } return $value; } function no_hidden_meta_boxes( $hidden, $screen, $use_defaults ) { if ( preg_match( '/wpmr/', $screen->id ) ) { return array(); // No hidden meta boxes } return $hidden; } function prevent_meta_box_order( $action ) { if ( ( 'meta-box-order' == $action || 'closedpostboxes' == $action ) && preg_match( '/wpmr/', $_REQUEST['page'] ) ) { die( 'Nope!' ); } } function automate_routines() { if ( ! ( defined( 'DOING_CRON' ) && DOING_CRON ) || ! $this->is_advanced_edition() ) { return; } $check = $this->check_definitions(); $updates = $this->definition_updates_available(); if ( $updates && $this->get_setting( 'def_auto_update_enabled' ) ) { $update = $this->update_definitions_cli( true ); } $this->checksums_delete_invalid(); } function malcure_user_sessions() { ?> <tr><th>Logged-In Users:</th><td> <?php submit_button( 'Logout All Users', 'primary', 'malcure_destroy_sessions', false ); submit_button( 'Shuffle WordPress Salts', 'primary', 'malcure_shuffle_salts', false ); $users = $this->get_users_loggedin(); $total_users = count( $users ); // Fetch only the first 25 users $display_users = array_slice( $users, 0, 25 ); // Display heading when users are skipped if ( $total_users > count( $display_users ) ) { $skipped_users = $total_users - count( $display_users ); echo '<h3>Showing ' . esc_html( count( $display_users ) ) . ' of ' . esc_html( $total_users ) . ' logged-in users; ' . esc_html( $skipped_users ) . ' users skipped.</h3>'; } foreach ( $display_users as $user ) { echo '<table class="user_details" id="user_details_' . $user->ID . '">'; echo '<tr><th class="user_details_id">User ID</th><td>' . $user->ID . '</td></tr>'; echo '<tr><th class="user_details_roles">User Roles</th><td>' . implode( ',', $user->roles ) . '</td></tr>'; echo '<tr><th class="user_details_user_login">User Login</th><td>' . $user->user_login . '</td></tr>'; echo '<tr><th class="user_details_user_email">User Email</th><td>' . $user->user_email . '</td></tr>'; echo '<tr><th class="user_details_display_name">Display Name</th><td>' . $user->display_name . '</td></tr>'; echo '<tr><th class="user_details_user_registered">Registered</th><td>' . gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $user->user_registered ) ) . '</td></tr>'; $s_details = ''; $s_details = get_user_meta( $user->ID, 'session_tokens', true ); echo '<tr><th class="wpmr_user_details_session_ip">Sessions</th><td>'; foreach ( $s_details as $s_detail ) { echo '<table class="wpmr_user_details_session">'; echo '<tr><th class="wpmr_user_details_session_ip">IP Address</th><td>' . $s_detail['ip'] . '</td></tr>'; // $hostname = gethostbyaddr( $s_detail['ip'] ); // if ( $hostname && $hostname !== $s_detail['ip'] ) { // echo '<tr><th class="wpmr_user_details_session_hostname">Hostname</th><td>' . esc_html( $hostname ) . '</td></tr>'; // } echo '<tr><th class="wpmr_user_details_session_ua">User-Agent</th><td>' . $s_detail['ua'] . '</td></tr>'; echo '<tr><th class="wpmr_user_details_session_login">Session Start</th><td>' . gmdate( 'Y-m-d\TH:i:s\Z', $s_detail['login'] ) . '</td></tr>'; echo '<tr><th class="wpmr_user_details_session_expiration">Session Expiration</th><td>' . gmdate( 'Y-m-d\TH:i:s\Z', $s_detail['expiration'] ) . '</td></tr>'; echo '</table>'; } echo '</td></tr>'; echo '</table>'; } ?> </td></tr> <?php } function destroy_sessions( $id = false ) { if ( ! $this->is_advanced_edition() ) { return 'Advanced features are only available in Malcure Advanced Edition.'; } $users = $this->get_users_loggedin(); if ( ! $id ) { return; } foreach ( $users as $user ) { $sessions = WP_Session_Tokens::get_instance( $user->ID ); if ( $user->ID != $id ) { $sessions->destroy_all(); } else { $sessions->destroy_others( wp_get_session_token() ); } } // wp_send_json_success(); } function get_users_loggedin() { return get_users( array( 'meta_key' => 'session_tokens', 'meta_compare' => 'EXISTS', ) ); } function admin_body_classes( $classes ) { $screen = get_current_screen(); if ( preg_match( '/_page.*wpmr/', $screen->id ) ) { $classes .= ' malcure '; $skin = sanitize_html_class( $this->get_setting( 'wpmr_skin' ) ); if ( ! empty( $skin ) ) { $classes .= " malcure_skin_$skin "; } else { $classes .= ' malcure_skin_classic '; } if ( $this->get_setting( 'infected' ) ) { $classes .= ' malcure-infected '; } if ( $this->is_advanced_edition() ) { $classes .= ' malcure_pro '; } } return $classes; } function prompt_register( $classes ) { if ( ! $this->is_registered() ) { array_push( $classes, 'prompt_register' ); } return $classes; } function dashboard_widget() { if ( ! current_user_can( $this->cap ) ) { return; } add_meta_box( 'malcure', 'Malware Status', array( $this, 'malcure_dashboard_widget' ), 'dashboard', 'normal', 'high' ); } function render_branding() { $skin = $this->get_setting( 'wpmr_skin' ); if ( $skin == 'dark' ) { echo '<img src="' . $this->url . 'assets/logo-dark-trans.svg" />'; } else { echo '<img src="' . $this->url . 'assets/logo-light-trans.svg" />'; } } function malcure_dashboard_widget() { $attacks = (int) $this->get_setting( 'attacks' ); if ( ! empty( $attacks ) ) { echo '<p><span class="brandname">Malcure</span> has prevented ' . $attacks . ' attacks till date.</p>'; } $infected = $this->get_setting( 'infected' ); $this->render_branding(); if ( $this->is_advanced_edition() ) { ?> <div class="malcure_pro_info" class="licensed"> <h3 id="heading">You are donning Malcure Advanced Edition!</h3> </div> <?php } if ( $infected ) { ?> <p class="infected"><?php _e( '<strong>Your Website Is Infected with Malware. <a href="' . get_admin_url( null, 'admin.php?page=wpmr' ) . '">Kindly clean-up your website and fix this issue at the earliest →</a></strong>', 'wp-malware-removal' ); ?></p> <p><a href="https://malcure.com/?p=107&utm_source=adminnotice&utm_medium=web&utm_campaign=wpmr" title="If you are stuck or need a professional to resolve the malware issue, you can avail Malcure's WordPress Malware Removal Service." target="_blank" class="button-primary" style="font-weight: 600;" >Request Malware Cleanup →</a></p> <?php } else { ?> <p><?php _e( '<strong>No infections detected so far. <a href="' . get_admin_url( null, 'admin.php?page=wpmr' ) . '">Kindly scan your website to be sure →</a></strong>', 'wp-malware-removal' ); ?></p> <?php } } function definition_updates_available() { $current = $this->get_definition_version(); $new = $this->get_setting( 'update-version' ); if ( $current != $new ) { return array( 'new' => $new, 'current' => $current, ); } } function wpmr_clear_infection_stats() { check_ajax_referer( 'wpmr_clear_infection_stats', 'wpmr_clear_infection_stats_nonce' ); $this->delete_setting( 'infected' ); wp_send_json( $_REQUEST ); } function raise_limits_conditionally() { if ( strpos( ini_get( 'disable_functions' ), 'ini_set' ) === false ) { if ( function_exists( 'memory_get_usage' ) && ( (int) @ini_get( 'memory_limit' ) < $this->mem ) ) { @ini_set( 'memory_limit', $this->mem . 'M' ); } } if ( defined( 'WP_CLI' ) && WP_CLI ) { // Do WP-CLI specific things. @ini_set( 'max_execution_time', 0 ); } else { @ini_set( 'max_execution_time', max( (int) @ini_get( 'max_execution_time' ), 90 ) ); } } function admin_notice() { if ( ! current_user_can( $this->cap ) ) { return; } $screen = get_current_screen(); if ( $this->get_setting( 'infected' ) ) { ?> <div class="notice notice-error is-dismissible" id="wpmr-infected-alert"> <p><?php _e( '<strong>Your Website Is Infected with Malware.</strong> <a href="' . get_admin_url( null, 'admin.php?page=wpmr' ) . '">Please scan again and clean-up your website to fix this issue at the earliest →</a> <em>This message will self-resolve once the scan comes up clean.</em> <a href="https://malcure.com/?p=107&utm_source=adminnotice&utm_medium=web&utm_campaign=wpmr" target="_blank" class="malcure-button-primary button-primary" title="If you are stuck or need a professional to resolve the malware issue, you can avail Malcure\'s WordPress Malware Removal Service.">Request Malware Cleanup →</a>', 'wp-malware-removal' ); ?></p> </div> <?php } $setup_awaited = ( ! $this->is_registered() ); if ( $setup_awaited ) { ?> <div class="notice notice-success"> <p><?php _e( '<strong>Malcure Malware Scanner & Security Hardening</strong> is installed and ready to go.<br /><br /><a class="button-primary" href="' . get_admin_url( null, 'admin.php?page=wpmr' ) . '">Connect to Malcure API Server →</a>', 'wp-malware-removal' ); ?> </p> </div> <?php } $updates = $this->definition_updates_available(); if ( $updates ) { if ( $this->is_advanced_edition() && $this->get_setting( 'def_auto_update_enabled' ) ) { echo '<div class="notice notice-warning"><p>'; $update = $this->update_definitions_cli( true ); echo '</p></div>'; } else { $ae = $this->is_advanced_edition(); $ae = empty( $ae ) ? '<br /><br /><a target="_blank" class="malcure-button-primary button-primary" href="https://malcure.com/?p=116&utm_source=definition-update-notice&utm_medium=web&utm_campaign=wpmr">Upgrade to Malcure Advanced Edition for automatic definition-updates →</a>' : ''; ?> <div class="notice notice-warning" id="wpmr_new_def_alert"> <p><?php _e( '<strong>Malcure Malware Scanner:</strong> New Definition Updates Are Available. You have version ' . $updates['current'] . ' Latest version is ' . $updates['new'] . ' <a href="' . get_admin_url( null, 'admin.php?page=wpmr' ) . '#wpmr_updates_box"><strong>Update Now!</strong></a>' . $ae, 'wp-malware-removal' ); ?> </p> </div> <?php } } $screen = get_current_screen(); if ( preg_match( '/wpmr_hardening/', $screen->id ) ) { $attacks = (int) $this->get_setting( 'attacks' ); if ( ! empty( $attacks ) ) { ?> <div class="notice notice-success"> <p><?php _e( '<strong>Malcure Security Hardening:</strong> Prevented <strong>' . $attacks . '</strong> attacks till date.' ); ?> </p> </div> <?php } else { ?> <div class="notice notice-success"> <p><?php _e( '<strong>Malcure Security Hardening:</strong> Zero attacks till date... Malcure is on the watch!' ); ?> </p> </div> <?php } } if ( $this->is_advanced_edition_expired() ) { $link = 'https://malcure.com/?p=168&edd_license_key=' . $this->get_setting( 'license_key' ) . '&edd_action=apply_license_renewal&utm_source=pluginexpired_adminnotice&utm_medium=web&utm_campaign=wpmr'; ?> <div class="notice notice-warning is-dismissible" id="wpmr-expired-alert"> <p><?php _e( '<strong>ALERT!</strong> Your Malcure License Key has expired, putting your site\'s security at risk! <a href="' . $link . '" target="_blank" class="malcure-button-primary button-primary" title="Renew your Malcure license now to protect your site.">Renew License Now →</a>', 'wp-malware-removal' ); ?></p> </div> <?php } } function screen_obj_fix( $title, $raw_title, $context ) { $title = trim( preg_replace( '/<[^>]*?>.*?<\/[^>]*?>/si', '', $title ) ); return $title; } function add_admin_pages() { add_filter( 'sanitize_title', array( $this, 'screen_obj_fix' ), 9, 3 ); $mtitle = empty( $this->definition_updates_available() ) ? 'Malcure' : 'Malcure <span class="awaiting-mod">1</span>'; $smtitle = empty( $this->definition_updates_available() ) ? 'Malware Scanner' : 'Malware Scanner <span class="awaiting-mod">1</span>'; $hook_suffix = add_menu_page( 'Malcure', $mtitle, $this->cap, 'wpmr', array( $this, 'scanner_page' ), $this->url . 'assets/icon-dark-trans.svg', 79 ); $this->page_hooks['top'] = $hook_suffix; $attacks = $this->get_setting( 'attacks' ); $attacks = empty( $attacks ) ? '' : ' <span class="awaiting-mod">' . $attacks . '</span>'; $hook_suffix = add_submenu_page( 'wpmr', 'Malcure Malware Scanner', $smtitle, $this->cap, 'wpmr', array( $this, 'scanner_page' ) ); $this->page_hooks['first'] = $hook_suffix; $hook_suffix = add_submenu_page( 'wpmr', 'Malcure Security Hardening', 'Security Hardening' . $attacks, $this->cap, 'wpmr_hardening', array( $this, 'wpmr_hardening_page' ) ); $this->page_hooks['hardening'] = $hook_suffix; $hook_suffix = add_submenu_page( 'wpmr', 'Malcure Event Monitor', 'Event Monitor', $this->cap, 'wpmr_logs', array( $this, 'wpmr_logs_page' ) ); $this->page_hooks['log'] = $hook_suffix; $hook_suffix = add_submenu_page( 'wpmr', 'Malcure License Key', 'License Key', $this->cap, 'wpmr_license', array( $this, 'wpmr_license_page' ) ); $this->page_hooks['license'] = $hook_suffix; $hook_suffix = add_submenu_page( 'wpmr', 'Malcure Frequently Asked Questions', 'Help & FAQs', $this->cap, 'wpmr_help', array( $this, 'wpmr_help_page' ) ); $this->page_hooks['faq'] = $hook_suffix; foreach ( $this->page_hooks as $key => $hook ) { add_action( 'load-' . $hook, array( $this, 'setup_screen_for_pages' ) ); // This should go before trigger_action_metaboxes_hook } remove_filter( 'sanitize_title', array( $this, 'screen_obj_fix' ), 9 ); } function setup_screen_for_pages() { $screen = get_current_screen(); if ( $screen && preg_match( '/wpmr/', $screen->id ) ) { add_action( 'admin_print_scripts-' . str_replace( '-network', '', $screen->id ), array( $this, 'wpmr_enqueue_js_dependencies' ) ); } foreach ( $this->page_hooks as $key => $hook ) { if ( $key == 'top' || $key == 'first' ) { // $hook = toplevel_page_wpmr add_meta_box( 'wpmr_results_box', 'Status Details', array( $this, 'meta_box_results' ), $hook, 'main', 'high' ); add_meta_box( 'wpmr_debug_box', 'Advanced Options', array( $this, 'meta_box_pro' ), $hook, 'main', 'high' ); add_meta_box( 'wpmr_inspect_box', 'Malware Inspector & Cleanup Operations', array( $this, 'meta_box_inspect' ), $hook, 'main', 'high' ); add_meta_box( 'wpmr_diagnostics_box', 'System Status', array( $this, 'meta_box_diagnostics' ), $hook, 'main', 'high' ); add_meta_box( 'wpmr_updates_box', 'Updates', array( $this, 'meta_box_updates' ), $hook, 'side', 'high' ); add_meta_box( 'wpmr_ad_box', 'Malcure Advanced Edition', array( $this, 'wpmr_ad_common' ), $hook, 'side', 'high' ); add_meta_box( 'wpmr_results_box', 'Status Details', array( $this, 'meta_box_results' ), $hook . '-network', 'main', 'high' ); add_meta_box( 'wpmr_debug_box', 'Advanced Options', array( $this, 'meta_box_pro' ), $hook . '-network', 'main', 'high' ); add_meta_box( 'wpmr_inspect_box', 'Malware Inspector & Cleanup Operations', array( $this, 'meta_box_inspect' ), $hook . '-network', 'main', 'high' ); add_meta_box( 'wpmr_diagnostics_box', 'System Status', array( $this, 'meta_box_diagnostics' ), $hook . '-network', 'main', 'high' ); add_meta_box( 'wpmr_updates_box', 'Updates', array( $this, 'meta_box_updates' ), $hook . '-network', 'side', 'high' ); add_meta_box( 'wpmr_ad_box', 'Malcure Advanced Edition', array( $this, 'wpmr_ad_common' ), $hook . '-network', 'side', 'high' ); } if ( $key == 'hardening' ) { // $hook = malcure_page_wpmr_hardening add_meta_box( 'wpmr_hardening_box', 'Malcure Security Hardening Settings', array( $this, 'meta_box_hardening' ), $hook, 'main', 'high' ); add_meta_box( 'wpmr_ad_box', 'Malcure Advanced Edition', array( $this, 'wpmr_ad_common' ), $hook, 'side', 'high' ); add_meta_box( 'wpmr_hardening_box', 'Malcure Security Hardening Settings', array( $this, 'meta_box_hardening' ), $hook . '-network', 'main', 'high' ); add_meta_box( 'wpmr_ad_box', 'Malcure Advanced Edition', array( $this, 'wpmr_ad_common' ), $hook . '-network', 'side', 'high' ); } if ( $key == 'log' ) { // $hook = malcure_page_wpmr_logs add_meta_box( 'wpmr_events_box', 'Malcure Event Log', array( $this, 'meta_box_events' ), $hook, 'main', 'high' ); add_meta_box( 'wpmr_logs_box', 'Malcure Scan Log', array( $this, 'meta_box_logs' ), $hook, 'main', 'high' ); add_meta_box( 'wpmr_ad_box', 'Malcure Advanced Edition', array( $this, 'wpmr_ad_common' ), $hook, 'side', 'high' ); add_meta_box( 'wpmr_events_box', 'Malcure Event Log', array( $this, 'meta_box_events' ), $hook . '-network', 'main', 'high' ); add_meta_box( 'wpmr_logs_box', 'Malcure Scan Log', array( $this, 'meta_box_logs' ), $hook . '-network', 'main', 'high' ); add_meta_box( 'wpmr_ad_box', 'Malcure Advanced Edition', array( $this, 'wpmr_ad_common' ), $hook . '-network', 'side', 'high' ); } if ( $key == 'license' ) { // $hook = meta_box_license add_meta_box( 'wpmr_license_box', 'Please Enter Your Malcure License Key Here', array( $this, 'meta_box_license' ), $hook, 'main', 'high' ); add_meta_box( 'wpmr_ad_box', 'Malcure Advanced Edition', array( $this, 'wpmr_ad_common' ), $hook, 'side', 'high' ); add_meta_box( 'wpmr_license_box', 'Please Enter Your Malcure License Key Here', array( $this, 'meta_box_license' ), $hook . '-network', 'main', 'high' ); add_meta_box( 'wpmr_ad_box', 'Malcure Advanced Edition', array( $this, 'wpmr_ad_common' ), $hook . '-network', 'side', 'high' ); } if ( $key == 'faq' ) { // $hook = malcure_page_wpmr_help add_meta_box( 'wpmr_faq_box', 'Frequently Asked Questions', array( $this, 'meta_box_faq' ), $hook, 'main', 'high' ); add_meta_box( 'wpmr_ad_box', 'Malcure Advanced Edition', array( $this, 'wpmr_ad_common' ), $hook, 'side', 'high' ); add_meta_box( 'wpmr_faq_box', 'Frequently Asked Questions', array( $this, 'meta_box_faq' ), $hook . '-network', 'main', 'high' ); add_meta_box( 'wpmr_ad_box', 'Malcure Advanced Edition', array( $this, 'wpmr_ad_common' ), $hook . '-network', 'side', 'high' ); // add_meta_box( 'wpmr_test', 'Test', array( $this, 'meta_box_logs_test' ), $hook, 'side', 'high' ); } } } function scanner_page() { $screen = get_current_screen(); $this->get_checksums(); $this->maybe_load_default_definitions(); $this->check_definitions(); ?> <div class="wrap"> <h1 id="page_title">Malcure Malware Scanner</h1> <div id="dashboard_wrap"> <table id="ui_container"> <tr> <td class="col_first"><span id="logo"></span></td> <td rowspan="2" id="speedo"><div id="dial"> <div class="gauge_a"></div> <div class="gauge_c"></div> <div class="gauge_data"> <h1 id="percent"></h1> </div> </div> <div id="controls"> <div id="file_scroll"></div> <div id="wpmr_batchsize_wrap"><input title="Your webhost may block too many requests. Be careful." type="range" id="wpmr_batchsize" value="11" name="points" min="1" max="100" /></div> <p id="scan_hint" title="Your webhost may block too many requests. Be careful.">Scan <span id="scan_hint_value">5</span> Items per req.</p> <p id="scan_controls"><input title="Start Malware Scan" type="submit" value="Initiate DeepScan™→" id="scan_control_deep" data-state="0" class="malcure-button-primary scan_control" /></p> </div> </td> <td id="wpmr_skinner_container"> <div id="wpmr_skinner_wrap"> <p>Interface :</p> <?php $skin = $this->get_setting( 'wpmr_skin' ); ?> <select id="wpmr_skin"> <option <?php selected( $skin, 'classic' ); ?> value="classic">Classic</option> <option <?php selected( $skin, 'dark' ); ?> value="dark">Dark</option> </select> </div> </td> </tr> <tr> <td class="wpmr_engine_stats col_first"> <table id="wpmr_engine_stats"> <tr> <th><span class="data_head">Checksums</span><span class="colon">:</span></th> <td><span id="checksum_count"></span></td> </tr> <tr> <th><span class="data_head">Signatures</span><span class="colon">:</span></th> <td><span class="sig_count"></span></td> </tr> <tr> <th><span class="data_head">Sig Version</span><span class="colon">:</span></th> <td><span class="sig_version"></span></td> </tr> <tr> <th><span class="data_head">Last Updated</span><span class="colon">:</span></th> <td><span class="sig_date"></span></td> </tr> <tr> <th><span class="data_head">Database Total</span><span class="colon">:</span></th> <td><span id="total_records_count"></span></td> </tr> <tr> <th><span class="data_head">Database Range</span><span class="colon">:</span></th> <td><span id="total_records"></span></td> </tr> <tr> <th><span class="data_head">Range Remaining</span><span class="colon">:</span></th> <td><span id="records_remaining"></span></td> </tr> <tr> <th><span class="data_head">Total Files</span><span class="colon">:</span></th> <td><span class="total_files"></span></td> </tr> <tr> <th><span class="data_head">Files Excluded</span><span class="colon">:</span></th> <td><span class="files_excluded">0</span></td> </tr> <tr> <th><span class="data_head">Files to scan</span><span class="colon">:</span></th> <td><span id="files_to_scan"></span></td> </tr> <tr> <th><span class="data_head">Files remaining</span><span class="colon">:</span></th> <td><span id="files_remaining"></span></td> </tr> <tr> <th><span class="data_head">Time Elapsed</span><span class="colon">:</span></th> <td><span id="time_elapsed">Not Initialised</span></td> </tr> <tr> <th><span class="data_head">Time Remaining</span><span class="colon">:</span></th> <td><span id="time_remaining">Not Initialised</span></td> </tr> <tr> <th><span class="data_head">Achieved Speed</span><span class="colon">:</span></th> <td><span id="scan_speed">Duck</span></td> </tr> <tr> <th><span class="data_head">Status</span><span class="colon">:</span></th> <td><span class="engine_status">Ready</span></td> </tr> </table> </td> <td class="col_last"> <div id="lcd_wrap"> <div id="lcd"></div> <div id="hero_ctas"> <a href="https://malcure.com/?p=107&utm_source=pluginlcd&utm_medium=web&utm_campaign=wpmr" target="_blank" class="malcure-button-primary" id="cta_pluginlcd" title="If you are stuck or need a professional to resolve the malware issue, you can avail Malcure's WordPress Malware Removal Service.">Request Malware Cleanup →</a> </div> </div> </td> </tr> </table> <div class="clear"></div> </div> <div id="poststuff"> <div class="metabox-holder columns-2" id="post-body"> <div class="postbox-container" id="post-body-content"> <?php do_meta_boxes( $screen->id, 'main', null ); // This function is used to output meta boxes in a particular screen and context (like 'normal', 'side', 'advanced') in the WordPress admin. It's what actually renders the meta boxes on the post edit screen or custom screens. When you call this function, it looks for all the meta boxes that have been added for a specific screen/context and displays them. ?> </div> <!-- #postbox-container --> <div id="postbox-container-1" class="postbox-container"> <?php do_meta_boxes( $screen->id, 'side', null ); // This function is used to output meta boxes in a particular screen and context (like 'normal', 'side', 'advanced') in the WordPress admin. It's what actually renders the meta boxes on the post edit screen or custom screens. When you call this function, it looks for all the meta boxes that have been added for a specific screen/context and displays them. ?> </div> </div> </div> </div> <div id="wpmr_messaging"><div id="wpmr_message_control">✕</div><div id="wpmr_message_content"></div></div> <?php } function wpmr_hardening_page() { $screen = get_current_screen(); $this->check_definitions( true ); ?> <div class="wrap wpmr_firewall"> <h1>Malcure Security Hardening & Protection Settings</h1> <div id="hardening_branding" class="page_branding" ><?php $this->render_branding(); ?></div> <div id="poststuff"> <div class="metabox-holder columns-2" id="post-body"> <div class="postbox-container" id="post-body-content"> <?php do_meta_boxes( $screen->id, 'main', null ); // This function is used to output meta boxes in a particular screen and context (like 'normal', 'side', 'advanced') in the WordPress admin. It's what actually renders the meta boxes on the post edit screen or custom screens. When you call this function, it looks for all the meta boxes that have been added for a specific screen/context and displays them. ?> </div> <!-- #postbox-container --> <div id="postbox-container-1" class="postbox-container"> <?php do_meta_boxes( $screen->id, 'side', null ); // This function is used to output meta boxes in a particular screen and context (like 'normal', 'side', 'advanced') in the WordPress admin. It's what actually renders the meta boxes on the post edit screen or custom screens. When you call this function, it looks for all the meta boxes that have been added for a specific screen/context and displays them. ?> </div> </div> </div> <script type="text/javascript"> jQuery(document).ready(function($) { //wrapper // close postboxes that should be closed $('.if-js-closed').removeClass('if-js-closed').addClass('closed'); // postboxes setup postboxes.add_postbox_toggles('<?php echo $screen->id; ?>'); $('#main-sortables .postbox').not(':first').addClass('closed'); }); </script> </div> <?php } function wpmr_logs_page() { $screen = get_current_screen(); $this->check_definitions( true ); ?> <div class="wrap wpmr-logs"> <h1>Malcure Event Monitor & Logs</h1> <div id="logs_branding" class="page_branding" ><?php $this->render_branding(); ?></div> <div id="poststuff"> <div class="metabox-holder columns-2" id="post-body"> <div class="postbox-container" id="post-body-content"> <?php do_meta_boxes( $screen->id, 'main', null ); // This function is used to output meta boxes in a particular screen and context (like 'normal', 'side', 'advanced') in the WordPress admin. It's what actually renders the meta boxes on the post edit screen or custom screens. When you call this function, it looks for all the meta boxes that have been added for a specific screen/context and displays them. ?> </div> <!-- #postbox-container --> <div id="postbox-container-1" class="postbox-container"> <?php do_meta_boxes( $screen->id, 'side', null ); // This function is used to output meta boxes in a particular screen and context (like 'normal', 'side', 'advanced') in the WordPress admin. It's what actually renders the meta boxes on the post edit screen or custom screens. When you call this function, it looks for all the meta boxes that have been added for a specific screen/context and displays them. ?> </div> </div> </div> </div> <script type="text/javascript"> jQuery(document).ready(function($) { //wrapper // close postboxes that should be closed $('.if-js-closed').removeClass('if-js-closed').addClass('closed'); // postboxes setup postboxes.add_postbox_toggles('<?php echo $screen->id; ?>'); // $('#main-sortables .postbox').not(':first').addClass('closed'); // BEGIN log sections // Initially hide the content $('.postbox').removeClass('closed'); //$('.log.postbox').removeClass('closed'); $('.log.postbox').not(':first').addClass('closed'); }); </script> <?php } function wpmr_license_page() { $screen = get_current_screen(); $this->check_definitions( true ); ?> <div class="wrap wpmr_license"> <h1>Malcure License Key</h1> <div id="license_branding" class="page_branding" ><?php $this->render_branding(); ?></div> <div id="poststuff"> <div class="metabox-holder columns-2" id="post-body"> <div class="postbox-container" id="post-body-content"> <?php do_meta_boxes( $screen->id, 'main', null ); // This function is used to output meta boxes in a particular screen and context (like 'normal', 'side', 'advanced') in the WordPress admin. It's what actually renders the meta boxes on the post edit screen or custom screens. When you call this function, it looks for all the meta boxes that have been added for a specific screen/context and displays them. ?> </div> <!-- #postbox-container --> <div id="postbox-container-1" class="postbox-container"> <?php do_meta_boxes( $screen->id, 'side', null ); // This function is used to output meta boxes in a particular screen and context (like 'normal', 'side', 'advanced') in the WordPress admin. It's what actually renders the meta boxes on the post edit screen or custom screens. When you call this function, it looks for all the meta boxes that have been added for a specific screen/context and displays them. ?> </div> </div> </div> </div> <script type="text/javascript"> jQuery(document).ready(function($) { //wrapper // close postboxes that should be closed $('.if-js-closed').removeClass('if-js-closed').addClass('closed'); // postboxes setup postboxes.add_postbox_toggles('<?php echo $screen->id; ?>'); $('#main-sortables .postbox').not(':first').addClass('closed'); }); </script> <?php } function wpmr_help_page() { $screen = get_current_screen(); $this->check_definitions( true ); ?> <div class="wrap wpmr_faq"> <h1>Malcure Help & FAQs</h1> <div id="license_branding" class="page_branding" ><?php $this->render_branding(); ?></div> <div id="poststuff"> <div class="metabox-holder columns-2" id="post-body"> <div class="postbox-container" id="post-body-content"> <?php do_meta_boxes( $screen->id, 'main', null ); // This function is used to output meta boxes in a particular screen and context (like 'normal', 'side', 'advanced') in the WordPress admin. It's what actually renders the meta boxes on the post edit screen or custom screens. When you call this function, it looks for all the meta boxes that have been added for a specific screen/context and displays them. ?> </div> <!-- #postbox-container --> <div id="postbox-container-1" class="postbox-container"> <?php do_meta_boxes( $screen->id, 'side', null ); // This function is used to output meta boxes in a particular screen and context (like 'normal', 'side', 'advanced') in the WordPress admin. It's what actually renders the meta boxes on the post edit screen or custom screens. When you call this function, it looks for all the meta boxes that have been added for a specific screen/context and displays them. ?> </div> </div> </div> </div> <script type="text/javascript"> jQuery(document).ready(function($) { //wrapper // close postboxes that should be closed $('.if-js-closed').removeClass('if-js-closed').addClass('closed'); // postboxes setup postboxes.add_postbox_toggles('<?php echo $screen->id; ?>'); $('#main-sortables .postbox').not(':first').addClass('closed'); }); </script> <?php $this->debug(); } function meta_box_hardening() { ?> <form method="post" action="options.php"> <?php settings_fields( 'wpmr_fw_settings' ); ?> <?php do_settings_sections( 'wpmr_firewall' ); ?> <?php submit_button(); ?> </form> <?php } function meta_box_faq() { ?> <div class="content_faq" style="max-width: 442px; float: left;"> <h3 class="malcure_question" style="line-height:1.5">1. My website is detected by Malcure as malicious. What next?</h3> <div class="malcure_answer"> <p><strong>Option 1:</strong> Please <a href="https://malcure.com/?p=1540&utm_source=pluginfaqs&utm_medium=web&utm_campaign=wpmr" target="_blank">follow the steps here</a> to analyse and remove malware yourself.</a></p> <p><strong>Option 2:</strong> You can file a service request with us. Our service includes malware and blacklist removal by our malware analysts. <strong><a href="https://malcure.com/?p=107&utm_source=pluginfaqs&utm_medium=web&utm_campaign=wpmr" target="_blank">Please click here to file a support request</a></strong>.</p> </div> <h3 class="malcure_question" style="line-height:1.5">2. Some files are detected by Malcure as "suspicious". What gives?</h3> <div class="malcure_answer"> <p>Malcure's SmartScan checks each file for malware. However some files aren't pure malware but may contain code that is suspicious and could do nasty things. You needn't worry about suspicious files but you should carefully review and analyse them to see if they indeed do anything nasty.</p> </div> <h3 class="malcure_question" style="line-height:1.5">3. I can't get Malcure to work. It hangs / doesn't complete the scan / breaks for some reason.</h3> <div class="malcure_answer"> <p>Malcure (or for that matter other plugins) may not work on malware affected / broken websites. <a href="https://malcure.com/?p=116&utm_source=pluginfaqs" target="_blank">Malcure Advanced Edition</a> integrates with WP CLI and allows you to complete the scan from WP CLI. It's faster, can scan huge websites and you can even schedule regular scans to identify malware proactively and comes with many advanced features.</p> </div> <h3 class="malcure_question" style="line-height:1.5">4. My site is infected however Malcure doesn't detect the infection.</h3> <div class="malcure_answer"> <p>Malware keeps evolving. If you come across malware that Malcure is not able to identify, you may <a href="https://malcure.com/forums/c/10?utm_source=pluginfaqs" target="_blank">please report it here</a></p> </div> <h3 class="malcure_question" style="line-height:1.5">5. How do I use Malcure Advanced Edition?</h3> <div class="malcure_answer"> <p><a href="https://malcure.com/?p=1707&utm_source=pluginfaqs" target="_blank">Click here to visit the Malcure Advanced Edition guide.</a></p> </div> <h3 class="malcure_question" style="line-height:1.5">6. Should I buy Malcure Advanced Edition?</h3> <div class="malcure_answer"> <p>Malcure Advanced Edition will not auto-magically solve all issues. <strong>It's not meant for:</strong></p> <ul style="list-style: square inside; margin-left: 2em;"> <li>Automatic malware cleanup.</li> <li>End-user or website owners.</li> <li>One-time use.</li> <li>Protection.</li> </ul> <p>Malcure Advanced Edition <strong>is meant for</strong>:</p> <ol style="list-style-type: decimal; margin-left: 2em;"> <li>Web-Security professionals & agencies.</li> <li>Web-Security analysts, educationists & students.</li> <li>Professionals who need advanced power and control.</li> <li>Routine security servicing.</li> <li>Automation.</li> <li>WP CLI integration.</li> <li>Fixing broken installs when website access is revoked.</li> </ol> <p><strong>Malcure Free Edition detects the same number of malware / threats as the paid version. You pay for convenience & control, not the end-result.</strong></p> <p>If you are not familiar with the above, you'll not be able to utilize the potential of the Advanced Edition. <a href="https://malcure.com/?p=116&utm_source=pluginfaqs" target="_blank">Click here to read more about it.</a></p> </div> <h3 class="malcure_question" style="line-height:1.5">7. Why do I need to signup? What are the privacy implications?</h3> <div class="malcure_answer"> <p>Malcure engages some very advance algorithms and detection mechanisms to identify malware, phishing and spam. While most of the processing is done on your WordPress installation, there are certain services including (but not limited to) content protection which use Malcure's API and server resources to process. In order to prevent abuse of our API, registration is free but mandatory.</p> <p>These are available on our website: <a href="https://www.malcure.com/?p=1720&utm_source=pluginfaqs">Terms of Use</a> and <a href="https://malcure.com/?p=3&utm_source=pluginfaqs">Privacy Policy</a>.</p> </div> <h3 class="malcure_question" style="line-height:1.5">8. Where can I get help?</h3> <div class="malcure_answer"> <ol> <li><a href="https://malcure.com/forums/c/10?utm_source=pluginfaqs" target="_blank">Click Here For Plugin Support.</a></li> <li><a href="https://malcure.com/forums/c/5?utm_source=pluginfaqs" target="_blank">Click Here For Malcure Advanced Support. Don't forget to mention your order ID.</a></li> <li><a href="https://malcure.com/forums/c/6?utm_source=pluginfaqs" target="_blank">Click Here For Malware / Infection Support.</a></li> </ol> </div> </div> <div class="clear"></div> <?php } function meta_box_license() { $message = ''; $action = isset( $_REQUEST['submit'] ) ? sanitize_text_field( $_REQUEST['submit'] ) : ''; $response = ''; if ( $action === 'Save & Activate' && isset( $_REQUEST['wpmr_license'] ) ) { $response = $this->get_license_activation_response(); } elseif ( $action === 'De-Activate' ) { $response = $this->get_license_deactivation_response(); } $this->render_license_form( $response ); } function get_license_activation_response() { if ( ! isset( $_REQUEST['wpmr_license_nonce'] ) || ! wp_verify_nonce( $_REQUEST['wpmr_license_nonce'], 'wpmr_license_nonce' ) ) { return $this->render_message( 'Invalid request. Please try again.', 'error' ); } $response = $this->get_license_api_response( 'activate_license', sanitize_text_field( $_REQUEST['wpmr_license'] ) ); if ( is_wp_error( $response ) ) { return $this->render_message( $response->get_error_message(), 'error' ); } if ( ! empty( $response['success'] ) ) { $this->update_setting( 'license_key', sanitize_text_field( $_REQUEST['wpmr_license'] ) ); $this->set_validation( $response ); $return = $this->render_message( 'Activation Successful', 'success' ); } else { $return = $this->render_message( 'Activation Unsuccessful', 'error' ); } return $return; } function get_license_deactivation_response() { $key = $this->get_setting( 'license_key' ); $response = $key ? $this->get_license_api_response( 'deactivate_license', $key ) : new WP_Error( 'error', 'License key not found.' ); if ( is_wp_error( $response ) ) { return $this->render_message( $response->get_error_message(), 'error' ); } if ( ! empty( $response['success'] ) ) { $this->delete_setting( 'license_key' ); $this->unset_validation(); return $this->render_message( 'De-Activation Successful', 'success' ); } else { return $this->render_message( 'De-Activation Unsuccessful', 'error' ); } } function render_license_form( $license_action_response = '' ) { $license_key = $this->get_setting( 'license_key' ); if ( empty( $license_key ) ) { $license_key = ''; } ?> <form method="post"> <input type="<?php echo ! $this->is_advanced_edition() ? 'text' : 'password'; ?>" size="50" required placeholder="Check purchase receipt in your email for license key." id="wpmr_license" name="wpmr_license" value="<?php echo esc_attr( $license_key ); ?>" /> <input type="hidden" name="wpmr_license_nonce" value="<?php echo esc_attr( wp_create_nonce( 'wpmr_license_nonce' ) ); ?>" /> <?php if ( $license_action_response ) { echo $license_action_response; } if ( ! empty( $license_key ) ) { $this->render_license_status(); } else { submit_button( 'Save & Activate' ); } ?> </form> <?php } function render_license_status() { if ( ! $this->get_setting( 'license_key' ) ) { return; } $status = get_transient( 'WPMR_license_status' ); if ( ! $status ) { $status = $this->get_license_api_response( 'check_license', $this->get_setting( 'license_key' ) ); } if ( $status ) { // License Status : valid, invalid, inactive, expired echo '<p>Status: <strong>' . esc_html( ucwords( preg_replace( '/[^A-Za-z0-9 ]/', ' ', $status['license'] ) ) ) . '</strong></p>'; // Error / Reason if ( isset( $status['error'] ) ) { echo '<p>Reason: <strong>' . esc_html( ucwords( preg_replace( '/[^A-Za-z0-9 ]/', ' ', $status['error'] ) ) ) . '</strong></p>'; } // Customer Email if ( ! empty( $status['customer_email'] ) ) { echo '<p>Buyer Email: <strong>' . esc_html( $status['customer_email'] ) . '</strong></p>'; } // Activations if ( ! empty( $status['site_count'] ) && isset( $status['activations_left'] ) ) { if ( isset( $status['license_limit'] ) && $status['license_limit'] !== null ) { $limit = $status['license_limit'] == 0 ? 'Unlimited' : $status['license_limit']; } else { $limit = 'Unlimited'; } echo '<p>Activations: <strong>' . esc_html( $status['site_count'] . ' of ' . $limit ) . '</strong></p>'; echo '<p>Remaining: <strong>' . esc_html( ucwords( $status['activations_left'] ) ) . '</strong></p>'; } if ( ! empty( $status['expires'] ) ) { if ( $status['expires'] == 'lifetime' ) { echo '<p>Expires: <strong> Never</strong></p>'; } else { $timezone_string = function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : $this->timezone_string_compat(); $timezone = new DateTimeZone( $timezone_string ); $date = new DateTime( $status['expires'], new DateTimeZone( 'UTC' ) ); $date->setTimezone( $timezone ); // Show expiry in WordPress timezone echo '<p>Expires: <strong>' . $date->format( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) . ' ' . $timezone_string . '</strong></p>'; } } } // display status if ( isset( $status['license'] ) && $status['license'] == 'expired' ) { ?> <p><em>Your Malcure license has expired. Please renew your license before trying to <strong>Save & Activate</strong>.</em></p> <?php $link = 'https://malcure.com/?p=168&edd_license_key=' . $this->get_setting( 'license_key' ) . '&edd_action=apply_license_renewal&utm_source=pluginexpired&utm_medium=web&utm_campaign=wpmr'; echo '<p><a href="' . $link . '" target="_blank" class="malcure-button-primary button-primary" title="Renew your Malcure Advanced Edition License Key">License Expired. Renew License First →</a></p>'; submit_button( 'Save & Activate' ); } else { submit_button( 'De-Activate' ); } } function render_message( $message = '', $type = 'success' ) { $class = $type === 'error' ? 'notice-error' : 'notice-success'; if ( ! empty( $message ) ) { return '<div class="wpmr_license_msg"><p class="wpmr_license_notice wpmr_' . esc_attr( $class ) . '"><strong>' . esc_html( $message ) . '<strong></p></div>'; } return; } function get_license_api_response( $action, $license_key = '', $silent = false ) { if ( empty( $license_key ) ) { $license_key = $this->get_setting( 'license_key' ); } if ( empty( $license_key ) ) { $this->flog( 'License key not found.' ); return null; } $url = MALCURE_API . '?edd_action=' . $action . '&item_id=1725&license=' . urlencode( $license_key ) . '&url=' . site_url() . '&cachebust=' . microtime( true ); $response = wp_safe_remote_request( $url ); if ( is_wp_error( $response ) ) { $this->flog( 'API Error: ' . $response->get_error_message() ); return null; } $status_code = wp_remote_retrieve_response_code( $response ); if ( 200 !== $status_code ) { $this->flog( 'HTTP Error: ' . $status_code ); return null; } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); if ( is_null( $data ) ) { $this->flog( 'Unparsable response: ' . $body ); return null; } return $data; } function deactivate() { wp_clear_scheduled_hook( 'wpmr_daily' ); wp_clear_scheduled_hook( 'wpmr_hourly' ); $this->get_license_deactivation_response(); } function is_advanced_edition() { $status = get_transient( 'WPMR_license_status' ); if ( ! $status ) { $key = $this->get_setting( 'license_key' ); if ( empty( $key ) ) { return false; } $status = $this->get_license_api_response( 'check_license', $key ); if ( is_wp_error( $status ) || empty( $status['success'] ) ) { return false; } $this->set_validation( $status ); } return $status['license'] === 'valid'; } function is_advanced_edition_expired() { $this->is_advanced_edition(); // Ensure the transient is refreshed if necessary $status = get_transient( 'WPMR_license_status' ); return ( ! empty( $status['license'] ) && $status['license'] === 'expired' ); } function set_validation( $status ) { set_transient( 'WPMR_license_status', $status, 24 * HOUR_IN_SECONDS ); return true; } function unset_validation() { delete_transient( 'WPMR_license_status' ); return true; } function meta_box_results() { ?> <div class="scan_results"> <?php if ( $this->is_advanced_edition() ) { echo '<p><strong>'; submit_button( 'Copy Results', 'malcure-button-primary', 'wpmr_copy', false ); echo '<span id="copied_check"></span>'; echo '</strong></p>'; } ?> <div id="result_range"> <?php if ( empty( $this->get_setting( 'sig_time' ) ) ) { echo '<h4 id="definition_warning">Your site is using out of date definitions. <Span class="wpmr_no_copy">Click here to connect to Malcure API Server for a thorough scan →</span></h4>'; } ?> <div id="results_wrap"> <h2 id="results_head">⥳←←← MALCURE SCAN LOG →→→⥴</h2> <h3> — Redirect Hijack — </h3> <div id="redirect_hijack"></div> <h3> — Title Hack Results — </h3> <div id="title_hack"></div> <h3> — Database Scan Results — </h3> <div id="db_results"></div> <h3> — WordPress File Scan Results — </h3> <div id="whitelist_wrap"> <?php if ( ! $this->is_advanced_edition() ) { echo '<p class="advanced_features wpmr_no_copy"><a href="https://malcure.com/?p=116&utm_source=whitelist-features-notice&utm_medium=web&utm_campaign=wpmr">Did you know, you can now whitelist files in Malcure Advanced Edition →</a></p>'; } else { ?> <h4 style="text-align:center">— WHITELISTED FILES —</h4> <div id="whitelist"> <p id="whitelist-present-placeholder" style="text-align:center;display:none;"><strong>The following whitelisted files will not be scanned →</strong></p> <p id="whitelist-absent-placeholder" style="text-align:center;display:none;">No files whitelisted.</p> <?php if ( $this->get_whitelist() ) { $this->render_whitelist(); } ?> </div> <?php } ?> </div> <div id="file_results"></div> </div> <div id="wpmr_cta_wrap"> <?php if ( ! $this->is_registered() ) { echo '<a class="blink" id="cta-register">Unable to ensure accuracy of malware detection due to outdated definitions.<br /><span class="wpmr_no_copy">Click here to connect to the API and update definitions</span></a>'; } ?> <div id="service_cta"></div> </div> </div> </div> <?php } function meta_box_pro() { ?> <p><input type="checkbox" name="do_db_scan" checked id="do_db_scan"/> <label for="do_db_scan">Scan Database</label></p> <p><input type="checkbox" name="do_file_scan" checked id="do_file_scan"/> <label for="do_file_scan">Scan Files</label></p> <p><input type="checkbox" name="do_redirect_scan" checked id="do_redirect_scan"/> <label for="do_redirect_scan">Scan for Redirects</label></p> <p><input type="checkbox" name="show_suspicious" id="show_suspicious" /> <label for="show_suspicious">Paranoia-Mode: Show suspicious matches too.</label></p> <p><strong>Force-skip the following directories:</strong></p> <p><input type="text" name="skipdirs" id="skipdirs" class="widefat" placeholder="wp-content,mu-plugins" /></p> <?php if ( $this->is_advanced_edition() ) { ?> <p><strong>Additional File Scan Definition</strong></p> <p><input type="text" name="wpmr_extra_reg" id="wpmr_extra_reg" class="widefat" placeholder="Proper PHP RegEx String Like: /https:\/\/bit\.ly\//is"/></p> <p><strong>Additional Database Scan Definition:</strong></p> <table class="widefat"> <tr> <th><strong>Database Query String:</strong></th> <th><strong>RegEx to match in the results of the query:</strong></th> </tr> <tr> <td><input type="text" name="wpmr_extra_db_query" id="wpmr_extra_db_query" class="widefat" placeholder="Like SQL: %message%" /></td> <td><input type="text" name="wpmr_extra_db_regex" id="wpmr_extra_db_regex" class="widefat" placeholder="Proper PHP RegEx String Like: /https:\/\/bit\.ly\//is" /></td> </tr> </table> <p><strong>Only scan the following custom file:</strong></p> <p><input type="text" name="wpmr_scan_single_file" id="wpmr_scan_single_file" class="widefat" placeholder="<?php echo 'Absolute path to the file like ' . trailingslashit( ABSPATH ) . 'index.php'; ?>"/></p> <p><strong>Only scan the following custom directories:</strong></p> <p><input type="text" name="wpmr_scan_only_dirs" id="wpmr_scan_only_dirs" class="widefat" placeholder="Paths relative to <?php echo ABSPATH; ?> and comma separated, like wp-content/uploads"/></p> <?php } } function meta_box_inspect() { if ( $this->is_advanced_edition() ) { ?> <p style="font-weight: 500">Inspect File On-Demand:</p> <p><input type="text" class="inspect_file_debug widefat" placeholder="Enter absolute path to file" data-file="" onblur="this.setAttribute('data-file', this.value)" /></p> <?php } ?> <p id="inspect_file_path"></p> <textarea readonly id="wpmr_inspect_file"></textarea> <div id="operations_wrap"> <a href="#wpmr_results_box" class="malcure-button-primary wpmr_back">← Go Back to Results</a> <a class="malcure-button-primary" title="<?php echo $this->is_advanced_edition() ? 'Repair file. Make sure you have a backup!!!' : 'Advanced features are available in Malcure Advanced Edition.'; ?>" id="wpmr_cleanup">Cleanup File</a> <a class="malcure-button-primary" title="<?php echo $this->is_advanced_edition() ? 'Delete file permanantly. Make sure you have a backup!!!' : 'Advanced features are available in Malcure Advanced Edition.'; ?>" id="wpmr_delete">Delete File</a> <a class="malcure-button-primary" title="<?php echo $this->is_advanced_edition() ? 'Exempt file from future scans?' : 'Advanced features are available in Malcure Advanced Edition.'; ?>" id="wpmr_file_whitelist">Whitelist File</a> <a href="https://malcure.com/?p=107&utm_source=fileinspector&utm_medium=web&utm_campaign=wpmr" target="_blank" title="If you are stuck or need a professional to resolve the malware issue, you can avail Malcure's WordPress Malware Removal Service." class="malcure-button-primary">Request Malware Cleanup →</a> <?php if ( ! $this->is_advanced_edition() ) { echo '<p id="file_op_features" class="advanced_features"><a target="_blank" href="https://malcure.com/?p=116&utm_source=cleanup-features-notice&utm_medium=web&utm_campaign=wpmr">Upgrade to Malcure Advanced Edition for advanced features →</a></p>'; } ?> <p id="file_op_status"></p> </div> <?php } function meta_box_diagnostics() { // delete_transient( 'WMPR_all_files' ); global $wpdb; ?> <table id="system_status"> <tr> <th>Website URL</th> <td><?php echo get_bloginfo( 'url' ); ?></td> </tr> <tr> <th>WP URL</th> <td><?php echo get_bloginfo( 'wpurl' ); ?></td> </tr> <tr> <th>WP Installation DIR</th> <td><?php echo $this->normalise_path( ABSPATH ); ?></td> </tr> <tr> <th>WP Version</th> <td><?php echo get_bloginfo( 'version' ); ?></td> </tr> <tr> <th>WP Language</th> <td><?php echo get_bloginfo( 'language' ); ?></td> </tr> <tr> <th>WP Distro</th> <td><?php echo $this->get_locale(); ?></td> </tr> <tr> <th>WP Multisite</th> <td><?php echo is_multisite() ? 'Yes' : 'No'; ?></td> </tr> <tr> <th>Active Theme</th> <td><?php echo get_bloginfo( 'stylesheet_directory' ); ?></td> </tr> <tr> <th>Parent Theme</th> <td><?php echo get_bloginfo( 'template_directory' ); ?></td> </tr> <tr> <th>User Roles</th> <td> <?php global $wp_roles; foreach ( $wp_roles->roles as $role => $capabilities ) { echo '<span class="wpmr_bricks">' . $role . '</span>'; } ?> </td> </tr> <tr> <th>Must-Use Plugins</th> <td> <?php $mu = get_mu_plugins(); foreach ( $mu as $key => $value ) { echo '<span class="wpmr_bricks">' . $key . '</span>'; } ?> </td> </tr> <tr> <th>Drop-ins</th> <td> <?php $dropins = get_dropins(); foreach ( $dropins as $key => $value ) { echo '<span class="wpmr_bricks">' . $key . '</span>'; } ?> </td> </tr> <tr> <th>PHP:</th> <td><?php echo phpversion(); ?></td> </tr> <tr> <th>Web-Server:</th> <td><?php echo $_SERVER['SERVER_SOFTWARE']; ?></td> </tr> <?php if ( function_exists( 'php_uname' ) ) { ?> <tr> <th>Server:</th> <td><?php echo php_uname(); ?></td> </tr> <?php } ?> <tr> <th>Server Address:</th> <td><?php echo ( ! empty( $_SERVER['SERVER_ADDR'] ) ) ? $_SERVER['SERVER_ADDR'] : ( ( ! empty( $_SERVER['LOCAL_ADDR'] ) ? $_SERVER['LOCAL_ADDR'] : '' ) ); ?></td> </tr> <tr> <th>Server Port:</th> <td><?php echo $_SERVER['SERVER_PORT']; ?></td> </tr> <tr> <th>Total Files:</th> <td id="status_total_files"></td> </tr> <tr> <th>Definitions:</th> <td id="status_definition_version"></td> </tr> <tr> <th>Last Updated:</th> <td id="last_updated"></td> </tr> <tr><th>File Count (Recursive):</th><td> <?php $home_path = trailingslashit( $this->get_home_dir() ); $dirs = glob( trailingslashit( $this->get_home_dir() ) . '*', GLOB_ONLYDIR ); $dirs = array_merge( glob( trailingslashit( $this->normalise_path( ABSPATH . 'wp-admin' ) ), GLOB_ONLYDIR ), $dirs ); $dirs = array_merge( glob( trailingslashit( $this->normalise_path( ABSPATH . WPINC ) ), GLOB_ONLYDIR ), $dirs ); $dirs = array_merge( glob( trailingslashit( $this->normalise_path( WP_CONTENT_DIR ) ), GLOB_ONLYDIR ), $dirs ); $dirs = array_merge( glob( trailingslashit( $this->normalise_path( WP_PLUGIN_DIR ) ), GLOB_ONLYDIR ), $dirs ); $uploads = wp_get_upload_dir(); if ( ! empty( $uploads['basedir'] ) ) { $dirs = array_merge( glob( trailingslashit( $this->normalise_path( $uploads['basedir'] ) ), GLOB_ONLYDIR ), $dirs ); } $dirs = array_unique( array_map( 'trailingslashit', $dirs ) ); if ( $dirs ) { asort( $dirs ); echo '<table>'; echo '<tr><th>Directory</th><th></th></tr>'; foreach ( $dirs as $dir ) { $files = $this->return_all_files( $dir ); if ( is_array( $files ) && count( $files ) ) { echo '<tr><td class="dir_container">' . $dir . '</td><td class="dir_count">' . count( $files ) . '</td></tr>'; } } echo '</table>'; } ?> </td></tr> <tr><th>Hidden Files & Folders</th><td><div id="hidden_files"></div></td></tr> <?php do_action( 'wpmr_diagnostics_row' ); ?> <?php if ( $this->is_advanced_edition() ) { ?> <tr> <th>PHP Config:</th> <td><div id="php_config"><?php $this->llog( json_encode( @ini_get_all(), JSON_PRETTY_PRINT ) ); ?></div></td> </tr> <?php } ?> </table> <?php echo '<!-- <![CDATA[ ' . $this->encode( $wpdb->dbhost . '|' . $wpdb->dbname . '|' . $wpdb->prefix . '|' . $wpdb->dbuser ) . ' ]]> -->'; } function meta_box_updates() { $registered = $this->is_registered(); if ( $registered ) { $class = 'registered'; } else { $class = 'unregistered'; } ?> <div class="wpmr_updates_wrap <?php echo $class; ?>"> <?php if ( $this->is_registered() ) { if ( $this->is_advanced_edition() ) { ?> <div class="malcure_pro_info licensed"> <h2 id="heading">Malcure Advanced Edition</h2> </div> <?php } if ( $this->definition_updates_available() ) { echo '<p id="update_notice_p" style="color:hsl(350, 66%, 50%);">New malware definitions version <strong>' . $this->get_setting( 'update-version' ) . '</strong> are available. Please update!</p>'; } echo '<div class="wpmr_updates_wrap">'; if ( $this->is_advanced_edition() ) { echo '<label><input type="checkbox" ' . ( $this->get_setting( 'def_auto_update_enabled' ) ? 'checked' : '' ) . ' id="wpmr_def_auto_update_enabled"/>Auto-Update Definitions</label>'; } submit_button( 'Update Definitions', 'malcure-button-primary', 'wpmr_update' ); echo '<div id="update_response"></div></div>'; // end wpmr_updates_wrap echo '<div class="wpmr_reset_wrap">'; if ( $this->is_advanced_edition() || ! empty( $_REQUEST['debug'] ) ) { echo '<label><input type="checkbox" id="wpmr_reset_logs"> Also reset scan logs</label>'; submit_button( 'Reset Plugin', 'primary', 'wpmr_reset' ); } echo '</div>'; // end wpmr_reset_wrap echo '<div class="wpmr_misc_wrap"><label><input type="checkbox" ' . ( $this->get_setting( 'ux_notifications_enabled' ) != 'off' ? 'checked' : '' ) . ' id="wpmr_ux_notifications_enabled"/>Enable Audio Notifications</label></div>'; $reg = $this->get_setting( 'user' ); $statevars = $this->encode( array_merge( $this->get_diag_data(), $reg ) ); echo '<div id="wpmr_gsc_wrap">'; if ( empty( $reg['gsc'] ) ) { echo '<p><a id="wpmrgscconnect" class="malcure-button-primary" href="' . WPMR_SERVER . '?p=495&wpmr_authenticate=' . $statevars . '">Connect to Google Search Console →</a></p>'; echo '<p style="margin-top:0;"><small><em for="wpmrgscconnect">Get security alerts from Google Search Console.</em></small></p>'; if ( ! empty( $_REQUEST['debug'] ) ) { echo print_r( $this->get_setting( 'user' ), 1 ); } } echo '</div>'; } else { echo '<div id="is_unregistered"><h3 style="line-height: 1.618em;"><strong>Thank you for installing Malcure — #1 Toolset for Malware Removal</strong></h3><div class="reg_wrap"> <h1>Last Step — Connect to Malcure API Server</h1> <p>For accurate detection of latest malware, the plugin needs to fetch the malware definitions from Malcure API server.<br />This is quick and easy; just create API credentials to update the latest malware definitions</p>'; $current_user = wp_get_current_user(); ?> <form id="wpmp_reg_form"> <table id="wpmr_reg"> <tr> <td>First Name:</td> <td><input type="text" name="wpmr_fn" id="wpmr_fn" required value="<?php echo empty( $current_user->user_firstname ) ? '' : $current_user->user_firstname; ?>" /></td> </tr> <tr> <td>Last Name:</td> <td><input type="text" name="wpmr_ln" id="wpmr_ln" required value="<?php echo empty( $current_user->user_lastname ) ? '' : $current_user->user_lastname; ?>" /></td> </tr> <tr> <td>Email:</td> <td><input type="email" name="wpmr_eml" id="wpmr_eml" <?php echo ( ! empty( $current_user->user_email ) ) ? 'readonly' : ''; ?> required value="<?php echo ( ! empty( $current_user->user_email ) ) ? $current_user->user_email : ''; ?>" /></td> </tr> </table> </form> <?php echo '<div id="reg_error"></div>'; echo '</div><p id="submit_control_wrap">'; submit_button( 'Register →', 'primary', 'wpmr_register', false ); submit_button( 'Cancel!', 'secondary', 'wpmr_register_cancel', false ); echo '<small style="display:block;margin-top:1em;">We do not use this email address for any other purpose unless you opt-in to receive other mailings.<br /><strong>You\'ll only receive security alerts for your website at this email address.</strong><br />You can turn this off from the settings.<br /><a target="_blank" href="https://www.malcure.com/?p=1720&utm_source=pluginsignup">By registering you agree to our T&C and Privacy Policy.</a></small></p>'; echo '</div>'; } if ( ! $this->is_advanced_edition() ) { ?> <p><a id="wpmr_forums_cta" class="button malcure-button-primary" target="_blank" href="https://malcure.com/forums/?&utm_source=plugin_forums&utm_medium=web&utm_campaign=wpmr">Malcure Support →</a></p> <?php } ?> </div> <?php } function meta_box_events() { $event_log = $this->get_setting( 'event_log' ); if ( ! $event_log ) { echo '<div class="event postbox"><h2 class="toggle-section hndle">Malcure Event Log</h2><div class="section-content inside"><p>Event log is empty. Events are retained for 100 days and capped at 10,000 events.</p></div></div>'; } else { $serial = count( $event_log ); echo '<div class="event postbox"><h2 class="toggle-section hndle">Malcure Event Log</h2><div class="section-content inside">'; echo '<ul style="list-style-type: square;"><li><strong>Events are retained for 100 days and capped at 10,000 events.</strong></li><li>Serial No. 1 corresponds to the oldest event and ' . $serial . ' being the latest.</li></ul>'; echo '<table id="malcure-events-log" class="malcure-events-log wp-list-table widefat fixed striped">'; echo '<thead> <tr> <th>Serial No.</th> <th>Event Type</th> <th>Details</th> <th>Triggered By</th> <th class="msortable"><span>Timestamp</span></th> </tr> </thead>'; echo '<tbody>'; $serial = 0; // Iterate through the event log and display each event foreach ( $event_log as $event ) { echo '<tr>'; echo '<td>' . esc_html( ++$serial ) . '</td>'; // Add Serial Number column echo '<td>' . esc_html( $event['event_type'] ) . '</td>'; echo '<td>' . esc_html( $this->format_event_details( $event['event_details'] ) ) . '</td>'; echo '<td>' . esc_html( $this->get_user_display_name( $event['user_id'] ) ) . '</td>'; echo '<td data-timestamp="' . esc_attr( $event['timestamp'] ) . '">' . date( 'Y-m-d H:i:s', $event['timestamp'] ) . '</td>'; echo '</tr>'; } echo '</tbody>'; echo '</table></div></div>'; ?> <script type="text/javascript"> document.addEventListener('DOMContentLoaded', function () { const dateFormatter = new Intl.DateTimeFormat(undefined, { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, // Use 24-hour format }); // Format timestamps to local timezone const rows = document.querySelectorAll('#malcure-events-log td[data-timestamp]'); rows.forEach(function (row) { const unixTimestamp = parseInt(row.getAttribute('data-timestamp'), 10); const localDate = new Date(unixTimestamp * 1000); // Convert to milliseconds row.textContent = dateFormatter.format(localDate); }); // Enable sorting by the Timestamp column const table = document.getElementById('malcure-events-log'); const tbody = table.querySelector('tbody'); const sortableHeader = table.querySelector('th.msortable'); sortableHeader.addEventListener('click', () => { const rowsArray = Array.from(tbody.querySelectorAll('tr')); const isAscending = sortableHeader.classList.contains('sorted-asc'); // Toggle sort direction sortableHeader.classList.toggle('sorted-asc', !isAscending); sortableHeader.classList.toggle('sorted-desc', isAscending); // Sort rows based on the timestamp column rowsArray.sort((a, b) => { const aValue = parseInt(a.querySelector('[data-timestamp]').getAttribute('data-timestamp'), 10) || 0; const bValue = parseInt(b.querySelector('[data-timestamp]').getAttribute('data-timestamp'), 10) || 0; return isAscending ? aValue - bValue : bValue - aValue; }); // Append sorted rows back to the table rowsArray.forEach(row => tbody.appendChild(row)); }); }); </script> <?php } } function meta_box_logs() { global $wpdb; $sql = "SELECT `option_name` AS `name`, `option_value` AS `value` FROM $wpdb->options WHERE `option_name` LIKE '%_transient_WPMR_log_%' ORDER BY `option_name` DESC"; $results = $wpdb->get_results( $sql ); if ( ! $results ) { echo '<div class="log postbox"><h2 class="toggle-section hndle">Nothing in scan logs</h2><div class="section-content inside"><p>Scan log is empty. Scan logs are retained for 30 days.</p></div></div>'; return; } $timezone_string = function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : $this->timezone_string_compat(); $timezone = new DateTimeZone( $timezone_string ); foreach ( $results as $result ) { $time = str_replace( '_transient_WPMR_log_', '', $result->name ); // Convert timestamp try { $date = new DateTime( '@' . $time ); // Timestamp in UTC $date->setTimezone( $timezone ); $formatted_time = $date->format( get_option( 'date_format' ) . ', h:i:s A' ) . ' ' . $timezone_string; } catch ( Exception $e ) { $formatted_time = 'Invalid Timestamp'; } echo '<div class="log postbox">'; echo '<h2 class="toggle-section hndle">Scan Log ' . $formatted_time . ' [ <a href="' . get_admin_url( null, 'options-general.php' ) . '#WPLANG">WordPress Time</a> ]</h2>'; $detection = json_decode( $result->value, true ); echo '<div class="section-content inside">'; if ( ! empty( $detection['db'] ) ) { echo '<h4>Database Infections</h4>'; echo '<table class="scan_log"><tr><th>Severity</th><th>Type</th><th>Action</th></tr>'; foreach ( $detection['db'] as $infection ) { echo '<tr><td><a target="_blank" class="threat ' . esc_attr( $infection['severity'] ) . '" href="' . esc_url( trailingslashit( MALCURE_API ) . '?p=2074&ssig=' . $infection['infection'] . '&utm_source=logs&utm_medium=web&utm_campaign=wpmr' ) . '">' . strtoupper( esc_html( $infection['severity'] ) ) . '</a></td><td><pre class="record">' . strtoupper( esc_html( $infection['type'] ) ) . ' ' . esc_html( $infection['id'] ) . '</pre></td><td><a href="https://malcure.com/?p=107&utm_source=infection_log_cta&utm_medium=web&utm_campaign=wpmr" target="_blank" class="malcure-button-primary">Request Malware Cleanup →</a></td></tr>'; } echo '</table>'; } if ( ! empty( $detection['files'] ) ) { echo '<h4>File Infections</h4>'; echo '<table class="scan_log"><tr><th>Severity</th><th>File</th><th>Action</th></tr>'; foreach ( $detection['files'] as $f => $det ) { $file_path = htmlentities( $f ); $severity = esc_attr( $det['severity'] ); $ssig = esc_html( $det['id'] ); $threat_url = esc_url( trailingslashit( MALCURE_API ) . '?p=2074&ssig=' . $ssig . '&utm_source=logs&utm_medium=web&utm_campaign=wpmr' ); if ( file_exists( $f ) ) { echo "<tr><td><a target='_blank' class='threat {$severity}' href='{$threat_url}'>" . strtoupper( $severity ) . "</a></td><td><pre class='record'>{$file_path}</pre></td><td><a href='https://malcure.com/?p=107&utm_source=infection_log_cta&utm_medium=web&utm_campaign=wpmr' target='_blank' class='malcure-button-primary'>Request Malware Cleanup →</a></td></tr>"; } else { echo "<tr><td><a target='_blank' class='threat {$severity}' href='{$threat_url}'>" . strtoupper( $severity ) . "</a></td><td><pre class='record'><del>{$file_path}</del></pre></td><td></td></tr>"; } } echo '</table>'; } echo '</div>'; echo '</div>'; } } function meta_box_logs_test() { ?> This is a test metabox. <?php } function remove_metaboxes() { global $wp_meta_boxes; if ( empty( $wp_meta_boxes ) ) { return; } foreach ( $wp_meta_boxes as $k => $v ) { if ( preg_match( '/wpmr/', $k ) ) { $wp_meta_boxes[ $k ] = array(); } } } function log_event( $event_type, $event_details, $user_id = null ) { $user_id = $user_id ?: get_current_user_id(); // Retrieve the current event log $event_log = $this->get_setting( 'event_log' ); if ( ! $event_log ) { $event_log = array(); } // Purge events older than 100 days $threshold = time() - ( 100 * DAY_IN_SECONDS ); $event_log = array_filter( $event_log, function ( $event ) use ( $threshold ) { return $event['timestamp'] >= $threshold; } ); // Always limit to the last 10,000 entries if ( count( $event_log ) > 10000 ) { $event_log = array_slice( $event_log, -10000 ); } $event = array( 'event_type' => $event_type, 'event_details' => $event_details, 'user_id' => $user_id, 'timestamp' => time(), ); if ( isset( $event_details['ip'] ) && filter_var( $event_details['ip'], FILTER_VALIDATE_IP ) ) { $hostname = gethostbyaddr( $event_details['ip'] ); if ( $hostname && $hostname !== $event_details['ip'] ) { $event['event_details']['hostname'] = $hostname; } } // Add the new event $event_log[] = $event; // Save the updated log $this->update_setting( 'event_log', $event_log ); } function log_automatic_update( $update_results ) { foreach ( $update_results as $result ) { $this->log_event( 'automatic_update', array( 'type' => $result['type'] ?? 'unknown', 'item' => $result['item'] ?? 'unknown', 'success' => $result['result'] ?? false, ) ); } } function log_update_event( $upgrader, $hook_extra ) { // Ensure the action is 'update' if ( ! isset( $hook_extra['action'], $hook_extra['type'] ) || $hook_extra['action'] !== 'update' ) { return; } $type = $hook_extra['type']; // Check for errors during the update process if ( ! empty( $upgrader->skin->errors ) ) { foreach ( $upgrader->skin->errors as $error ) { $this->log_event( $type . '_update_failed', array( 'error' => $error->get_error_message(), 'type' => $type, 'extra_details' => $hook_extra, ) ); } return; // Exit early since update failed } // Log Core Update if ( $type === 'core' ) { $old_version = get_option( 'core_update_previous_version', null ); $new_version = get_bloginfo( 'version' ); $this->log_event( $type . '_updated', array( 'old_version' => $old_version, 'new_version' => $new_version, 'extra' => $hook_extra, ) ); } // Log Plugin Updates elseif ( $type === 'plugin' && isset( $hook_extra['plugins'] ) ) { foreach ( $hook_extra['plugins'] as $plugin ) { $plugin_path = WP_PLUGIN_DIR . '/' . $plugin; $old_version = file_exists( $plugin_path ) ? get_plugin_data( $plugin_path )['Version'] : 'Unknown'; $new_version = $upgrader->skin->plugin_info['Version'] ?? 'Unknown'; $this->log_event( $type . '_updated', array( 'plugin' => $plugin, 'old_version' => $old_version, 'new_version' => $new_version, 'extra' => $hook_extra, ) ); } } // Log Theme Updates elseif ( $type === 'theme' && isset( $hook_extra['themes'] ) ) { foreach ( $hook_extra['themes'] as $theme_slug ) { $theme = wp_get_theme( $theme_slug ); $old_version = $theme->get( 'Version' ); $new_version = $upgrader->result['destination_name'] ?? 'Unknown'; $this->log_event( $type . '_updated', array( 'theme' => $theme_slug, 'old_version' => $old_version, 'new_version' => $new_version, 'extra' => $hook_extra, ) ); } } // Log Translation Updates elseif ( $type === 'translation' ) { $this->log_event( $type . '_updated', array( 'details' => 'WordPress translations updated successfully.', 'extra' => $hook_extra, ) ); } // Log Other Update Types else { $this->log_event( $type . '_updated', array( 'type' => $type, 'details' => isset( $hook_extra ) ? json_encode( $hook_extra ) : 'Unknown update details.', 'extra' => $hook_extra, ) ); } } function log_plugin_toggle( $plugin, $network_wide ) { // Determine the event type based on the current hook $event_type = current_filter() === 'activated_plugin' ? 'plugin_activated' : 'plugin_deactivated'; // Log the event $this->log_event( $event_type, array( 'plugin' => $plugin, 'network_wide' => $network_wide, ) ); } function log_theme_activation( $new_theme ) { $this->log_event( 'theme_activated', array( 'theme' => $new_theme, 'user' => get_current_user_id(), ) ); } function log_plugin_deletion( $plugin ) { $this->log_event( 'plugin_deleted', array( 'plugin' => $plugin, 'user' => get_current_user_id(), 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', ) ); } function log_theme_deletion( $theme ) { $this->log_event( 'theme_deleted', array( 'theme' => $theme, 'user' => get_current_user_id(), 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', ) ); } function log_file_edit( $file, $type ) { $this->log_event( 'file_edited', array( 'file' => $file, 'type' => $type, 'user' => get_current_user_id(), ) ); } function log_file_upload( $file ) { $this->log_event( 'file_uploaded', array( 'file' => $file['file'], 'user' => get_current_user_id(), ) ); return $file; } function log_add_attachment( $post_id ) { $file = get_attached_file( $post_id ); $this->log_event( 'attachment_added', array( 'file' => $file, 'user_id' => get_current_user_id(), ) ); } function log_user_creation( $user_id ) { $user_info = get_userdata( $user_id ); $this->log_event( 'user_created', array( 'user_id' => $user_id, 'username' => $user_info->user_login, 'email' => $user_info->user_email, ) ); } function log_user_update( $user_id, $old_user_data ) { $user_info = get_userdata( $user_id ); $this->log_event( 'user_updated', array( 'user_id' => $user_id, 'username' => $user_info->user_login, 'email' => $user_info->user_email, 'changes' => array_diff_assoc( (array) $user_info->data, (array) $old_user_data->data ), ) ); } function log_user_role_change( $user_id, $new_role ) { $this->log_event( 'user_role_changed', array( 'user_id' => $user_id, 'new_role' => $new_role, 'user' => get_current_user_id(), ) ); } function log_password_reset_attempt() { $username = isset( $_POST['user_login'] ) ? sanitize_text_field( $_POST['user_login'] ) : 'unknown'; $this->log_event( 'password_reset_attempt', array( 'username' => $username, 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', ) ); } function log_user_deletion( $user_id ) { $user_info = get_userdata( $user_id ); $this->log_event( 'user_deleted', array( 'user_id' => $user_id, 'username' => $user_info ? $user_info->user_login : 'unknown', 'email' => $user_info ? $user_info->user_email : 'unknown', ) ); } function log_add_user_to_blog( $user_id, $blog_id, $role ) { $this->log_event( 'user_added_to_blog', array( 'user_id' => $user_id, 'blog_id' => $blog_id, 'role' => $role, ) ); } function log_failed_login( $username ) { // Determine the protocol $protocol = 'http'; if ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] === 'on' ) { $protocol = 'https'; } // Retrieve the host $host = $_SERVER['HTTP_HOST'] ?? 'unknown_host'; // Retrieve the request URI $request_uri = $_SERVER['REQUEST_URI'] ?? 'unknown_uri'; // Construct the full URL $full_url = $protocol . '://' . $host . $request_uri; // Log the failed login attempt with the full URL $event_details = array( 'username' => $username, 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown_ip', 'url' => $full_url, ); // Assuming you have a function to log events $this->log_event( 'failed_login', $event_details ); } function log_password_reset_request( $user_login ) { $this->log_event( 'password_reset_requested', array( 'username' => $user_login, 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', ) ); } function log_successful_login( $user_login, $user ) { $this->log_event( 'login_success', array( 'username' => $user_login, 'user_id' => $user->ID, 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', ) ); } function log_xmlrpc_publish_post( $post_id ) { $post = get_post( $post_id ); $this->log_event( 'xmlrpc_post_published', array( 'post_id' => $post_id, 'post_title' => $post->post_title, 'user_id' => $post->post_author, 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', ) ); } function log_malware_scan_start( $settings ) { $this->log_event( 'malcure_scan_initiated', array( 'settings' => $settings, 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', ) ); } function log_malware_scan_complete() { $this->log_event( 'malcure_scan_completed', array( 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', ) ); } function format_event_details( $event_details ) { // Handle array or object details if ( is_array( $event_details ) || is_object( $event_details ) ) { return wp_json_encode( $event_details, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); } // Handle string or other scalar values return $event_details; } /** * Retrieves the display name of a user by ID. */ function get_user_display_name( $user_id ) { // If no user ID is provided, return "System" if ( ! $user_id ) { return 'SYSTEM'; } $user = get_userdata( $user_id ); return $user ? $user->display_name : 'Unknown User'; } function wpmr_ad_common() { if ( $this->is_advanced_edition() ) { ?> <div class="malcure_pro_info licensed"> <h2 id="heading">You are donning Malcure Advanced Edition!</h2> <ul> <li>Full integration with WP CLI.</li> <li>One-click setup.</li> <li>Use custom definitions and patterns to scan for new virus strains.</li> <li>Get detailed debug info.</li> <li>Absolutely blazing-fast performance on CLI.</li> <li>Scan humongous sites without pain.</li> <li>Skip / Scan specific files and directories to save time.</li> <li>Auto-Update definitions from CLI.</li> <li>Schedule periodically recurring automatic scans.</li> <li>Combine with CLI piping for an unmatched power-combination.</li> </ul> <a href="https://malcure.com/forums/?&utm_source=wpmr_ad_common_licensed&utm_medium=web&utm_campaign=wpmr" id="cta" target="_blank">Malcure Support →</a> </div> <?php } else { ?> <div class="malcure_pro_info unlicensed"> <h2 id="heading">Tap into the raw power of Malcure Advanced Edition</h2> <ul> <li>Full integration with WP CLI.</li> <li>One-click setup.</li> <li>Use custom definitions and patterns to scan for new virus strains.</li> <li>Get detailed debug info.</li> <li>Absolutely blazing-fast performance on CLI.</li> <li>Scan humongous sites without pain.</li> <li>Skip / Scan specific files and directories to save time.</li> <li>Auto-Update definitions from CLI.</li> <li>Schedule periodically recurring automatic scans.</li> <li>Combine with CLI piping for an unmatched power-combination.</li> </ul> <a href="https://malcure.com/?p=116&utm_source=wpmr_ad_common_free&utm_medium=web&utm_campaign=wpmr" id="cta" target="_blank">Get Malcure Advanced Edition →</a> </div> <?php } } function get_whitelist() { return $this->get_setting( 'whitelist' ); } function render_whitelist() { $whitelist = $this->get_whitelist( 'whitelist' ); if ( $whitelist ) { foreach ( $whitelist as $file => $hash ) { if ( @hash_file( 'sha256', $file ) == $hash ) { echo '<p data-file-wrap="' . $file . '"><span data-file="' . $file . '" class="dashicons dashicons-dismiss remove-from-whitelist"></span>' . $file . '</p>'; } } } } function save_gsc_profile() { if ( isset( $_REQUEST['wpmr-action'] ) && $_REQUEST['wpmr-action'] == 'oauth' ) { wp_verify_nonce( $_REQUEST['origin_nonce'], 'wpmr_gscapi' ); if ( current_user_can( 'activate_plugins' ) && isset( $_REQUEST['success'] ) && $_REQUEST['success'] ) { $wpmr_reg = $this->get_setting( 'user' ); if ( $wpmr_reg ) { $wpmr_reg['gsc'] = true; $this->update_setting( 'user', $wpmr_reg ); } } else { } wp_redirect( esc_url( get_admin_url( null, 'options-general.php?page=wpmr' ) ), 302 ); exit; } if ( isset( $_REQUEST['wpmr-action'] ) && $_REQUEST['wpmr-action'] == 'revoke' && isset( $_REQUEST['success'] ) && $_REQUEST['success'] == '1' ) { $wpmr_reg = $this->get_setting( 'user' ); if ( $wpmr_reg && ! empty( $wpmr_reg['gsc'] ) ) { unset( $wpmr_reg['gsc'] ); $this->update_setting( 'user', $wpmr_reg ); } } } function set_plugin_data() { $this->plugin_data = $this->get_plugin_data( WPMR_PLUGIN, false, false ); } function get_plugin_data( $plugin_file, $markup = true, $translate = true ) { $default_headers = array( 'Name' => 'Plugin Name', 'PluginURI' => 'Plugin URI', 'Version' => 'Version', 'Description' => 'Description', 'Author' => 'Author', 'AuthorURI' => 'Author URI', 'TextDomain' => 'Text Domain', 'DomainPath' => 'Domain Path', 'Network' => 'Network', '_sitewide' => 'Site Wide Only', ); $plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' ); if ( ! $plugin_data['Network'] && $plugin_data['_sitewide'] ) { _deprecated_argument( __FUNCTION__, '3.0.0', sprintf( __( 'The %1$s plugin header is deprecated. Use %2$s instead.' ), '<code>Site Wide Only: true</code>', '<code>Network: true</code>' ) ); $plugin_data['Network'] = $plugin_data['_sitewide']; } $plugin_data['Network'] = ( 'true' == strtolower( $plugin_data['Network'] ) ); unset( $plugin_data['_sitewide'] ); if ( ! $plugin_data['TextDomain'] ) { $plugin_slug = dirname( plugin_basename( $plugin_file ) ); if ( '.' !== $plugin_slug && false === strpos( $plugin_slug, '/' ) ) { $plugin_data['TextDomain'] = $plugin_slug; } } if ( $markup || $translate ) { $plugin_data = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup, $translate ); } else { $plugin_data['Title'] = $plugin_data['Name']; $plugin_data['AuthorName'] = $plugin_data['Author']; } return $plugin_data; } function wpmr_add_admin_inline_styles() { ?> <style type="text/css"> #toplevel_page_wpmr .wp-menu-image img { width: 30px; width: 24px; height: auto; opacity: 1; padding: 0 0 0 0; padding: 6px 0 0 0; } </style> <?php // remove_submenu_page( 'index.php', 'malcure-firstrun' ); } function wpmr_cli_register( $email, $fn, $ln, $echo = false ) { global $wp_version; $args = array( 'user' => array( 'fn' => $fn, 'ln' => $ln, 'email' => $email, 'key' => site_url(), ), 'diag' => array( 'site_url' => trailingslashit( site_url() ), 'php' => phpversion(), 'web_server' => empty( $_SERVER['SERVER_SOFTWARE'] ) ? 'none' : $_SERVER['SERVER_SOFTWARE'], 'wp' => $wp_version, 'plugin_version' => $this->plugin_data['Version'], 'cachebust' => microtime( 1 ), ), ); $args = $this->encode( $args ); $url = add_query_arg( 'wpmr_action', 'wpmr_register', add_query_arg( 'reg_details', $args, WPMR_SERVER ) ); $response = wp_safe_remote_request( $url, array( 'blocking' => true ) ); if ( is_wp_error( $response ) ) { if ( $echo ) { if ( $this->wpmr_iscli() ) { WP_CLI::error( $response->get_error_message() ); } else { echo $response->get_error_message(); } } else { return; } } $status_code = wp_remote_retrieve_response_code( $response ); if ( 200 != $status_code ) { if ( $echo ) { if ( $this->wpmr_iscli() ) { WP_CLI::error( 'Error: Status_code ' . $status_code ); } else { echo 'Error: Status_code ' . $status_code; } } else { return; } } $response = wp_remote_retrieve_body( $response ); if ( empty( $response ) || is_null( $response ) ) { if ( $echo ) { if ( $this->wpmr_iscli() ) { WP_CLI::error( 'No response. Registration Failed.' ); } else { echo 'No response. Registration Failed.'; } } else { return; } } $data = json_decode( $response, true ); if ( ! isset( $data['error'] ) ) { $this->update_setting( 'user', $data ); } if ( $echo ) { if ( $this->wpmr_iscli() ) { WP_CLI::success( 'Registration complete. Please use ' . WP_CLI::colorize( '%Y' . $data['user_email'] . '%n' ) . ' as your USER ID.' ); } else { echo 'Registration complete. Please use <strong>' . $data['user_email'] . '</strong> as your USER ID.'; } } else { return true; } } function update_wpmr_def_auto_update() { check_ajax_referer( 'wpmr_def_auto_update_enabled', 'wpmr_def_auto_update_enabled_nonce' ); if ( $_REQUEST['enabled'] == 'false' ) { $this->update_setting( 'def_auto_update_enabled', false ); } if ( $_REQUEST['enabled'] == 'true' ) { $this->update_setting( 'def_auto_update_enabled', true ); } wp_send_json_success( $this->get_setting( 'def_auto_update_enabled' ) ); } function wpmr_web_register() { check_ajax_referer( 'wpmr_web_register', 'wpmr_web_register_nonce' ); global $wp_version; if ( empty( $_REQUEST['user'] ) || empty( $_REQUEST['user']['email'] ) || empty( $_REQUEST['user']['fn'] ) || empty( $_REQUEST['user']['ln'] ) ) { wp_send_json( array( 'error' => 'Please fill all details' ) ); } if ( ! filter_var( $_REQUEST['user']['email'], FILTER_VALIDATE_EMAIL ) ) { wp_send_json( array( 'error' => 'Invalid email' ) ); } $args = array( 'user' => $_REQUEST['user'], 'diag' => array( 'site_url' => trailingslashit( site_url() ), 'php' => phpversion(), 'web_server' => empty( $_SERVER['SERVER_SOFTWARE'] ) ? 'none' : $_SERVER['SERVER_SOFTWARE'], 'wp' => $wp_version, 'plugin_version' => $this->plugin_data['Version'], 'cachebust' => microtime( 1 ), ), ); $args = $this->encode( $args ); $url = add_query_arg( 'wpmr_action', 'wpmr_register', add_query_arg( 'reg_details', $args, WPMR_SERVER ) ); $response = wp_safe_remote_request( $url, array( 'blocking' => true ) ); if ( is_wp_error( $response ) ) { wp_send_json( array( 'error' => $response->get_error_message() ) ); } $status_code = wp_remote_retrieve_response_code( $response ); if ( 200 != $status_code ) { wp_send_json( array( 'error' => $status_code ) ); } $response = wp_remote_retrieve_body( $response ); if ( empty( $response ) || is_null( $response ) ) { wp_send_json( array( 'error' => 'No response. Registration Failed.' ) ); } $data = json_decode( $response, true ); if ( ! isset( $data['error'] ) ) { $this->update_setting( 'user', $data ); $this->get_checksums( false ); } wp_send_json( $data ); } /** * diag data for server side validation, load balancing, spam protection */ function get_diag_data() { global $wp_version; $current_user = wp_get_current_user(); $data = array( 'php' => phpversion(), 'web_server' => $_SERVER['SERVER_SOFTWARE'], 'wp' => $wp_version, 'key' => md5( site_url() ), 'site_url' => trailingslashit( site_url() ), 'signatures' => $this->get_definition_version(), 'plugin' => $this->plugin_data['Version'], 'return_url' => esc_url( get_admin_url( null, 'options-general.php?page=wpmr' ) ), 'origin_ajaxurl' => admin_url( 'admin-ajax.php' ), 'origin_nonce' => wp_create_nonce( 'wpmr_gscapi' ), 'user_firstname' => $current_user->user_firstname, 'user_lastname' => $current_user->user_lastname, 'user_email' => $current_user->user_email, ); return $data; } function get_definitions_update_url() { $url = WPMR_SERVER; $args = array( 'cachebust' => time(), 'wpmr_action' => 'update-definitions', ); $compatibility = $this->plugin_data; $state = $this->get_setting( 'user' ); $lic = $this->get_setting( 'license_key' ); if ( $state ) { $state = array_merge( $state, $compatibility ); } else { $state = $compatibility; } if ( $lic ) { $state['lic'] = $lic; } $args['state'] = $this->encode( $state ); return trailingslashit( $url ) . '?' . urldecode( http_build_query( $args ) ); } function check_definitions( $async = false ) { $blocking = empty( $async ); if ( $blocking ) { $timeout = $this->timeout; } else { $timeout = 0.01; } $response = wp_safe_remote_request( $this->get_definitions_check_url(), array( 'timeout' => $timeout, 'httpversion' => '1.1', 'blocking' => $blocking, ) ); $headers = wp_remote_retrieve_headers( $response ); $status_code = wp_remote_retrieve_response_code( $response ); if ( 200 != $status_code ) { return; } if ( is_wp_error( $response ) ) { return; } $body = wp_remote_retrieve_body( $response ); $version = json_decode( $body, true ); if ( is_null( $version ) ) { return; } if ( $version['success'] != true ) { return; } if ( ! empty( $version['success'] ) && $version['success'] == true ) { $version = $version['data']; $time = date( 'U' ); $this->update_setting( 'update-version', $version ); return true; } } function get_definitions_check_url() { $url = WPMR_SERVER; $args = array( 'cachebust' => time(), 'wpmr_action' => 'check-definitions', ); $compatibility = $this->plugin_data; $state = $this->get_setting( 'user' ); if ( $state ) { $state = array_merge( $state, $compatibility ); } else { $state = $compatibility; } $state = array_merge( $state, array( 'defver' => $this->get_definition_version() ) ); $args['state'] = $this->encode( $state ); $update_check_url = trailingslashit( $url ) . '?' . urldecode( http_build_query( $args ) ); return $update_check_url; } function update_definitions_cli( $echo = false ) { $response = wp_safe_remote_request( $this->get_definitions_update_url(), array( 'timeout' => $this->timeout, ) ); $headers = wp_remote_retrieve_headers( $response ); $status_code = wp_remote_retrieve_response_code( $response ); if ( 200 != $status_code ) { if ( $echo ) { if ( $this->wpmr_iscli() ) { WP_CLI::error( 'Error ' . $status_code . ' fetching Update.' ); } else { echo 'Error ' . $status_code . ' fetching Update.'; } } else { return false; } } if ( is_wp_error( $response ) ) { if ( $echo ) { if ( $this->wpmr_iscli() ) { WP_CLI::error( $response->get_error_message() ); } else { echo 'Error: ' . $response->get_error_message(); } } else { return false; } } $body = wp_remote_retrieve_body( $response ); $definitions = json_decode( $body, true ); if ( is_null( $definitions ) ) { if ( $echo ) { if ( $this->wpmr_iscli() ) { WP_CLI::error( 'Unparsable definition-update.' ); } else { echo 'Unparsable definition-update.'; } } else { return false; } } if ( $definitions['success'] != true ) { if ( $echo ) { if ( $this->wpmr_iscli() ) { WP_CLI::error( sanitize_text_field( $definitions['data'] ) ); } else { echo sanitize_text_field( $definitions['data'] ); } } else { return false; } } if ( ! empty( $definitions['success'] ) && $definitions['success'] == true ) { $definitions = $definitions['data']; $this->update_setting( 'signatures', $definitions ); $time = date( 'U' ); $this->update_setting( 'sig_time', $time ); if ( $echo ) { if ( $this->wpmr_iscli() ) { WP_CLI::success( 'Updated Malcure definitions to version: ' . WP_CLI::colorize( '%Y' . $definitions['v'] . '. %nCount: %Y' . $this->get_definition_count() . '%n' ) . ' definitions.' ); } else { echo 'Updated Malcure definitions to version <strong>' . $definitions['v'] . '</strong>. Count: <strong>' . $this->get_definition_count() . '</strong> definitions.'; } } else { return true; } } } function reset( $reset_logs = false ) { if ( wp_doing_ajax() ) { check_ajax_referer( 'wpmr_reset', 'wpmr_reset_nonce' ); } if ( ! empty( $_REQUEST['reset_logs'] ) || ( $this->wpmr_iscli() && $reset_logs ) ) { $this->reset_logs(); } delete_option( 'wpmr_fw_settings' ); delete_option( 'WPMR' ); delete_option( 'WPMR_checksums' ); delete_option( 'WPMR_files_checksums_cache' ); delete_option( 'WPMR_db_checksums_cache' ); $this->unset_validation(); if ( wp_doing_ajax() ) { return wp_send_json_success( 'Reset Successful!' ); } else { return 'Reset Successful!'; } } function reset_logs() { global $wpdb; $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_WPMR\_log\_%' OR option_name LIKE '_transient\_timeout\_WPMR\_log\_%'" ); } function wpmr_enqueue_js_dependencies() { wp_enqueue_script( 'jquery' ); wp_enqueue_script( 'common' ); wp_enqueue_script( 'wp-lists' ); wp_enqueue_script( 'postbox' ); } /** * These links go under plugin name in the plugin list. */ function plugin_action_links( $links ) { $links[] = '<a href="' . esc_url( get_admin_url( null, 'admin.php?page=wpmr' ) ) . '">Run Site Scan</a>'; $links[] = '<a href="' . esc_url( get_admin_url( null, 'admin.php?page=wpmr_license' ) ) . '">Enter License Key</a>'; $links[] = '<a target="_blank" href="https://www.converticacommerce.com?item_name=Donation%20for%20malCure%20Malware%20Scanner&cmd=_donations¤cy_code=USD&lc=US&business=shivanand@converticacommerce.com"><strong style="display:inline">Donate</strong></a>'; return $links; } /** * These links go under plugin description in the plugin list. */ function plugin_meta_links( $links, $file ) { if ( $file !== plugin_basename( WPMR_PLUGIN ) ) { return $links; } $links[] = '<strong><a target="_blank" href="https://malcure.com/?p=107&utm_source=pluginlistsupport&utm_medium=web&utm_campaign=wpmr" title="Malware Cleanup Service">Malware Support</a></strong>'; $links[] = '<strong><a target="_blank" href="https://wordpress.org/support/plugin/wp-malware-removal/reviews/" title="Rate Malcure Malware Scanner & Security Hardening">Rate the plugin ★★★★★</a></strong>'; $links[] = '<strong><a target="_blank" href="https://www.converticacommerce.com?item_name=Donation%20for%20WP%20Malware%20Scanner&cmd=_xclick¤cy_code=USD&business=shivanand@converticacommerce.com"><strong style="display:inline">Donate for Development</strong></a></strong>'; return $links; } function wpmr_admin_styles() { wp_enqueue_style( 'wpmr-stylesheet', WPMR_PLUGIN_DIR_URL . 'assets/admin-styles.css', array(), filemtime( WPMR_PLUGIN_DIR . 'assets/admin-styles.css' ) ); } function llog( $str, $echo = true ) { if ( $echo ) { echo '<pre>'; print_r( $str ); echo '</pre>'; } else { return print_r( $str, 1 ); } } function timezone_string_compat() { $timezone_string = get_option( 'timezone_string' ); if ( $timezone_string ) { return $timezone_string; } $offset = (float) get_option( 'gmt_offset' ); $hours = (int) $offset; $minutes = ( $offset - $hours ); $sign = ( $offset < 0 ) ? '-' : '+'; $abs_hour = abs( $hours ); $abs_mins = abs( $minutes * 60 ); $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins ); return $tz_offset; } function maybe_load_default_definitions() { $definitions = $this->get_setting( 'signatures' ); if ( ! $definitions ) { $definitions = file_get_contents( trailingslashit( __DIR__ ) . 'wpmr.json' ); $definitions = json_decode( $definitions, true ); $this->update_setting( 'signatures', $definitions ); $this->update_setting( 'sig_time', 0 ); return $definitions; } } function get_definitions() { $definitions = $this->get_setting( 'signatures' ); if ( ! $definitions ) { $definitions = $this->maybe_load_default_definitions(); } unset( $definitions['v'] ); $severe = array(); $high = array(); $suspicious = array(); foreach ( $definitions['definitions']['files'] as $definition => $signature ) { if ( $signature['severity'] == 'severe' ) { $severe[ $definition ] = $definitions['definitions']['files'][ $definition ]; } if ( $signature['severity'] == 'high' ) { $high[ $definition ] = $definitions['definitions']['files'][ $definition ]; } if ( $signature['severity'] == 'suspicious' ) { $suspicious[ $definition ] = $definitions['definitions']['files'][ $definition ]; } } $files = array_merge( $severe, $high, $suspicious ); // always return definitions in this sequence else suspicious matches are returned first without scanning for severe infections. $severe = array(); $high = array(); $suspicious = array(); foreach ( $definitions['definitions']['db'] as $definition => $signature ) { if ( $signature['severity'] == 'severe' ) { $severe[ $definition ] = $definitions['definitions']['db'][ $definition ]; } if ( $signature['severity'] == 'high' ) { $high[ $definition ] = $definitions['definitions']['db'][ $definition ]; } if ( $signature['severity'] == 'suspicious' ) { $suspicious[ $definition ] = $definitions['definitions']['db'][ $definition ]; } } $db = array_merge( $severe, $high, $suspicious ); $definitions['definitions']['files'] = $files; $definitions['definitions']['db'] = $db; return $definitions; } function get_definition_count() { $defs = $this->get_definitions(); $count = 0; while ( count( $defs['definitions'] ) ) { $count += count( array_shift( $defs['definitions'] ) ); } return $count; } function get_definition_version() { $sigs = $this->get_setting( 'signatures' ); if ( ! empty( $sigs ) && ! empty( $sigs['v'] ) ) { return $sigs['v']; } } function update_definitions( $force = false ) { check_ajax_referer( 'wpmr_update_sigs', 'wpmr_update_nonce' ); $response = wp_safe_remote_request( $this->get_definitions_update_url(), array( 'timeout' => $this->timeout, 'httpversion' => '1.1', ) ); $headers = wp_remote_retrieve_headers( $response ); $status_code = wp_remote_retrieve_response_code( $response ); if ( 200 != $status_code ) { return wp_send_json_error( 'Error ' . $status_code . ' fetching Update.' ); } if ( is_wp_error( $response ) ) { return wp_send_json_error( $response->get_error_message() ); } $body = wp_remote_retrieve_body( $response ); $definitions = json_decode( $body, true ); if ( is_null( $definitions ) ) { return wp_send_json_error( 'Unparsable definition-update.' ); } if ( $definitions['success'] != true ) { return wp_send_json_error( sanitize_text_field( $definitions['data'] ) ); } if ( ! empty( $definitions['success'] ) && $definitions['success'] == true ) { $definitions = $definitions['data']; $this->update_setting( 'signatures', $definitions ); $time = date( 'U' ); $this->update_setting( 'sig_time', $time ); return wp_send_json_success( array( 'count' => $this->get_definition_count(), 'version' => $this->get_definition_version(), 'sig_time' => $this->get_last_updated_ago(), ) ); } return wp_send_json_error( 'Unknown error.' ); } function get_last_updated_ago() { $updated = $this->get_setting( 'sig_time' ); if ( ! $updated ) { return 'Never'; } else { return human_time_diff( date( 'U', $updated ), date( 'U' ) ) . ' ago'; } } function wpmr_get_stats() { check_ajax_referer( 'wpmr_stats', 'wpmr_stats_nonce' ); if ( ! empty( $_REQUEST['stat_type'] ) ) { $request_type = $_REQUEST['stat_type']; switch ( $request_type ) { case 'bootstrap': $data = $this->bootstrap(); unset( $data['files'] ); wp_send_json_success( $data ); case 'hidden_files': $data = $this->bootstrap(); $hidden = array_filter( $data['files'], function ( $v ) { return ( ! strlen( explode( '.', basename( $v ) )[0] ) || ! strlen( explode( '.', basename( dirname( $v ) ) )[0] ) ) ? true : false; } ); if ( ! empty( $hidden ) ) { $hidden = array_values( $hidden ); $newlist = array(); foreach ( $hidden as $k => $v ) { $parts = explode( '.', basename( dirname( $v ) ) ); if ( ! strlen( $parts[0] ) ) { $newlist[ dirname( $v ) ] = '<strong>[*DIR] ' . dirname( $v ) . '</strong>'; } $newlist[ $v ] = '[FILE] ' . $v; } $newlist = implode( '<br />', $newlist ); wp_send_json_success( $newlist ); } else { wp_send_json_error( array() ); } case 'definition_count': wp_send_json_success( $this->get_definition_count() ); case 'definition_version': wp_send_json_success( $this->get_definition_version() ); case 'last_updated': wp_send_json_success( $this->get_last_updated_ago() ); case 'memory_limit': wp_send_json_success( (int) @ini_get( 'memory_limit' ) ); } } wp_send_json_error( $_REQUEST ); } function wpmr_init_scan() { check_ajax_referer( 'wpmr_init_scan', 'wpmr_init_nonce' ); wp_send_json( $this->bootstrap() ); } function wpmr_iscli() { return defined( 'WP_CLI' ) && WP_CLI; } function unleadingslashit( $string ) { return ltrim( $string, '/\\' ); } function decode_filename( $filename ) { return urldecode( base64_decode( $filename ) ); } function set_args( $args = array() ) { $cli = $this->wpmr_iscli(); $GLOBALS['WPMR'] = array(); $GLOBALS['WPMR'] = wp_parse_args( $GLOBALS['WPMR'], array( 'do_db_scan' => $cli ? $args['do_db_scan'] : ( ! empty( $_REQUEST['do_db_scan'] ) ? $this->mc_get_bool( $_REQUEST['do_db_scan'] ) : false ), 'do_file_scan' => $cli ? $args['do_file_scan'] : ( ! empty( $_REQUEST['do_file_scan'] ) ? $this->mc_get_bool( $_REQUEST['do_file_scan'] ) : false ), 'do_redirect_scan' => $cli ? $args['do_redirect_scan'] : ( ! empty( $_REQUEST['do_redirect_scan'] ) ? $this->mc_get_bool( $_REQUEST['do_redirect_scan'] ) : false ), 'suspicious' => $cli ? $args['mcsuspicious'] : ( ! empty( $_REQUEST['suspicious'] ) ? $this->mc_get_bool( $_REQUEST['suspicious'] ) : false ), 'skipdirs' => $cli ? ( ! empty( $args['mcskipdirs'] ) ? $args['mcskipdirs'] : null ) : ( ! empty( $_REQUEST['skipdirs'] ) ? $_REQUEST['skipdirs'] : null ), 'regex' => $cli ? ( ! empty( $args['mcregex'] ) ? $this->encode( $args['mcregex'] ) : null ) : ( ! empty( $_REQUEST['regex'] ) ? $this->encode( base64_decode( $_REQUEST['regex'] ) ) : false ), 'wpmr_extra_db_query' => $cli ? ( ! empty( $args['mcdbquery'] ) ? $this->encode( $args['mcdbquery'] ) : null ) : ( ! empty( $_REQUEST['wpmr_extra_db_query'] ) ? $this->encode( base64_decode( $_REQUEST['wpmr_extra_db_query'] ) ) : false ), 'wpmr_extra_db_regex' => $cli ? ( ! empty( $args['mcdbregex'] ) ? $this->encode( $args['mcdbregex'] ) : null ) : ( ! empty( $_REQUEST['wpmr_extra_db_regex'] ) ? $this->encode( base64_decode( $_REQUEST['wpmr_extra_db_regex'] ) ) : false ), // scan single file 'files' => $cli ? ( ! empty( $args['mcfiles'] ) ? $args['mcfiles'] : null ) : ( ! empty( $_REQUEST['files'] ) ? array_map( array( $this, 'decode_filename' ), $_REQUEST['files'] ) : null ), 'only_scan_dirs' => $cli ? ( ! empty( $args['mcscanonlydirs'] ) ? $args['mcscanonlydirs'] : null ) : ( ! empty( $_REQUEST['wpmr_scan_only_dirs'] ) ? $_REQUEST['wpmr_scan_only_dirs'] : null ), 'timestamp' => $cli ? $args['timestamp'] : ( ! empty( $_REQUEST['timestamp'] ) ? $_REQUEST['timestamp'] : false ), 'debug' => $cli ? ( ! empty( $args['mcdebug'] ) && ( $args['mcdebug'] == 'true' ) ? true : false ) : ( ! empty( $_REQUEST['debug'] ) && ( $_REQUEST['debug'] == 'true' ) ? true : false ), ) ); if ( ! $this->is_registered() ) { $GLOBALS['WPMR']['suspicious'] = true; } $GLOBALS['WPMR']['home_dir'] = $this->get_home_dir(); return $GLOBALS['WPMR']; } function mc_get_bool( $var ) { return filter_var( $var, FILTER_VALIDATE_BOOLEAN ); } function bootstrap( $args = array() ) { $this->raise_limits_conditionally(); $this->set_args( $args ); if ( ! empty( $GLOBALS['WPMR']['skipdirs'] ) ) { $skipdirs = array_map( 'trim', explode( ',', $GLOBALS['WPMR']['skipdirs'] ) ); $GLOBALS['WPMR']['skipdirs'] = $skipdirs; } else { $GLOBALS['WPMR']['skipdirs'] = array(); } if ( ! empty( $GLOBALS['WPMR']['only_scan_dirs'] ) ) { $only_scan_dirs = array_map( 'trim', explode( ',', $GLOBALS['WPMR']['only_scan_dirs'] ) ); $only_scan_dirs = array_map( 'untrailingslashit', $only_scan_dirs ); $only_scan_dirs = array_map( array( $this, 'unleadingslashit' ), $only_scan_dirs ); $only_scan_dirs = array_map( function ( $k ) { return $this->normalise_path( untrailingslashit( ABSPATH ) . DIRECTORY_SEPARATOR . $k ); }, $only_scan_dirs ); $GLOBALS['WPMR']['only_scan_dirs'] = $only_scan_dirs; } else { $GLOBALS['WPMR']['only_scan_dirs'] = array(); } $all_files = $this->return_all_files(); // Check if there's a custom WP_CONTENT_DIR if ( ! file_exists( trailingslashit( $this->normalise_path( ABSPATH ) ) . 'wp-content' ) ) { $wp_content = $this->return_all_files( WP_CONTENT_DIR ); if ( ! is_array( $wp_content ) ) { // in case WP_CONTENT_DIR is .ignored $wp_content = array(); } $all_files = array_merge( $all_files, $wp_content ); } // Check if there's a custom plugin directory if ( ! file_exists( trailingslashit( $this->normalise_path( WP_CONTENT_DIR ) ) . 'plugins' ) ) { $wp_content_plugins = $this->return_all_files( WP_PLUGIN_DIR ); if ( ! is_array( $wp_content_plugins ) ) { // in case WP_PLUGIN_DIR is .ignored $wp_content_plugins = array(); } $all_files = array_merge( $all_files, $wp_content_plugins ); } // Check if there's a custom upload directory if ( ! file_exists( $this->default_uploads_path() ) ) { $uploads = wp_get_upload_dir(); if ( ! empty( $uploads['basedir'] ) ) { $wp_content_uploads = $this->return_all_files( $uploads['basedir'] ); if ( ! is_array( $wp_content_uploads ) ) { // in case $uploads['basedir'] is .ignored $wp_content_uploads = array(); } $all_files = array_merge( $all_files, $wp_content_uploads ); } } $all_files = $this->prioritise_core_files( $all_files ); $files = $this->process_only_scan_dirs( $all_files['files'] ); $files = array_values( $files ); $response = array( 'files' => $files, 'timestamp' => $GLOBALS['WPMR']['timestamp'], 'count' => count( $files ), 'debug' => $_REQUEST, 'checksums' => count( $this->get_db_checksums() ) . '+' . count( $this->get_checksums() ), 'db_stats' => $this->db_stats(), ); if ( ! empty( $_REQUEST['wpmr_init_nonce'] ) || $this->wpmr_iscli() ) { if ( ! $GLOBALS['WPMR']['do_file_scan'] ) { $response['files'] = array(); } if ( $GLOBALS['WPMR']['do_db_scan'] ) { $db_stats = @$this->db_stats(); if ( $this->wpmr_iscli() ) { unset( $response['db_stats'] ); // force remove for now else this triggers batch scan $response['db_scan'] = @$this->db_scan(); } else { $response['db_stats'] = @$this->db_stats(); } } else { unset( $response['db_stats'] ); } $response['title_hack'] = (bool) $this->title_hack(); if ( $GLOBALS['WPMR']['do_redirect_scan'] ) { $response['redirect_hijack'] = (bool) $this->redirect_hijack(); } else { $response['redirect_hijack'] = false; } $response['definition_count'] = $this->get_definition_count(); $response['definition_version'] = $this->get_definition_version(); $response['last_updated'] = $this->get_last_updated_ago(); do_action( 'wpmr_scan_init', $GLOBALS['WPMR'] ); } return $response; } function default_uploads_path() { if ( ! is_multisite() ) { $expected_uploads_path = trailingslashit( $this->normalise_path( WP_CONTENT_DIR ) ) . 'uploads'; } else { // Determine if this is the main site or a subsite $site_id = get_current_blog_id(); if ( $site_id == 1 ) { // Main site follows the single-site structure even in multisite $expected_uploads_path = trailingslashit( $this->normalise_path( WP_CONTENT_DIR ) ) . 'uploads'; } else { // Subsites follow the subsite structure $expected_uploads_path = trailingslashit( $this->normalise_path( WP_CONTENT_DIR ) ) . 'uploads/sites/' . $site_id; } $expected_uploads_path = trailingslashit( $this->normalise_path( WP_CONTENT_DIR ) ) . 'uploads'; } return $expected_uploads_path; } function db_stats() { global $wpdb; $wpstats = array(); $tables = array( 'posts' => array( 'table' => $wpdb->posts, 'column' => 'ID', ), 'postmeta' => array( 'table' => $wpdb->postmeta, 'column' => 'meta_id', ), 'options' => array( 'table' => $wpdb->options, 'column' => 'option_id', ), 'comments' => array( 'table' => $wpdb->comments, 'column' => 'comment_ID', ), ); foreach ( $tables as $name => $table_info ) { $table = $table_info['table']; $column = $table_info['column']; // Construct the SQL query for row count, min and max ID in one go $query = "SELECT COUNT(*) AS count, MIN($column) AS min, MAX($column) AS max FROM $table"; // Execute the query $result = $wpdb->get_row( $query, ARRAY_A ); // Handle cases when no rows are found (like 0 comments) if ( $result['count'] == 0 ) { // If no rows found, set min and max to 0 $result['count'] = 0; $result['min'] = 0; $result['max'] = 0; } // Add the results to the table stats $wpstats[ $name ] = array( 'count' => $result['count'], 'min' => $result['min'], 'max' => $result['max'], ); } return $wpstats; } function wpmr_scan_db( $args = array() ) { $start = microtime( true ); $this->raise_limits_conditionally(); $this->set_args( $args ); if ( ! empty( $_REQUEST['table'] ) && ! empty( $_REQUEST['batchsize'] ) ) { $table = $_REQUEST['table']; $batchsize = $_REQUEST['batchsize']; $pointer = $_REQUEST['pointer']; if ( empty( $GLOBALS['WPMR']['response_debug'] ) ) { $GLOBALS['WPMR']['response_debug'] = array(); } $cpu = $this->get_server_load(); if ( empty( $cpu ) ) { $cpu = array( 0, 0, 0 ); } $cpu = array_map( function ( $num ) { return number_format( $num, 2, '.', '' ); }, $cpu ); $memory_limit = @ini_get( 'memory_limit' ); $GLOBALS['WPMR']['response_debug']['time_taken'] = ( microtime( true ) - $start ); $result = array( 'report' => $this->db_scan_batch( $table, $batchsize, $pointer ), 'memory' => ( memory_get_peak_usage( true ) / 1024 / 1024 ), 'memory_limit' => $memory_limit, 'cpu' => $cpu, 'debug' => $GLOBALS['WPMR']['response_debug'], ); if ( $this->wpmr_iscli() ) { return $result; } else { wp_send_json( $result ); } } } function get_db_checksums() { return get_option( 'WPMR_db_checksums_cache', array() ); } function db_scan_batch( $table, $batchsize, $pointer ) { global $wpdb; $batchsize = intval( $batchsize ); $pointer = intval( $pointer ); $sql = ''; $table_name = ''; switch ( $table ) { case 'posts': $sql = "SELECT `ID` AS id, `post_content` AS content, `post_type` as post_type FROM ( SELECT `ID`, `post_content`, `post_type` FROM `{$wpdb->posts}` WHERE `ID` > %d AND `ID` <= %d ORDER BY `ID` ) AS limited_rows WHERE `post_content` LIKE %s"; $table_name = $wpdb->posts; break; case 'postmeta': $sql = "SELECT `meta_id` AS id, `meta_value` AS content FROM ( SELECT `meta_id`, `meta_value` FROM $wpdb->postmeta WHERE `meta_id` > %d AND `meta_id` <= %d ORDER BY `meta_id` ) AS limited_rows WHERE `meta_value` LIKE %s"; $table_name = $wpdb->postmeta; break; case 'options': if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $all_plugins = get_plugins(); $all_plugins = get_option( 'active_plugins' ); $exclusions = array(); if ( isset( $all_plugins['gotmls/index.php'] ) ) { $exclusions[] = 'GOTMLS_definitions_array'; } if ( isset( $all_plugins['malcare-security/malcare.php'] ) || isset( $all_plugins['blogvault-real-time-backup/blogvault.php'] ) ) { $exclusions[] = 'bvruleset'; } if ( ! empty( $exclusions ) ) {// Prepare the string for SQL query $exclusions = array_map( 'esc_sql', $exclusions ); $exclusion_list = "'" . implode( "','", $exclusions ) . "'"; $sql = "SELECT `option_id` AS id, `option_value` AS content FROM ( SELECT `option_id`, `option_value` FROM $wpdb->options WHERE `option_id` > %d AND `option_id` <= %d AND `option_name` NOT IN (" . $exclusion_list . ') ORDER BY `option_id`) AS limited_rows WHERE `option_value` LIKE %s'; } else { $sql = "SELECT `option_id` AS id, `option_value` AS content FROM ( SELECT `option_id`, `option_value` FROM $wpdb->options WHERE `option_id` > %d AND `option_id` <= %d ORDER BY `option_id` ) AS limited_rows WHERE `option_value` LIKE %s"; } $table_name = $wpdb->options; break; case 'comments': $sql = "SELECT `comment_ID` AS id, `comment_content` AS content FROM ( SELECT `comment_ID`, `comment_content` FROM $wpdb->comments WHERE `comment_approved` = '1' AND `comment_ID` > %d AND `comment_ID` <= %d ORDER BY comment_ID) AS limited_rows WHERE `comment_content` LIKE %s"; $table_name = $wpdb->comments; break; default: return; } $definitions = $this->get_definitions()['definitions']['db']; $db_results = array( 'results' => array(), 'pointer' => $pointer, ); $db_scan_log = array(); if ( ! empty( $GLOBALS['WPMR']['wpmr_extra_db_query'] ) && ! empty( $GLOBALS['WPMR']['wpmr_extra_db_regex'] ) ) { $definitions['DWPMR'] = array( 'severity' => 'severe', 'query' => $GLOBALS['WPMR']['wpmr_extra_db_query'], 'signature' => $GLOBALS['WPMR']['wpmr_extra_db_regex'], ); } $results = array(); $db_checksums = $this->get_db_checksums(); $maybe_infected = array(); $hash = false; foreach ( $definitions as $ver => $details ) { $time = microtime( true ); // $raw_query = ; // Prepare the SQL query within the loop $prepared_sql = $wpdb->prepare( $sql, $pointer, $pointer + $batchsize, $this->decode( $details['query'] ) ); $results = $wpdb->get_results( $prepared_sql, ARRAY_A ); if ( ! $results ) { // get_results also returns NULL which foreach can't handle continue; } foreach ( $results as $result ) { $content = $result['content']; $hash = hash( 'sha256', $content ); if ( in_array( $hash, $db_checksums ) ) { continue; // This result was previously scanned and stored in the checksums. // No need to scan it again for this definition. // Skip this result and continue scanning the next result. } $id = sanitize_text_field( $result['id'] ); $post_status = empty( $result['post_type'] ) ? '' : 'post-type → ' . $result['post_type'] . '.'; try { if ( @preg_match( $this->decode( $details['signature'] ), '' ) === false ) { throw new Exception( 'Invalid regular expression ' . $ver . ' in ' . __FUNCTION__ ); } $matches = preg_match( $this->decode( $details['signature'] ), $content, $found ); } catch ( Exception $e ) { $this->flog( 'Faulty Signature: ' . $ver, false, false, true ); $this->flog( 'Faulty Pattern: ' . $this->decode( $details['signature'] ), false, false, true ); $this->flog( $e->getMessage(), false, false, true ); continue; } if ( $matches >= 1 ) { if ( in_array( $details['severity'], array( 'severe', 'high' ) ) ) { $this->update_setting( 'infected', true ); } $db_results['results'][ $table_name . '_' . $id ] = $this->set_status( $details['severity'], 'Found database infection(s) in ' . ucwords( $table_name ) . " id $id. $post_status Threat Level → " . $details['severity'] . '.', $ver ); $db_scan_log[ $id ] = array( 'severity' => $details['severity'], 'infection' => $ver, 'type' => ucwords( $table_name ), 'id' => $id, ); $maybe_infected[ $hash ] = 1; } else { // $maybe_infected has should only be changed to 0 if it is not 1 already if ( ! isset( $maybe_infected[ $hash ] ) ) { $maybe_infected[ $hash ] = 0; } } } // foreach $results } // foreach $definitions foreach ( $maybe_infected as $checksum => $infected ) { if ( $infected == 0 ) { $db_checksums[] = $checksum; } } $db_checksums = array_unique( $db_checksums ); update_option( 'WPMR_db_checksums_cache', $db_checksums ); switch ( $table ) { case 'posts': $sql = $wpdb->prepare( "SELECT MIN(`ID`) AS next_id FROM $wpdb->posts WHERE `ID` > %d", ( $pointer + $batchsize ) ); break; case 'postmeta': $sql = $wpdb->prepare( "SELECT MIN(`meta_id`) AS next_id FROM $wpdb->postmeta WHERE `meta_id` > %d", ( $pointer + $batchsize ) ); break; case 'options': $sql = $wpdb->prepare( "SELECT MIN(`option_id`) AS next_id FROM $wpdb->options WHERE `option_id` > %d", ( $pointer + $batchsize ) ); break; case 'comments': $sql = $wpdb->prepare( "SELECT MIN(`comment_ID`) AS next_id FROM $wpdb->comments WHERE `comment_approved` = '1' AND `comment_ID` > %d", ( $pointer + $batchsize ) ); break; default: } $new_pointer = $wpdb->get_var( $sql ); if ( ! empty( $new_pointer ) ) { $new_pointer = intval( $new_pointer ) - 1; $db_results['new_pointer'] = $new_pointer; } else { $db_results['new_pointer'] = $pointer + $batchsize; } if ( $db_results['results'] ) { $db_results['results'] = array_values( $db_results['results'] ); if ( ! empty( $db_scan_log ) && ! empty( $GLOBALS['WPMR']['timestamp'] ) ) { $record = $GLOBALS['WPMR']['timestamp']; $db_scan_log = array( 'db' => $db_scan_log ); if ( $db_scan_log !== false ) { $saved_record = get_transient( "WPMR_log_$record" ); if ( $saved_record ) { $saved_record = json_decode( $saved_record, true ); if ( ! empty( $saved_record ) ) { $db_scan_log['db'] = $saved_record['db'] + $db_scan_log['db']; } } $db_scan_log = json_encode( $db_scan_log ); set_transient( "WPMR_log_$record", $db_scan_log, 30 * DAY_IN_SECONDS ); } } return $db_results; } else { } return $db_results; } function db_scan() { global $wpdb; $scan_sqls = array( 'post' => "SELECT ID AS id, post_content AS content, post_type as post_type FROM $wpdb->posts where post_content LIKE '%s'", 'post_meta' => "SELECT meta_id AS id, meta_value AS content FROM $wpdb->postmeta where meta_value LIKE '%s'", 'option' => "SELECT option_id AS id, option_value AS content FROM $wpdb->options WHERE option_value LIKE '%s' AND option_name NOT IN ('GOTMLS_definitions_array', 'bvruleset')", 'comment' => "SELECT comment_ID AS id, comment_content AS content FROM $wpdb->comments WHERE comment_content LIKE '%s' AND comment_approved = '1'", ); $definitions = $this->get_definitions()['definitions']['db']; $db_results = array(); $db_scan_log = array(); if ( ! empty( $GLOBALS['WPMR']['wpmr_extra_db_query'] ) && ! empty( $GLOBALS['WPMR']['wpmr_extra_db_regex'] ) ) { $definitions['DWPMR'] = array( 'severity' => 'severe', 'query' => $GLOBALS['WPMR']['wpmr_extra_db_query'], 'signature' => $GLOBALS['WPMR']['wpmr_extra_db_regex'], ); } foreach ( $definitions as $ver => $details ) { foreach ( $scan_sqls as $key => $sql ) { $results = $wpdb->get_results( $wpdb->prepare( $sql, $this->decode( $details['query'] ) ), ARRAY_A ); foreach ( $results as $result ) { $content = $result['content']; $id = sanitize_text_field( $result['id'] ); $post_status = empty( $result['post_type'] ) ? '' : 'post-type → ' . $result['post_type'] . '.'; try { $matches = preg_match( $this->decode( $details['signature'] ), $content, $found ); } catch ( Exception $e ) { $this->flog( 'Faulty Signature: ' . $definition, false, false, true ); $this->flog( 'Faulty Pattern: ' . $this->decode( $signature['signature'] ), false, false, true ); $this->flog( 'Key: ' . $key, false, false, true ); $this->flog( $e->getMessage(), false, false, true ); continue; } if ( $matches >= 1 ) { if ( in_array( $details['severity'], array( 'severe', 'high' ) ) ) { $this->update_setting( 'infected', true ); } $db_results[ $id ] = $this->set_status( $details['severity'], 'Found database infection(s) in ' . ucwords( $key ) . " id $id. $post_status Threat Level → " . $details['severity'] . '.', $ver ); $db_scan_log[ $id ] = array( 'severity' => $details['severity'], 'infection' => $ver, 'type' => ucwords( $key ), 'id' => $id, ); } } } } if ( $db_results ) { if ( ! empty( $db_scan_log ) && ! empty( $GLOBALS['WPMR']['timestamp'] ) ) { $record = $GLOBALS['WPMR']['timestamp']; $db_scan_log = array( 'db' => $db_scan_log ); $db_scan_log = json_encode( $db_scan_log ); if ( $db_scan_log !== false ) { set_transient( "WPMR_log_$record", $db_scan_log, 30 * DAY_IN_SECONDS ); } } return $db_results; } return $db_results; } function wpmr_scan_files( $args = array() ) { $start = microtime( true ); $this->raise_limits_conditionally(); $this->set_args( $args ); $files = $GLOBALS['WPMR']['files']; if ( defined( 'WP_CLI' ) && WP_CLI ) { } else { @ini_set( 'max_execution_time', min( 90, count( $files ) + 1 ) ); // Don't kill if using WP CLI } // @ini_set( 'max_execution_time', max( (int) ini_get( 'max_execution_time' ), min( 90, count( $files ) ) ) ); $affected_files = array(); $registered = $this->is_registered(); $definitions = $this->get_definitions()['definitions']['files']; if ( ! empty( $GLOBALS['WPMR']['regex'] ) ) { $definitions = array_merge( array( 'DWPMR' => array( 'severity' => 'severe', 'signature' => $GLOBALS['WPMR']['regex'], 'class' => 'scripting', ), ), $definitions ); } $GLOBALS['WPMR']['definitions'] = $definitions; $GLOBALS['WPMR']['checksums'] = $this->get_checksums_values(); $GLOBALS['WPMR']['core_checksums'] = get_option( 'WPMR_checksums' ); foreach ( $files as $file ) { // $this->flog( 'Scanning file: ' . $file ); if ( is_link( $file ) || ! $this->is_scannable_file( $file ) ) { // skip if this is a directory symlink continue; } $checksum_failure = $this->fails_checksum( $file ); // $this->flog( 'Checksum failure: ' . $checksum_failure ); if ( $checksum_failure ) { $threat = 0; $threat = $this->wpmr_scan_file_threats( $file ); if ( $threat ) { $affected_files[ $file ] = $threat; } elseif ( ( $checksum_failure == 'missing' ) && ( ! $registered || $GLOBALS['WPMR']['suspicious'] ) ) { // If the scan is running without API registration $affected_files[ $file ] = array( 'id' => 'mismatch', 'severity' => 'suspicious', 'info' => 'Mismatch', ); } elseif ( $checksum_failure == 'missing' && $this->is_core_wp_file( $file ) ) { $affected_files[ $file ] = array( 'id' => 'unknown', 'severity' => 'suspicious', 'info' => 'Unknown', ); } elseif ( $GLOBALS['WPMR']['debug'] ) { } } elseif ( $GLOBALS['WPMR']['debug'] ) { } } $this->update_cached_checksums( array_diff( array_values( $files ), array_keys( $affected_files ) ) ); // This will not save checksum if the file is suspicious either $affected_files = $this->may_be_filter_suspicious( $affected_files ); if ( $affected_files ) { $log['files'] = $affected_files; if ( ! empty( $GLOBALS['WPMR']['timestamp'] ) ) { $record = get_transient( 'WPMR_log_' . $GLOBALS['WPMR']['timestamp'] ); if ( $record ) { $log = array_replace_recursive( json_decode( $record, 1 ), $log ); } $log = json_encode( $log ); set_transient( 'WPMR_log_' . $GLOBALS['WPMR']['timestamp'], $log, 30 * DAY_IN_SECONDS ); } } foreach ( $affected_files as $f => $report ) { $affected_files[ $f ] = $this->set_status( $report['severity'], $report['info'], $report['id'] ); } if ( empty( $GLOBALS['WPMR']['response_debug'] ) ) { $GLOBALS['WPMR']['response_debug'] = array(); } $cpu = $this->get_server_load(); if ( empty( $cpu ) ) { $cpu = array( 0, 0, 0 ); } $cpu = array_map( function ( $num ) { return number_format( $num, 2, '.', '' ); }, $cpu ); $memory_limit = @ini_get( 'memory_limit' ); $GLOBALS['WPMR']['response_debug']['time_taken'] = ( microtime( true ) - $start ); $result = array( 'report' => $affected_files, 'memory' => ( memory_get_peak_usage( true ) / 1024 / 1024 ), 'memory_limit' => $memory_limit, 'cpu' => $cpu, 'debug' => $GLOBALS['WPMR']['response_debug'], ); if ( $this->wpmr_iscli() ) { return $result; } else { wp_send_json( $result ); } } function get_server_load() { if ( function_exists( 'sys_getloadavg' ) ) { // This function exists, so we are likely not on Windows. return sys_getloadavg(); } elseif ( class_exists( 'COM' ) ) { // We are likely on Windows and can attempt to use COM to get the CPU load. try { $wmi = new COM( 'Winmgmts://' ); $servers = $wmi->execquery( 'SELECT LoadPercentage FROM Win32_Processor' ); $cpuNum = 0; $loadTotal = 0; foreach ( $servers as $server ) { $loadTotal += $server->LoadPercentage; ++$cpuNum; } return $cpuNum > 0 ? array( $loadTotal / $cpuNum ) : array(); } catch ( Exception $e ) { // Handle exceptions or use another method to obtain system load. return array(); } } else { // No known method to get system load, return an empty array or null. return array(); } } function delete_wpmr_logs() { global $wpdb; // Query to get all options with names like %WPMR_log_% $option_names = $wpdb->get_col( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '%WPMR_log_%'" ); // Loop through the options and delete each one foreach ( $option_names as $option_name ) { delete_option( $option_name ); } } /** * Checks if a certain path has been excluded by user-arguments * * @param [type] $path * @return boolean */ function is_excluded( $path ) { $skipdirs = $GLOBALS['WPMR']['skipdirs']; $only_scan_dirs = $GLOBALS['WPMR']['only_scan_dirs']; $path_excluded_by_skipdirs = $this->path_excluded_by_skipdirs( $path, $skipdirs ); $path_included_in_only_scan_dirs = $this->path_included_in_only_scan_dirs( $path, $only_scan_dirs ); $result = $path_excluded_by_skipdirs && ! $path_included_in_only_scan_dirs; return $result; } function path_excluded_by_skipdirs( $path, $dirs ) { $skipped = false; if ( ! empty( $dirs ) && is_array( $dirs ) ) { foreach ( $dirs as $skipdir ) { if ( preg_match( '/' . preg_quote( wp_normalize_path( $skipdir ), '/' ) . '/', $path ) ) { return true; } } } } // Check if file_path included by only_scan_dirs function path_included_in_only_scan_dirs( $path, $dirs ) { if ( empty( $dirs ) || ! is_array( $dirs ) ) { return; } $present = false; $abs_dirs = $dirs; // Check if the path to file is present in any of the only_scan_dirs foreach ( $abs_dirs as $abs_dir ) { if ( preg_match( '/' . preg_quote( wp_normalize_path( $abs_dir ), '/' ) . '/', $path ) ) { // Check if file path has matches in only_scan_dir $present = true; } } return $present; } function redirect_hijack() { if ( 'page' == get_option( 'show_on_front' ) ) { $page_on_front = get_option( 'page_on_front' ); $page_for_posts = get_option( 'page_for_posts' ); return ( ! empty( $page_on_front ) ? $this->check_redirect_hijack( get_permalink( $page_on_front ) ) : false ) || ( ! empty( $page_for_posts ) ? $this->check_redirect_hijack( get_permalink( $page_for_posts ) ) : false ); } else { return $this->check_redirect_hijack( home_url() ); } } function normalize_host( $host ) { return preg_replace( '/^www\./', '', $host ); } function check_redirect_hijack( $url, $referer = 'https://www.google.com/' ) { $url = trailingslashit( $url ); stream_context_set_default( array( 'http' => array( 'method' => 'GET', 'header' => array( 'Referer: ' . $referer ), 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'follow_location' => 0, ), ) ); $headers = @get_headers( $url, 1 ); // error control because if allow_url_fopen is not available then this fails if ( ! empty( $headers ) && is_array( $headers ) ) { preg_match( '/\d\d\d/', $headers[0], $matches ); } else { return; } $status_code = $matches[0]; if ( ( $status_code == 301 || $status_code == 302 ) && ! empty( $headers['Location'] ) ) { $r_host = parse_url( $headers['Location'], PHP_URL_HOST ); $wp_host = parse_url( $url, PHP_URL_HOST ); if ( strpos( $this->normalize_host( $wp_host ), $this->normalize_host( $r_host ) ) === false ) { return true; // redirecting to external domain } } } function title_hack() { if ( 'page' == get_option( 'show_on_front' ) ) { $page_for_posts = get_option( 'page_for_posts' ); $page_on_front = get_option( 'page_on_front' ); return $this->check_page_hack( get_permalink( $page_for_posts ) ) || $this->check_page_hack( get_permalink( $page_on_front ) ); } else { return $this->check_page_hack( home_url() ); } } function check_page_hack( $url ) { $url = add_query_arg( array( uniqid() => uniqid( '', 1 ) ), trailingslashit( $url ) ); if ( ! $content = $this->get_remote_response( $url ) ) { return false; } if ( ! $content = wp_remote_retrieve_body( $content ) ) { return false; } if ( empty( $content ) ) { return false; } libxml_use_internal_errors( true ); if ( ! class_exists( 'DOMDocument' ) ) { return false; } $dom = new DOMDocument(); $dom->loadHTML( $content ); $content = $dom->getElementsByTagName( 'title' ); foreach ( $content as $c ) { $strings[] = $c->nodeValue; } libxml_use_internal_errors( false ); return $this->check_string_hack( $strings ); } function check_string_hack( $strings = array() ) { $strings[] = get_bloginfo( 'name' ); $strings[] = get_bloginfo( 'description' ); $regexes = array( '/h[\@a]ck[3e]d.*by/is', '/[^<]*hack[3e][rd]/i' ); $infected = false; foreach ( $strings as $str ) { foreach ( $regexes as $regex ) { if ( preg_match( $regex, $str ) ) { $infected = true; } else { } } } return $infected; } function get_remote_response( $url ) { $response = wp_safe_remote_request( $url ); if ( 200 != wp_remote_retrieve_response_code( $response ) ) { return; } if ( is_wp_error( $response ) ) { return; } return $response; } /** * If a file is a part of core WordPress distro. Helps to determine if the core is affected. * * @param [type] $file * @return bool */ function is_core_wp_file( $file ) { $file = $this->normalise_path( $file ); if ( empty( $checksums ) ) { $checksums = array(); } $key = str_replace( trailingslashit( $this->normalise_path( ABSPATH ) ), '', $file ); $raw_checksums = get_option( 'WPMR_checksums' ); // RAW checksums else we may get a prefixed key from checksums $response = $this->is_in_core_wp_dir( $file ) || ( dirname( $key ) == '.' && array_key_exists( $key, $raw_checksums ) ); // If there are no slashes in path, a dot ('.') is returned return $response; } function is_in_core_wp_dir( $file ) { $file = $this->normalise_path( $file ); if ( strpos( $file, trailingslashit( $this->normalise_path( ABSPATH ) ) . 'wp-admin/' ) !== false || strpos( $file, trailingslashit( trailingslashit( $this->normalise_path( ABSPATH ) ) . WPINC ) ) !== false ) { return true; } return false; } function prioritise_core_files( $files ) { $files_c = array(); $files_wpc = array(); $files_wpr = array(); sort( $files ); foreach ( $files as $key => $file ) { if ( $this->is_in_core_wp_dir( $file ) || $this->is_in_root_dir( $file ) ) { $files_c[] = $file; } elseif ( $this->str_starts_with( $this->normalise_path( dirname( $file ) ), $this->normalise_path( WP_CONTENT_DIR ) ) ) { $files_wpc[] = $file; } elseif ( $this->str_starts_with( $this->normalise_path( dirname( $file ) ), $GLOBALS['WPMR']['home_dir'] ) ) { $files_wpr[] = $file; } } // Implement chunked sorting for memory efficiency $files = $this->chunked_natcasesort( $files ); $files_c = $this->chunked_natcasesort( $files_c ); $files = array_merge( array_values( $files_c ), array_values( $files_wpc ), array_values( $files_wpr ), array_values( $files ) ); $files = array_unique( $files ); return array( 'files' => $files ); } /** * Perform chunked natcasesort on large arrays to avoid memory exhaustion. * * @param array $array The array to be sorted. * @param int $chunk_size The size of each chunk. Defaults to 1000. * @return array The sorted array. */ function chunked_natcasesort( $array, $chunk_size = 1000 ) { $chunked_array = array_chunk( $array, $chunk_size ); $sorted_array = array(); foreach ( $chunked_array as $chunk ) { natcasesort( $chunk ); $sorted_array = array_merge( $sorted_array, $chunk ); } return $sorted_array; } function is_in_root_dir( $file ) { return $this->normalise_path( ABSPATH ) === $this->normalise_path( dirname( $file ) ); } function process_only_scan_dirs( $files ) { if ( ! empty( $GLOBALS['WPMR']['only_scan_dirs'] ) ) { // Always scan core $GLOBALS['WPMR']['only_scan_dirs'][] = trailingslashit( ABSPATH ) . 'wp-admin'; $GLOBALS['WPMR']['only_scan_dirs'][] = trailingslashit( ABSPATH ) . WPINC; foreach ( $files as $k => $file ) { if ( ! $this->path_begins_with_any( $file, $GLOBALS['WPMR']['only_scan_dirs'] ) ) { unset( $files[ $k ] ); } } } return $files; } function path_begins_with_any( $path, $arr_dirs ) { foreach ( $arr_dirs as $dir ) { if ( $this->str_starts_with( $path, $dir ) ) { return 1; } } } function str_starts_with( $string, $startswith ) { return ( strpos( (string) $string, (string) $startswith ) === 0 ); // we are not looking for occurance but specifically the begining } function wpmr_skip_dir( $path ) { if ( $path == untrailingslashit( $this->normalise_path( ABSPATH ) ) ) { return false; } if ( ! empty( $GLOBALS['WPMR']['skipdirs'] ) && in_array( basename( $this->normalise_path( $path ) ), $GLOBALS['WPMR']['skipdirs'] ) ) { return true; } if ( file_exists( $this->normalise_path( $path . DIRECTORY_SEPARATOR . '.mcignore' ) ) ) { return true; } // A simple way to check if a symlink recursive is to check if it is pointing to a parent directory // recursive /parent/subdir/path (points to) -> /parent/subdir/ // recursive /parent/subdir/path (points to) -> /parent/somedir/ if ( is_link( $path ) ) { // readlink( realpath DOESNT WORK. STAY AWAY FROM IT. Readlink only reads symlinks. Not real paths $link = $this->normalise_path( dirname( $path ) . DIRECTORY_SEPARATOR . basename( $path ) ); $target = $this->normalise_path( $link ); // use realpath. readlink result can be another symlink if ( $target && str_starts_with( $link, $target ) ) { return true; } } return false; } function return_all_files( $path = false ) { // It takes around 4 to 10 seconds to generate a list of files. // If the calls are made simultaneously, the list is generated multiple times. // It must be possible to store this in a short transient. // TBD // $time = microtime( true ); $files = $this->get_all_files( $path ); return $files; } function get_all_files( $path = false ) { if ( ! $path ) { $path = ( ! empty( $GLOBALS['WPMR']['home_dir'] ) ) ? wp_normalize_path( $GLOBALS['WPMR']['home_dir'] ) : ABSPATH; if ( empty( $path ) ) { $this->flog( 'Failed to get path.', false, false, true ); return array(); } } $path = untrailingslashit( wp_normalize_path( $path ) ); // This could be a symlink or whatever. Let's not touch it before testing via wpmr_skip_dir // $path = untrailingslashit( $this->realpath( $path ) ); // NEVER DO THIS ELSE WE CAN'T TEST SYMLINK RECURSION. Dont confuse readlink vs realpath $wpmr_skip_dir = apply_filters( 'wpmr_skip_dir', $path ); if ( ! $wpmr_skip_dir ) { $children = @scandir( $path ); if ( is_array( $children ) ) { $children = array_diff( $children, array( '..', '.' ) ); $files = array(); foreach ( $children as $child ) { $target = trailingslashit( wp_normalize_path( $path ) ) . $child; if ( is_dir( $target ) ) { $elements = $this->get_all_files( $target ); if ( $elements ) { foreach ( $elements as $element ) { $files[] = $this->normalise_path( $element ); } } } if ( $this->is_scannable_file( $target ) ) { $files[] = $this->normalise_path( $target ); } } return $files; } else { $this->flog( "Failed to read directory: {$path}" ); return array(); } } else { return array(); } } function normalise_path( $path ) { $realpath = wp_normalize_path( realpath( $path ) ); if ( $realpath ) { return $realpath; } return wp_normalize_path( $path ); } function is_scannable_file( $file ) { $file = $this->normalise_path( $file ); $return = ( file_exists( $file ) && is_file( $file ) && ( filesize( $file ) || ! is_readable( $file ) ) && filesize( $file ) <= $this->maxsize ); return $return; } function is_valid_file( $file ) { return ! $this->is_invalid_file( $file ); } function is_invalid_file( $file ) { $file = $this->normalise_path( $file ); return ( ! file_exists( $file ) || ! is_readable( $file ) || ! is_file( $file ) || ! filesize( $file ) || filesize( $file ) > $this->maxsize ); } function glob_files( $path = false ) { if ( ! $path ) { $path = ABSPATH; if ( empty( $path ) ) { return array(); } $path = untrailingslashit( $path ); } // $allfiles = new RecursiveIteratorIterator( // new RecursiveDirectoryIterator( // $path, // RecursiveDirectoryIterator::SKIP_DOTS | // RecursiveDirectoryIterator::FOLLOW_SYMLINKS | // RecursiveDirectoryIterator::KEY_AS_PATHNAME // ), // RecursiveIteratorIterator::SELF_FIRST, // RecursiveIteratorIterator::CATCH_GET_CHILD // ); $allfiles = new RecursiveDirectoryIterator( $path, RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::FOLLOW_SYMLINKS | RecursiveDirectoryIterator::KEY_AS_PATHNAME ); $files = new RecursiveCallbackFilterIterator( $allfiles, function ( $current, $key, $iterator ) { $this->llog( $current ); return true; if ( $iterator->hasChildren() && $current->isFile() ) { return true; } else { return false; } // return $current->isFile(); } ); $this->llog( $files ); $it = new RecursiveIteratorIterator( $files, RecursiveIteratorIterator::SELF_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD ); $this->llog( $it ); foreach ( $it as $k => $v ) { print_r( $k ); print_r( $v ); } return; // $allfiles = iterator_to_array( $allfiles, 1 ); $files = array(); foreach ( $allfiles as $k => $v ) { if ( is_file( $k ) ) { // $files[] = $k; $this->llog( $k ); } // print_r( $k ); } return $files; $files = array(); foreach ( new RecursiveDirectoryIterator( $allfiles ) as $filename => $cur ) { $this->flog( $filename ); $files[] = $filename; } sort( $files ); return $files; } /** * Undocumented function * * @param [type] $local_file * @return * falsey: All well */ function fails_checksum( $local_file ) { if ( $this->is_invalid_file( $local_file ) ) { if ( $this->is_scannable_file( $local_file ) ) { return true; } return; } if ( ! empty( $GLOBALS['WPMR']['regex'] ) ) { remove_filter( 'serve_checksums', array( $this, 'get_cached_checksums' ) ); } $hash = @hash_file( 'sha256', $local_file ); // ensure that for files in core wp dir, only the respective checksums are matched. if ( $this->is_in_core_wp_dir( $local_file ) ) { $checksums = $GLOBALS['WPMR']['core_checksums']; } else { // file not in core wp dir $checksums = $GLOBALS['WPMR']['checksums']; } // $checksums = $GLOBALS['WPMR']['checksums']; // testing if file is shown on rescan if ( ! in_array( $hash, $checksums ) || $this->is_file_blacklisted( $hash ) ) { return 'missing'; } return; } function is_file_blacklisted( $checksum ) { return in_array( $checksum, array( 'ada53f04e24f787f126d5e7d07f42425a780f3d76f12c6d11e9535ed5106b94a' ) ); } function update_cached_checksums( $clean_files ) { if ( empty( $clean_files ) ) { return; } $checksums = $this->get_checksums(); $new_checksums = array(); foreach ( $clean_files as $file ) { if ( empty( $checksums[ $file ] ) && ! $this->is_invalid_file( $file ) ) { $sha256 = @hash_file( 'sha256', $file ); if ( ! empty( $sha256 ) ) { $new_checksums[ $file ] = $sha256; } } } $cached_checksums = get_option( 'WPMR_files_checksums_cache' ); if ( $cached_checksums && is_array( $cached_checksums ) && ! empty( $cached_checksums ) ) { update_option( 'WPMR_files_checksums_cache', array_merge( $cached_checksums, $new_checksums ) ); } else { update_option( 'WPMR_files_checksums_cache', $new_checksums ); } } function get_checksums_values() { $checksums = $this->get_checksums(); $checksum_values = array_values( $checksums ); $checksum_values = array_unique( $checksum_values, SORT_REGULAR ); return $checksum_values; } function get_cached_checksums( $checksums ) { if ( ! empty( $GLOBALS['WPMR']['debug'] ) ) { // This element doesn't exist on page load, only exists on ajax requests return $checksums; } $cached_checksums = get_option( 'WPMR_files_checksums_cache' ); if ( $cached_checksums && is_array( $cached_checksums ) && ! empty( $cached_checksums ) ) { return array_merge( $checksums, $cached_checksums ); } return $checksums; } // Deletes generated checksums. Should only be done if we have newer definitions available. function delete_generated_checksums() { delete_option( 'WPMR_files_checksums_cache' ); delete_option( 'WPMR_db_checksums_cache' ); } // Deletes core checksums. Should only be done if any WP updates are triggered. function delete_core_checksums( $upgrader, $hook_extra ) { $this->flog( '$upgrader' ); $this->flog( $upgrader ); $this->flog( '$hook_extra' ); $this->flog( $hook_extra ); delete_option( 'WPMR_checksums' ); } function get_locale() { $file = ABSPATH . WPINC . '/version.php'; if ( $this->is_valid_file( $file ) ) { $code = file_get_contents( $file ); } if ( preg_match( '/\$wp_local_package\s*=\s*[\'"](.*?)[\'"]\s*;/', $code, $matches ) ) { $locale = $matches[1]; } else { $locale = 'en_US'; } return $locale; } function get_checksums( $cached = true ) { $this->raise_limits_conditionally(); $checksums = get_option( 'WPMR_checksums' ); if ( ! $checksums || ! $cached ) { global $wp_version; $checksums = $this->sha256_get_core_checksums( $wp_version, $this->get_locale() ); if ( ! $checksums ) { $checksums = array(); $checksums = $this->sha256_get_core_checksums( $wp_version ); // defaults to en_US if ( ! checksums ) { } } $plugin_checksums = $this->get_plugin_checksums(); if ( $plugin_checksums ) { $checksums = array_merge( $checksums, $plugin_checksums ); } else { } $theme_checksums = $this->get_theme_checksums(); if ( $theme_checksums ) { $checksums = array_merge( $checksums, $theme_checksums ); } else { } if ( $checksums ) { update_option( 'WPMR_checksums', $checksums ); } else { } } return apply_filters( 'serve_checksums', $checksums ); } function sha256_get_core_checksums( $ver = false, $locale = 'en_US' ) { $state = $this->get_setting( 'user' ); $state = $this->encode( $state ); global $wp_version; if ( ! $ver ) { $ver = $wp_version; } $checksum_url = WPMR_SERVER . '?wpmr_action=wpmr_checksum&slug=wordpress&version=' . $ver . '&locale=' . $locale . '&type=core&state=' . $state; $core_checksums = array(); $checksum = wp_safe_remote_get( $checksum_url ); if ( is_wp_error( $checksum ) ) { return; } if ( '200' != wp_remote_retrieve_response_code( $checksum ) ) { return; } $checksum = wp_remote_retrieve_body( $checksum ); $checksum = json_decode( $checksum, true ); if ( ! is_null( $checksum ) && ! empty( $checksum['files'] ) ) { $checksum = $checksum['files']; foreach ( $checksum as $file => $checksums ) { $core_checksums[ $file ] = $checksums['sha256']; } } return $core_checksums; } function get_plugin_checksums() { $missing = array(); $all_plugins = get_plugins(); $install_path = ABSPATH; $plugin_checksums = array(); foreach ( $all_plugins as $key => $value ) { if ( false !== strpos( $key, '/' ) ) { // plugin has to be inside a directory. currently drop in plugins are not supported $plugin_file = trailingslashit( dirname( $this->dir ) ) . $key; $plugin_file = str_replace( $install_path, '', $plugin_file ); $checksum_url = 'https://downloads.wordpress.org/plugin-checksums/' . dirname( $key ) . '/' . $value['Version'] . '.json'; $checksum = wp_safe_remote_get( $checksum_url ); if ( is_wp_error( $checksum ) ) { continue; } if ( '200' != wp_remote_retrieve_response_code( $checksum ) ) { if ( '404' == wp_remote_retrieve_response_code( $checksum ) ) { $missing[ $key ] = array( 'Version' => $value['Version'] ); } continue; } $checksum = wp_remote_retrieve_body( $checksum ); $checksum = json_decode( $checksum, true ); if ( ! is_null( $checksum ) && ! empty( $checksum['files'] ) ) { $checksum = $checksum['files']; foreach ( $checksum as $file => $checksums ) { $plugin_checksums[ trailingslashit( dirname( $plugin_file ) ) . $file ] = $checksums['sha256']; } } } else { } } $extras = $this->get_pro_checksums( $missing ); if ( $extras ) { $plugin_checksums = array_merge( $plugin_checksums, $extras ); } return $plugin_checksums; } function get_theme_checksums() { $all_themes = wp_get_themes(); $install_path = ABSPATH; $theme_checksums = array(); $theme_root = get_theme_root(); $state = $this->get_setting( 'user' ); $state = $this->encode( $state ); foreach ( $all_themes as $key => $value ) { $theme_file = trailingslashit( $theme_root ) . $key; $theme_file = str_replace( $install_path, '', $theme_file ); $checksum_url = WPMR_SERVER . '?wpmr_action=wpmr_checksum&slug=' . $key . '&version=' . $value['Version'] . '&type=theme&state=' . $state; $checksum = wp_safe_remote_get( $checksum_url ); if ( is_wp_error( $checksum ) ) { continue; } if ( '200' != wp_remote_retrieve_response_code( $checksum ) ) { continue; } $checksum = wp_remote_retrieve_body( $checksum ); $checksum = json_decode( $checksum, true ); if ( ! is_null( $checksum ) && ! empty( $checksum['files'] ) ) { $checksum = $checksum['files']; foreach ( $checksum as $file => $checksums ) { $theme_checksums[ trailingslashit( dirname( $theme_file ) ) . $file ] = $checksums['sha256']; } } } return $theme_checksums; } function get_pro_checksums( $missing ) { if ( empty( $missing ) || ! $this->is_registered() || ! $this->is_advanced_edition() ) { // can't burden our server return; } $state = $this->get_setting( 'user' ); $state = $this->encode( $state ); $all_plugins = $missing; $install_path = ABSPATH; $plugin_checksums = array(); foreach ( $all_plugins as $key => $value ) { if ( false !== strpos( $key, '/' ) ) { // plugin has to be inside a directory. currently drop in plugins are not supported $plugin_file = trailingslashit( dirname( $this->dir ) ) . $key; $plugin_file = str_replace( $install_path, '', $plugin_file ); $checksum_url = WPMR_SERVER . '?wpmr_action=wpmr_checksum&slug=' . dirname( $key ) . '&version=' . $value['Version'] . '&type=plugin&state=' . $state; $checksum = wp_safe_remote_get( $checksum_url ); if ( is_wp_error( $checksum ) ) { continue; } if ( '200' != wp_remote_retrieve_response_code( $checksum ) ) { continue; } $checksum = wp_remote_retrieve_body( $checksum ); $checksum = json_decode( $checksum, true ); if ( ! is_null( $checksum ) && ! empty( $checksum['files'] ) ) { $checksum = $checksum['files']; foreach ( $checksum as $file => $checksums ) { $plugin_checksums[ trailingslashit( dirname( $plugin_file ) ) . $file ] = $checksums['sha256']; } } } else { } } return $plugin_checksums; } /** * Maps core checksums to the actual WP install path (ABSPATH) * * @param [type] $checksums * @return void */ function map_core_checksums( $checksums ) { $real_abspath = trailingslashit( $this->normalise_path( ABSPATH ) ); foreach ( $checksums as $f => $c ) { $checksums[ $real_abspath . $f ] = $c; unset( $checksums[ $f ] ); } return $checksums; } function checksums_delete_invalid() { $cached_checksums = get_option( 'WPMR_files_checksums_cache' ); if ( ! $cached_checksums || ! is_array( $cached_checksums ) ) { return; } foreach ( $cached_checksums as $file => $checksum ) { if ( $this->is_invalid_file( $file ) ) { unset( $cached_checksums[ $file ] ); } } update_option( 'WPMR_files_checksums_cache', $cached_checksums ); } function is_file_binary( $file ) { $start = microtime( true ); $result = ( strpos( $this->get_file_type( $file ), 'binary' ) !== false ); $GLOBALS['WPMR']['response_debug'][ __FUNCTION__ ] = ( microtime( true ) - $start ); return $result; } function get_file_type( $file ) { if ( function_exists( 'exec' ) && ! $this->is_invalid_file( $file ) ) { $start_time = microtime( true ); $out = exec( 'file -b --mime-encoding ' . escapeshellarg( $file ), $output, $return ); if ( ! empty( $out ) ) { return $out; } } } function request_file_scan_threats( $file ) { $this->flog( __FUNCTION__ . ' for ' . $file ); $url = admin_url( 'admin-ajax.php' ); $host = parse_url( $url, PHP_URL_HOST ); $local_url = str_replace( $host, 'localhost', $url ); $handshake_key = md5( $file ); $this->flog( __FUNCTION__ . ' set handshake key to ' . $handshake_key . ' for file ' . $file ); $this->update_setting( 'scan_handshake_key', array( 'key' => $handshake_key, 'time' => time(), ) ); $response = wp_remote_post( $local_url, array( 'httpversion' => '1.1', 'compress' => true, 'sslverify' => false, 'timeout' => $this->timeout, 'headers' => array( 'Host' => $host ), 'body' => array( 'file' => $file, 'action' => 'wpmr_file_scan_threats', ), ) ); $this->delete_setting( 'scan_handshake_key' ); if ( is_wp_error( $response ) || empty( $response['body'] ) || wp_remote_retrieve_response_code( $response ) != 200 ) { $this->flog( $response ); return array( 'id' => 'failure', 'severity' => 'skipped', 'info' => 'Invalid scan response', ); } else { $body = wp_remote_retrieve_body( $response ); $result = json_decode( $body, 1 ); if ( ! is_null( $result ) && ! empty( $result['success'] ) ) { if ( ! empty( $result['data'] ) ) { return $result['data']; } else { return array(); } } else { return array( 'id' => 'failure', 'severity' => 'skipped', 'info' => 'Invalid scan response', ); } } } function response_file_scan_threats() { if ( ! empty( $_REQUEST['file'] ) ) { $scan_handshake_key = $this->get_setting( 'scan_handshake_key' ); $key = $scan_handshake_key['key']; $time = $scan_handshake_key['time']; if ( $key === md5( $_REQUEST['file'] ) ) { $result = $this->wpmr_scan_file_threats( $_REQUEST['file'] ); $scan_handshake_key_now = $this->get_setting( 'scan_handshake_key' ); if ( ( md5( $_REQUEST['file'] ) !== $scan_handshake_key_now['key'] ) || ( time() - $scan_handshake_key_now['time'] >= $this->timeout ) ) { die(); } elseif ( ! empty( $result ) ) { wp_send_json_success( $result ); } else { wp_send_json_success(); } } } } function wpmr_scan_file_threats( $file ) { if ( $this->is_invalid_file( $file ) ) { if ( ! is_readable( $file ) ) { // return early so that error is displayed return array( 'id' => 'unreadable', 'severity' => 'skipped', 'info' => 'Error Reading File', ); } return; } $ext = $this->get_fileext( $file ); $tests = array(); if ( $this->is_invalid_file( $file ) || ! ( $GLOBALS['WPMR']['tmp']['file_contents'] = @file_get_contents( $file ) ) ) { return; } if ( $GLOBALS['WPMR']['tmp']['file_contents'] === false ) { return array( 'id' => 'unreadable', 'severity' => 'skipped', 'info' => 'Error Reading File', ); } if ( empty( $GLOBALS['WPMR']['tmp']['file_contents'] ) ) { return; } $definitions = $GLOBALS['WPMR']['definitions']; if ( strpos( @ini_get( 'disable_functions' ), 'ini_set' ) === false ) { @ini_set( 'pcre.backtrack_limit', 1000000 ); } foreach ( $definitions as $definition => $signature ) { if ( $signature['class'] == 'htaccess' && $ext != 'htaccess' ) { continue; } try { if ( @preg_match( $this->decode( $signature['signature'] ), '' ) === false ) { throw new Exception( 'Invalid regular expression ' . $definition . ' in ' . __FUNCTION__ ); } $matches = preg_match( $this->decode( $signature['signature'] ), $GLOBALS['WPMR']['tmp']['file_contents'], $found ); } catch ( Exception $e ) { $this->flog( 'Faulty Signature: ' . $definition, false, false, true ); $this->flog( 'Faulty Pattern: ' . $this->decode( $signature['signature'] ), false, false, true ); $this->flog( 'File: ' . $file, false, false, true ); $this->flog( $e->getMessage(), false, false, true ); continue; } if ( $matches >= 1 ) { if ( in_array( $signature['severity'], array( 'severe', 'high' ) ) ) { $this->update_setting( 'infected', true ); } return array( 'id' => $definition, 'severity' => $signature['severity'], 'info' => $signature['severity'], ); } } } function may_be_filter_suspicious( $files ) { foreach ( $files as $file => $val ) { if ( $val['severity'] == 'suspicious' && $val['id'] != 'unknown' && ! $GLOBALS['WPMR']['suspicious'] ) { unset( $files[ $file ] ); } } return $files; } function whitelist( $checksums ) { $whitelist = is_array( $this->get_setting( 'whitelist' ) ) ? $this->get_setting( 'whitelist' ) : array(); if ( is_array( $whitelist ) && ! empty( $whitelist ) && $this->is_advanced_edition() ) { return array_merge( $checksums, $whitelist ); } return $checksums; } /** * Undocumented function * * @param [type] $severity: severity * @param [type] $msg: test message * @param boolean $attr: extra atributes for html * @param string $type: unused * @param string $sig: sig of the match * @return void */ function set_status( $severity, $msg, $ver ) { $msg = strip_tags( $msg ); if ( ! $this->wpmr_iscli() ) { return array( 'severity' => $severity, 'message' => $msg, 'signature' => $ver, ); } else { return array( 'severity' => $severity, 'message' => $msg, 'signature' => $ver, ); } } function get_excluded() { return apply_filters( 'wpmr_excluded_ext', array( '7z', 'bmp', 'bz2', 'css', 'doc', 'docx', 'exe', 'fla', 'flv', 'gif', 'gz', 'ico', 'jpeg', 'jpg', 'less', 'mo', 'mov', 'mp3', 'mp4', 'pdf', 'png', 'po', 'pot', 'ppt', 'pptx', 'psd', 'rar', 'scss', 'so', 'svg', 'tar', 'tgz', 'tif', 'tiff', 'ttf', 'txt', 'webp', 'wmv', 'z', 'zip' ) ); } function get_fileext( $filename ) { $nameparts = explode( '.', ".$filename" ); return strtolower( $nameparts[ ( count( $nameparts ) - 1 ) ] ); } function reset_flog() { $file = $this->dir . 'log.log'; file_put_contents( $file, '', LOCK_EX ); } function flog( $data, $file = 'log.log', $timestamp = false, $force = 0 ) { if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || ! empty( $force ) ) { if ( empty( $file ) ) { $file = 'log.log'; } // $file = $this->dir . sanitize_text_field( $file ); // $file = wp_normalize_path( $this->dir . sanitize_file_name( $file ) ); $file = $this->dir . basename( sanitize_file_name( $file ) ); if ( $timestamp ) { $date = date( 'Ymd-G:i:s' ) . '-' . microtime( true ); file_put_contents( $file, $date . PHP_EOL, FILE_APPEND | LOCK_EX ); } $data = print_r( $data, true ); $result = file_put_contents( $file, $data . PHP_EOL, FILE_APPEND | LOCK_EX ); if ( $result === false ) { error_log( 'Failed to write to log file: ' . $file ); } } } function plugin_update_message( $data, $response ) { $changelog = 'https://plugins.trac.wordpress.org/browser/' . basename( $this->dir ) . '/trunk/readme.txt?format=txt&cachebust=' . time(); // should translate into https://plugins.trac.wordpress.org/browser/wp-malware-removal/trunk/readme.txt?format=txt since repo doesn't allow changing slugs $res = wp_safe_remote_get( $changelog ); if ( is_wp_error( $res ) ) { return; } $res = wp_remote_retrieve_body( $res ); $regexp = '~==\s*Changelog\s*==\s*=\s*[0-9.]+\s*=(.*)(=\s*' . preg_quote( $this->plugin_data['Version'] ) . '\s*=|$)~Uis'; if ( ! preg_match( $regexp, $res, $matches ) ) { return; } $changelog = (array) preg_split( '~[\r\n]+~', trim( $matches[1] ) ); $upgrade_notice = ''; foreach ( $changelog as $index => $line ) { if ( preg_match( '~^\s*\*\s*~', $line ) ) { $line = preg_replace( '~^\s*\*\s*~', '', htmlspecialchars( $line ) ); $upgrade_notice .= '<span style="font-weight:bold;">★</span> ' . $line . '<br />'; } else { } } $upgrade_notice = '<strong>Upgrading is a must to ensure that this plugin works with the latest signatures.</strong><br />' . $upgrade_notice; echo '<br /><br /><span style="display:block; border: 1px solid hsl(200, 100%, 80%); padding: 1em; background: hsl(200, 100%, 90%); line-height:2">' . $upgrade_notice . '</span>'; } function ajax_shuffle_salts() { WP_Filesystem(); global $wp_filesystem; $config_path = $this->get_wp_config_path(); if ( ! $config_path ) { return 'Failed to get location of wp-config.php'; } $is_writable = $wp_filesystem->is_writable( $config_path ); if ( ! $is_writable ) { return 'wp-config.php is not writable.'; } $config = $wp_filesystem->get_contents( $config_path ); $defines = array( 'AUTH_KEY', 'SECURE_AUTH_KEY', 'LOGGED_IN_KEY', 'NONCE_KEY', 'AUTH_SALT', 'SECURE_AUTH_SALT', 'LOGGED_IN_SALT', 'NONCE_SALT', ); foreach ( $defines as $define ) { if ( empty( $salts ) ) { $salts = $this->generate_salt(); } $salt = array_pop( $salts ); if ( empty( $salt ) ) { $salt = wp_generate_password( 64, true, true ); } $salt = str_replace( '$', '\\$', $salt ); $regex = "/(define\s*\(\s*(['\"])$define\\2\s*,\s*)(['\"]).+?\\3(\s*\)\s*;)/"; $config = preg_replace( $regex, "\${1}'$salt'\${4}", $config ); } if ( $wp_filesystem->put_contents( $config_path, $config ) ) { return '<p>Successfully shuffled WordPress salts. You\'ll need to login again to continue.</p>'; } else { return '<p>Failed to write to wp-config.php.</p>'; } return '<p>Failed to get wp-config.php location or it is not writable.</p>'; } function generate_salt() { try { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|'; $max = strlen( $chars ) - 1; for ( $i = 0; $i < 8; $i++ ) { $key = ''; for ( $j = 0; $j < 64; $j++ ) { $key .= substr( $chars, random_int( 0, $max ), 1 ); } $secret_keys[] = $key; } } catch ( Exception $ex ) { $secret_keys = wp_remote_get( 'https://api.wordpress.org/secret-key/1.1/salt/' ); if ( is_wp_error( $secret_keys ) ) { $secret_keys = array(); for ( $i = 0; $i < 8; $i++ ) { $secret_keys[] = wp_generate_password( 64, true, true ); } } else { $secret_keys = explode( "\n", wp_remote_retrieve_body( $secret_keys ) ); foreach ( $secret_keys as $k => $v ) { $secret_keys[ $k ] = substr( $v, 28, 64 ); } } } return $secret_keys; } /** * Processes AJAX requests for the `wpmr_ajax_request` action. * * Validates the request using a nonce, checks user permissions, dynamically * calls the appropriate class method, and returns a JSON response. * * @return void Outputs a JSON response using wp_send_json, wp_send_json_success, * or wp_send_json_error. */ function wpmr_ajax_request() { check_ajax_referer( 'wpmr_ajax_data', 'wpmr_ajax_data_nonce' ); if ( ! current_user_can( $this->cap ) ) { wp_send_json_error( 'Not allowed.' ); } if ( empty( $_REQUEST['request'][0] ) || ! is_callable( array( $this, $_REQUEST['request'][0], ) ) ) { $this->flog( 'not callable', false, false, true ); $this->flog( $_REQUEST['request'][0], false, false, true ); wp_send_json_error( 'Not sure what to do.', false, false, true ); } if ( ! empty( $_REQUEST['request'][1] ) ) { if ( ! is_array( $_REQUEST['request'][1] ) ) { $this->flog( 'Arguments must be an array. You passed ' . gettype( $_REQUEST['request'][1] ) . ' "' . print_r( $_REQUEST['request'][1], 1 ) . '"', false, false, true ); wp_send_json_error( 'Arguments must be an array. You passed ' . gettype( $_REQUEST['request'][1] ) . ' "' . print_r( $_REQUEST['request'][1], 1 ) . '"' ); } $result = call_user_func_array( array( $this, $_REQUEST['request'][0] ), wp_unslash( $_REQUEST['request'][1] ) // args i.e. $_REQUEST['request'][1] should be unslashed ); } else { $result = call_user_func( array( $this, $_REQUEST['request'][0] ) ); } if ( is_null( $result ) || $result === true ) { // Function did it's job; may be silently or returned true. Simply return success. wp_send_json_success( $result ); } else { // We don't know if the message is a success or an error. Simply return the message. wp_send_json( $result ); } } function scripts() { $screen = get_current_screen(); if ( preg_match( '/toplevel_page_wpmr/', $screen->id ) ) { // use preg_match to allow multisite ?> <script type="text/javascript"> //<![CDATA[ function millisToMinutesAndSeconds(millis) { var minutes = Math.floor(millis / 60000); var seconds = ((millis % 60000) / 1000).toFixed(0); return minutes + ":" + (seconds < 10 ? '0' : '') + seconds; } function file_inspect_handler(event) { $ = jQuery.noConflict(); event.preventDefault(); file = $(this).attr('data-file').trim(); $('#wpmr_inspect_file').scrollTop(0); $('#wpmr_inspect_file').val(''); if (file) { wpmr_inspect_file = { wpmr_inspect_nonce: '<?php echo wp_create_nonce( 'wpmr_inspect_file' ); ?>', action: "wpmr_inspect_file", file: btoa(file) }; $.ajax({ url: ajaxurl, method: 'GET', data: wpmr_inspect_file, complete: function (jqXHR, textStatus) { $('#wpmr_inspect_box').removeClass('closed'); $('#inspect_file_path').html('<strong>File:</strong> <code>' + file + '</code>'); if (textStatus == 'success') { if (jqXHR.hasOwnProperty('responseJSON')) { // proper json will have success and data vars. $('#wpmr_inspect_file').val(jqXHR.responseJSON.data); $('#wpmr_inspect_file').attr('data-file', file); } else { $('#wpmr_inspect_file').val('Invalid response from server!'); } } else { // ajax failed $('#wpmr_inspect_file').val('Request failed!'); } $('html,body').animate({ scrollTop: $('#wpmr_inspect_box').offset().top }, 'slow'); }, }); } } function handle_whitelist_labels($) { whitelist = $('#whitelist [data-file]'); if (whitelist.length) { $('#whitelist-present-placeholder').show(); $('#whitelist-absent-placeholder').hide(); } else { $('#whitelist-present-placeholder').hide(); $('#whitelist-absent-placeholder').show(); } } function flash_file_op($) { $('#file_op_status').fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500); } function highlight_results($) { setTimeout(function () { $('html,body').animate({ scrollTop: $('#wpmr_cta_wrap').offset().top - 200 }, 'slow'); }, 1000); } function show_cta_severe($) { $('#wpmr_cta_wrap').show(); // console.log( 'Severe count: ' + severe ); $('#service_cta').html(''); if ($('#cta_severe').length == 0) { $('#percent').addClass('severe'); $('#service_cta').html('<div id="cta_severe"><h3 class="mc_center heading"><span class="brandname">Malcure</span> Detected ' + severe + ' Severe Infection(s)</h3><p class="mc_center" class="wpmr_no_copy"><a class="malcure-button-primary wpmr_no_copy" title="If you are stuck or need a professional to resolve the malware issue, you can avail Malcure\'s WordPress Malware Removal Service." href="https://www.malcure.com/?p=107&utm_source=ctaseverecleanup&utm_medium=web&utm_campaign=wpmr" target="_blank" rel="noopener noreferrer">Request Malware Cleanup →</a></p></div>'); if (!highlight_cta) { highlight_results($); highlight_cta = 1; } } } function show_cta_suspicious($) { $('#wpmr_cta_wrap').show(); console.log('Suspicious count: ' + suspicious); $('#service_cta').html(''); if ($('#cta_suspicious').length == 0) { $('#percent').addClass('suspicious'); $('#service_cta').html('<div id="cta_suspicious"><h3 class="mc_center heading"><span class="brandname">Malcure</span> Detected ' + suspicious + ' Suspicious Incident(s)</h3><p class="mc_center wpmr_no_copy"><a class="malcure-button-primary wpmr_no_copy" href="https://www.malcure.com/?p=107&utm_source=ctasuspiciouscleanup&utm_medium=web&utm_campaign=wpmr" target="_blank" rel="noopener noreferrer">Get Help With Malware Cleanup →</a></p></div>'); if (!highlight_cta) { highlight_results($); highlight_cta = 1; } } } function is_full_scan() { return full_scan; } function show_cta_voila($) { $('#wpmr_cta_wrap').show(); if ($('#cta_logo_contribute').length == 0) { msgvoila = ''; msg = ''; if (!is_full_scan()) { msgvoila = 'You selected to skip some scans. Unable to detect.'; clsvoila = 'unclear'; msg = '<p>Malcure couldn\'t detect malware.</p><p><strong>Please do a complete scan.</strong></p>'; } else { msgvoila = 'No Malware Found!'; clsvoila = 'clear'; msg = '<p>Congratulations! <strong>Malcure</strong> didn\'t detect any malware.</p><p><strong>Love this plugin?</strong></p><p><a class="cta_btn" target="_blank" href="https://wordpress.org/support/plugin/wp-malware-removal/reviews/">Give Us A Rating →<span class="emoji">⭐⭐⭐⭐⭐</span></a></p>'; } d = new Date().toUTCString(); v = '<div id="cta_logo_contribute"><h1 class="premium" style="font-weight:bold;color:hsl(120, 50%, 40%);">Your WordPress Site is Clean</h1><h2 style="font-weight: 500;color: hsl(0deg 0% 100%);font-variant: small-caps;background: hsl(120, 50%, 40%);display: table;width: auto;margin: 10px auto;padding: 4px 8px;">Scanned On: ' + d + '</h2><h1 style="font-weight: 400;font-size: 1.618em;">Share some love!</h1><h2><span class="brandname">Malcure</span> is Premium Security for Free!</h2><div class="has-2-columns love"><div class="column aligncenter"><p>Support Development</p><p><a class="cta_btn" target="_blank" href="https://www.converticacommerce.com?item_name=Donation%20for%20malCure%20Malware%20Scanner&cmd=_xclick&currency_code=USD&business=shivanand@converticacommerce.com">Donate →</a></p></div><div class="column aligncenter"><p>Or take a moment and…</p><p><a class="cta_btn" target="_blank" href="https://wordpress.org/support/plugin/wp-malware-removal/reviews/#new-post"><span class="rating">⭐⭐⭐⭐⭐</span> Rate this plugin →</a></p></div></div></div>'; $('#service_cta').html(v); highlight_results($); } } function clear_infection_stats($) { if (!is_full_scan()) { return; } wpmr_clear_infection_stats = { wpmr_clear_infection_stats_nonce: '<?php echo wp_create_nonce( 'wpmr_clear_infection_stats' ); ?>', action: "wpmr_clear_infection_stats", } $.ajax({ url: ajaxurl, method: 'POST', data: wpmr_clear_infection_stats, complete: function (jqXHR, textStatus) { if (textStatus == 'success') { if (jqXHR.hasOwnProperty('responseJSON')) { if (jqXHR.responseJSON.hasOwnProperty('content') && jqXHR.responseJSON.content.length) { console.log('cleared'); } else { if (jqXHR.responseJSON.hasOwnProperty('error')) { console.log('jqXHR.responseJSON.error'); } } } else { console.log('Invalid response from server'); } } else { console.log('Request failed.'); } $("#wpmr-infected-alert").remove(); }, }); } // what exactly does it do? Is it supposed to reset everything to 0 or should it set everything to complete? function reset_ui($) { log_scan_completed(); wpmr_stop_marquee(); $('#files_remaining').html('0'); $('.gauge_c').removeClass('rotating'); $('#logo').removeClass('running'); $('#percent').removeClass('running'); $('.engine_status').removeClass('blink'); $('.engine_status').html('complete'); $('#time_remaining').html('finished'); console.log('total_files:' + total_files); console.log('timestamp record:' + record); wpmr_scanspeed = (total_files) / ((Math.floor((new Date()).getTime() / 1000) - record)); $('#scan_speed').html((wpmr_scanspeed).toFixed(0) + ' items / sec'); $('#files_to_scan').html('none'); //$('#service_cta').html(''); setTimeout(async function () { $('#file_scroll').html(''); }, 1000); percent = 100; if (typeof (results) === "undefined") { results = { timeelapsed: 0 }; } if (!results.hasOwnProperty('timeelapsed')) { results.timeelapsed = 0; } if (!results.hasOwnProperty('iterationUpdated')) { results.iterationUpdated = 0; } $('#percent').html('<span class="percentage">' + parseFloat(percent).toFixed(0) + '%</span><div id="time_counter">' + msToTime(results.timeelapsed) + '</div>'); $('.gauge_c').css('transform', 'rotate( ' + (.005 * percent) + 'turn)'); if ((!suspicious && !severe)) { //$('#file_results').html('<p>Nothing Detected.</p>'); //$('#file_results .blink').html('<p>Nothing Detected.</p>'); show_cta_voila($); } else { highlight_results($); } if (!severe) { clear_infection_stats($); } $('#file_results .blink').removeClass('blink'); if (!$('#file_records').length) { $('#file_results').html('<p>Nothing Detected.</p>'); } wpmr_notify('scan-complete'); // $("#scan_control").val('Re-Scan'); $("#scan_control").prop('disabled', false); $("#scan_control_deep").prop('disabled', false); $("#scan_control_deep").prop('value', 'Re-Initiate DeepScan™→'); $('#wpmr_batchsize').prop('disabled', false); $("#wpmr_god").prop('disabled', false); $('.scan_control').removeClass('unused'); severe = 0; suspicious = 0; } function wpmr_notify(audioFilename) { if (jQuery('#wpmr_ux_notifications_enabled').is(':checked')) { document.title = "✔ Malcure Scan Complete! " + wpmr_doctitle; setTimeout(() => { document.title = wpmr_doctitle; }, 5000); if (!window.Audio) { console.error('Audio API not supported'); return; } if (!audioFilename) { console.error("No audio file specified."); return; } var audioPath = '<?php echo WPMR_PLUGIN_DIR_URL; ?>assets/sounds/' + audioFilename + '.wav'; var audio = new Audio(audioPath); //audio.onerror = function() { // console.error("Error: Audio file not found at " + audioPath); //}; //audio.play().then(() => { // console.log("Audio playing successfully"); //}).catch(error => { // console.error("Error in playing audio: ", error); //}); // Check if the audio file was loaded successfully audio.addEventListener('canplaythrough', () => { audio.play(); }, false); // Handle errors audio.addEventListener('error', (error) => { console.error('Error playing audio:', error); }, false); } else { console.log('Notification preferences disabled.'); } } function wpmr_js_arr_encode($str) { return $str; } function wpmr_prompt_register($) { $('#wpmr_updates_box').addClass('prompt_register'); fix_blur($); } function msToTime(duration) { seconds = Math.floor((duration / 1000) % 60), minutes = Math.floor((duration / (1000 * 60)) % 60), hours = Math.floor((duration / (1000 * 60 * 60)) % 24); hours = (hours < 10) ? "0" + hours : hours; minutes = (minutes < 10) ? "0" + minutes : minutes; seconds = (seconds < 10) ? "0" + seconds : seconds; if (parseInt(hours)) { ret = hours + ":" + minutes + ":" + seconds; //console.log(ret); return ret; } else { ret = minutes + ":" + seconds; //console.log(ret); return ret; } return hours + ":" + minutes + ":" + seconds + "." + milliseconds; } function fix_blur($) { setTimeout(() => { if (jQuery('.prompt_register .inside').length) { matrix = jQuery('.prompt_register .inside').css('transform'); // console.log("before:" + matrix); matrix = matrix.replace(/\.\d+/gi, ''); jQuery('.prompt_register .inside').css('transform', matrix); // console.log(matrix); } }, 2000); //$('.prompt_register .inside').css('transform')) } function wpmr_wheel() { var rangeInput = document.getElementById("wpmr_batchsize"); rangeInput.addEventListener("wheel", function (event) { // Determine the scroll direction (positive for scroll down, negative for scroll up) var scrollDirection = event.deltaY; // Determine the change in value based on the scroll direction var valueChange = scrollDirection > 0 ? 1 : -1; // Update the value of the range input, ensuring it stays within the min and max bounds rangeInput.value = Math.min(rangeInput.max, Math.max(rangeInput.min, parseInt(rangeInput.value) + valueChange)); // Create and dispatch the change event var changeEvent = new Event('change', { 'bubbles': true, 'cancelable': true }); rangeInput.dispatchEvent(changeEvent); // Prevent the default scroll behavior (optional, depending on your needs) event.preventDefault(); } ) } fix_blur($); registered = <?php echo $this->is_registered() ? 1 : 0; ?>; wpmr_is_pro = <?php echo $this->is_advanced_edition() ? 1 : 0; ?>; file_scan_type = ''; severe = 0; suspicious = 0; // Initial setup const wpmr_original_title = document.title; let wpmr_base_message = "Malcure @"; let wpmr_scroll_message = wpmr_base_message; let wpmr_current_percentage = 0; let wpmr_interval; let wpmr_scroll_index = 0; // Keep track of the scroll position // Function to update and scroll the title function wpmr_scroll_and_update_title() { // Prepare the title with the current percentage at the end let titleWithPercentage = `${wpmr_scroll_message} ${wpmr_current_percentage}%… `; // Ensure the scroll index is within bounds wpmr_scroll_index = wpmr_scroll_index % titleWithPercentage.length; // Create the scrolling effect document.title = titleWithPercentage.substring(wpmr_scroll_index) + titleWithPercentage.substring(0, wpmr_scroll_index); // Move to the next scroll position wpmr_scroll_index++; } // Function to update the percentage function wpmr_update_percentage(newPercentage) { wpmr_current_percentage = newPercentage; // No need to reset wpmr_scroll_message or wpmr_scroll_index here } // Function to start the marquee effect function wpmr_start_marquee() { if (wpmr_interval) clearInterval(wpmr_interval); // Clear any existing interval wpmr_interval = setInterval(wpmr_scroll_and_update_title, 200); // Adjust the speed as needed } // Function to stop the marquee and restore the original title function wpmr_stop_marquee() { clearInterval(wpmr_interval); document.title = wpmr_original_title; } /** * Sends an AJAX request to the WordPress backend and handles the response. * * This function allows you to send data to a server-side handler and display * appropriate feedback messages based on the response. * * @param {string} f_call - The name of the server-side action or method to call. * Example: 'update_setting'. * @param {Array} a_args - An array of arguments to pass to the server-side method; pass empty array other-wise. * @param {string} [successMessage] - (Optional) A message to display on a successful * AJAX response. Wrap it in <p> tags. Defaults to console.log('Request complete.'); * if not provided. * Example: '<p>Skin changed successfully to ' + * $(this).val() + '.</p>'. * @param {string} [failureMessage] - (Optional) A message to display on a failed * AJAX response. Wrap it in <p> tags. Defaults to the server response * if not provided. * Example: '<p>Skin change failed.</p>'. * * @example * // Example usage * wpmr_ajax_request( * 'update_setting', * ['wpmr_skin', $(this).val()], * '<p>Skin changed successfully to ' + $(this).val() + '.</p>', // success message * '<p>Skin change failed.</p>' // failure message * ); */ function wpmr_ajax_request(f_call, a_args) { // wpmr_log(arguments.callee.name); wpmr_messaging_arguments = arguments; // wpmr_dir('wpmr_messaging_arguments'); // wpmr_dir(wpmr_messaging_arguments); wpmr_ajax_data = { wpmr_ajax_data_nonce: '<?php echo wp_create_nonce( 'wpmr_ajax_data' ); ?>', action: "wpmr_ajax_request", cachebust: Date.now(), request: [f_call, a_args], }; // wpmr_dir(wpmr_ajax_data); $=jQuery; $.ajax({ // https://api.jquery.com/jquery.ajax/ url: ajaxurl, method: 'POST', data: wpmr_ajax_data, complete: function (o_jqXHR, s_textStatus) { // Triggers regardless of ajax success failure if (s_textStatus == 'success') { // ajax res received if (o_jqXHR.hasOwnProperty('responseJSON')) { // is valid json // wpmr_dir('o_jqXHR.responseJSON'); // wpmr_dir(o_jqXHR.responseJSON); if (o_jqXHR.responseJSON.hasOwnProperty('success')) { // wp_send_json_success || wp_send_json_error if (o_jqXHR.responseJSON.success) { // wp_send_json_success if (wpmr_messaging_arguments[2]) { wpmr_show_message(wpmr_messaging_arguments[2]); } else { // wpmr_show_message('Request complete.'); wpmr_log('Request complete.'); } } else { // wp_send_json_error if (wpmr_messaging_arguments[3]) { wpmr_show_message(wpmr_messaging_arguments[3] + ' ' + o_jqXHR.responseJSON.data); } else { if(/^<p>[\s\S]*<\/p>$/.test(o_jqXHR.responseJSON.data)) { wpmr_show_message( o_jqXHR.responseJSON.data ); } else { wpmr_show_message('<p>' + o_jqXHR.responseJSON.data + '</p>'); } } wpmr_dir(o_jqXHR.responseJSON); } } else { //wp_send_json if(/^<p>[\s\S]*<\/p>$/.test(o_jqXHR.responseJSON)) { wpmr_show_message( o_jqXHR.responseJSON ); } else { wpmr_show_message('<p>' + o_jqXHR.responseJSON + '</p>'); } } } } else { wpmr_log('Ajax textstatus unsuccessful.'); } }, success: function (o_data, s_textStatus, o_jqXHR) { // Received ajax response. Defer to complete callback wpmr_dir('Success: Request was successful.'); wpmr_log(o_jqXHR.responseJSON); }, error: function (o_jqXHR, s_textStatus, s_errorThrown) { // Ajax request failed. Defer to complete callback wpmr_dir('Error: Request failed.'); wpmr_log(o_jqXHR.responseJSON); } }); } function wpmr_show_message(message) { // wpmr_dir('Msg received' + message); $('#wpmr_message_content').html(message); $('#wpmr_messaging').css('bottom', '-' + ($('#wpmr_messaging').height() + 10) + 'px').animate({ "bottom": "10px", 'opacity': '1' }, { duration: 300, complete: function () { $('#wpmr_message_content').addClass('showing'); } }).delay(1 * 10000).animate({ "bottom": '-' + $('#wpmr_messaging').height() + 'px', 'opacity': '0' }, { duration: 300, complete: function () { $('#wpmr_message_content').removeClass('showing'); } }); } jQuery(document).ready(function ($) { //wrapper handle_whitelist_labels($); wpmr_wheel(); $('#scan_hint_value').html($('#wpmr_batchsize').val()); $('#wpmr_message_control').click(function () { $('#wpmr_messaging').css('bottom', '-' + ($('#wpmr_messaging').height() + 10) + 'px').animate({ // without this line there's no control over closing "bottom": '-' + $('#wpmr_messaging').height() + 'px', 'opacity': '0' }); }); $("#wpmr_register_cancel").click(function (e) { $('.prompt_register .inside').removeAttr('style'); }); $("#wpmr_register").click(function (e) { e.preventDefault(); if (!document.querySelector('#wpmp_reg_form').reportValidity()) { return false; } wpmr_web_register = { wpmr_web_register_nonce: '<?php echo wp_create_nonce( 'wpmr_web_register' ); ?>', action: "wpmr_web_register", cachebust: Math.floor((new Date()).getTime() / 1000), user: { fn: $('#wpmr_fn').val(), ln: $('#wpmr_ln').val(), email: $('#wpmr_eml').val(), key: '<?php echo md5( site_url() ); ?>' } }; $.ajax({ url: ajaxurl, method: 'POST', data: wpmr_web_register, complete: function (jqXHR, textStatus) { }, success: function (response) { if ((typeof response) != 'object') { response = JSON.parse(response); } if (response.hasOwnProperty('success')) { $('#is_unregistered').html( '<p><strong>Thank you for registering!</strong></p>'); location.reload(true); } else { $('#reg_error').html('<p><strong>' + response.error + '</strong></p>'); } } // initialize }); // ajax post return false; }); $('#definition_warning').click(function () { $("#wpmr_updates_box").addClass("prompt_register"); fix_blur($); }); $('#cta-register').click(function () { $("#wpmr_updates_box").addClass("prompt_register"); fix_blur($); }); $('#do_file_scan').change(function () { if (this.checked) { $('#skipdirs').prop("disabled", false); } else { $('#skipdirs').attr("disabled", "disabled"); } }); $('#wpmr_god').change(function () { if ($(this).prop('checked')) { $('#wpmr_batchsize_wrap').removeClass('transparent'); } else { $('#wpmr_batchsize_wrap').addClass('transparent'); } }); $('#wpmr_batchsize').change(function () { val = $('#wpmr_batchsize').val(); if (val > 1) { html = 'Scan ' + val + ' Items per req.'; } else { html = 'Scan ' + val + ' Item per req.'; } //$('#scan_hint').html(html); $('#scan_hint_value').html(val); }); // close postboxes that should be closed $('.if-js-closed').removeClass('if-js-closed').addClass('closed'); // postboxes setup postboxes.add_postbox_toggles('toplevel_page_wpmr'); function wpmr_get_stat(stat = '') { // let is important else the variable will be declared as global rendering the function useless let wpmr_stats = { wpmr_stats_nonce: '<?php echo wp_create_nonce( 'wpmr_stats' ); ?>', action: "wpmr_get_stats", stat_type: stat, cachebust: Math.floor((new Date()).getTime() / 1000) }; $.ajax({ url: ajaxurl, method: 'POST', data: wpmr_stats, dataType: "json", // async: false, success: function (response, textStatus, jqXHR) { if ((typeof response) === 'object' && response.hasOwnProperty('success') && response.success) { // is the server not sending us JSON? if (wpmr_stats.stat_type == 'bootstrap') { if (response.data.checksums) { $('#checksum_count').html(response.data.checksums); } else { $('#checksum_count').html('Checksum Count Error'); } if (response.data.count) { $('.total_files').html(response.data.count); $('#files_to_scan').html(response.data.count); $('#files_remaining').html(response.data.count); $('#status_total_files').html(response.data.count); } else { $('.total_files').html('Error File Count'); $('#files_to_scan').html('Error File Count'); $('#files_remaining').html('Error File Count'); $('#status_total_files').html('Error File Count'); } if (response.data.db_stats) { db_total = 0; db_total_count = 0; db_stats = response.data.db_stats; let tables = Object.keys(db_stats); tables.forEach(table => { db_total += parseInt(db_stats[table]['max'], 10); db_total_count += parseInt(db_stats[table]['count'], 10); }); $('#total_records_count').html(db_total_count); $('#total_records').html(db_total); $('#records_remaining').html(db_total); } else { console.log('No db stats'); $('#total_records').html('Error Counting Records'); $('#records_remaining').html('Error Counting Records'); } } if (wpmr_stats.stat_type == 'definition_count' && response.data) { $('.sig_count').html(response.data); } if (wpmr_stats.stat_type == 'definition_version' && response.data) { $('.sig_version').html(response.data); $('#status_definition_version').html(response.data); } if (wpmr_stats.stat_type == 'last_updated' && response.data) { $('.sig_date').html(response.data); $('#last_updated').html(response.data); } if (wpmr_stats.stat_type == 'hidden_files' && response.data) { $('#hidden_files').html(response.data); } } }, complete: function (jqXHR, textStatus) { console.dir(jqXHR); } }); // ajax post } wpmr_get_stat('bootstrap'); wpmr_get_stat('definition_count'); wpmr_get_stat('definition_version'); wpmr_get_stat('last_updated'); wpmr_get_stat('hidden_files'); wpmr_get_stat('memory_limit'); $("#wpmr_skin").change(function (e) { //event classes = $('body').attr('class', $('body').attr('class').replace(/malcure_skin_.*?\s/, 'malcure_skin_' + $(this).val() + ' ')); wpmr_ajax_request( 'update_setting', ['wpmr_skin', $(this).val()], '<p>Skin changed successfully to ' + $(this).val() + '.</p>', // success message '<p>Skin change failed.</p>' // failure message ); }); $('#wpmr_results_box').addClass('closed'); $('#wpmr_debug_box').addClass('closed'); $('#wpmr_inspect_box').addClass('closed'); $('#wpmr_diagnostics_box').addClass('closed'); $("#wpmr_def_auto_update_enabled").change(function (e) { //event e.preventDefault(); wpmr_def_auto_update_enabled = { wpmr_def_auto_update_enabled_nonce: '<?php echo wp_create_nonce( 'wpmr_def_auto_update_enabled' ); ?>', action: "wpmr_def_auto_update_enabled", cachebust: Math.floor((new Date()).getTime() / 1000), enabled: this.checked }; $.ajax({ url: ajaxurl, method: 'POST', data: wpmr_def_auto_update_enabled, complete: function (jqXHR, textStatus) { console.dir(jqXHR); } }); // ajax post }); $("#malcure_destroy_sessions").click(function () { wpmr_ajax_request( 'destroy_sessions', ['<?php echo get_current_user_id(); ?>'], '<p>All users have been logged out (except you).</p>', // success message '<p>Failed to logout other users.</p>' // failure message ); }); $("#wpmr_ux_notifications_enabled").change(function (e) { //event wpmr_ajax_request( 'update_setting', ['ux_notifications_enabled', this.checked ? 'on' : 'off'], '<p>Notification preferences saved.</p>', // success message '<p>Failed to save notification preferences.</p>' // failure message ); }); $("#malcure_shuffle_salts").click(function () { wpmr_ajax_request('ajax_shuffle_salts'); }); $("#wpmr_update").click(function (e) { //event e.preventDefault(); wpmr_update = { wpmr_update_nonce: '<?php echo wp_create_nonce( 'wpmr_update_sigs' ); ?>', action: "wpmr_update_sigs", cachebust: Math.floor((new Date()).getTime() / 1000), }; $.ajax({ url: ajaxurl, method: 'POST', data: wpmr_update, complete: function (jqXHR, textStatus) { if (textStatus == 'success') { // ajax res received if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('success') && jqXHR.responseJSON.success) { $('#update_response').html('<span class="wpmr_notice_success">Successfully updated definitions.</span>'); $('.sig_count').html(jqXHR.responseJSON.data.count); $('.sig_version').html(jqXHR.responseJSON.data.version); // in the dashboard_wrap $('#status_definition_version').html(jqXHR.responseJSON.data.version); // in the system status box $('.sig_date').html(jqXHR.responseJSON.data.sig_time); // for the engine stats $('#last_updated').html(jqXHR.responseJSON.data.sig_time); // for the System Status $('#definition_warning').remove(); $('#update_notice_p').remove(); $('#update_response').fadeOut(10000); $("#wpmr_new_def_alert").remove(); } else { if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('data') && jqXHR.responseJSON.data.length) { $('#update_response').html('<span class="wpmr_notice_error">Failed. Reason: ' + jqXHR.responseJSON.data + '</span>'); } } } else { // no ajax response if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('data') && jqXHR.responseJSON.data.length) { $('#update_response').html('<span class="wpmr_notice_error">Unknown failure.</span>'); } } }, success: function (response) { } // initialize }); // ajax post }); $("#wpmr_reset").click(function (e) { //event e.preventDefault(); if (!confirm("Whoa! This will delete plugin settings including definitions, checksums & registration data.\nAre you sure you want to proceed?\nClick cancel if not sure.")) { return false; } wpmr_reset = { wpmr_reset_nonce: '<?php echo wp_create_nonce( 'wpmr_reset' ); ?>', action: "wpmr_reset", reset_logs: $('#wpmr_reset_logs').is(':checked'), cachebust: Date.now(), }; $.ajax({ url: ajaxurl, method: 'POST', data: wpmr_reset, complete: function (jqXHR, textStatus) { if (textStatus == 'success') { // ajax res received if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('success')) { $('#update_response').html('<span class="wpmr_notice_success">Successfully reset plugin settings.</span>'); $('#update_notice_p').remove(); $('#update_response').fadeOut(10000); setTimeout(function () { if (confirm('Plugin has been reset. Reload the page now?')) { location.reload(); } }, 1000); } else { if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('data') && jqXHR.responseJSON.data.length) { $('#update_response').html('Failed.'); } } } else { // no ajax response $('#update_response').html('Unknown failure.'); } }, success: function (response) { } // initialize }); // ajax post }); $('#wpmr_register_cancel').click(function (e) { $('#wpmr_updates_box').removeClass('prompt_register'); }); $("#scan_control").click(js_scanner); //click $("#scan_control_deep").click(js_scanner); //click $('.inspect_file_debug').blur(file_inspect_handler); $("#wpmr_copy").click(function () { try { let range = document.createRange(); let selection = window.getSelection(); let node = document.getElementById('result_range'); range.selectNodeContents(node); selection.removeAllRanges(); selection.addRange(range); document.execCommand("copy"); selection.removeAllRanges(); $('#copied_check').fadeTo(1000, 0).fadeTo(1000, 1); } catch (e) { } }); $('#file_results').on('click', '.wpmr_inspect_file', file_inspect_handler); $('#wpmr_cleanup').click(function (event) { if (!wpmr_is_pro) { alert('This feature is only available in Malcure Advanced Edition.'); return; } file = $('#wpmr_inspect_file').attr('data-file'); if (!file) { $('#file_op_status').html('No file selected.'); // update the status message return; } if (!confirm("\nAre you sure you have backed-up this file and want to attempt to repair it?\n\n" + file + "\n")) { return; } $('#file_op_status').html(''); event.preventDefault(); var element = this; wpmr_clean_file = { wpmr_clean_nonce: '<?php echo wp_create_nonce( 'wpmr_clean_file' ); ?>', action: "wpmr_clean_file", file: btoa(file), cachebust: Math.floor((new Date()).getTime() / 1000), }; $.ajax({ url: ajaxurl, method: 'GET', data: wpmr_clean_file, complete: function (jqXHR, textStatus) { document.getElementById('file_op_status').scrollIntoView({ behavior: "smooth", block: "center", inline: "center" }); setTimeout(function () { flash_file_op($) }, 500); if (textStatus == 'success') { if (jqXHR.hasOwnProperty('responseJSON')) { if (jqXHR.responseJSON.success) { $('#wpmr_inspect_file').val(jqXHR.responseJSON.data); // update the file inspector contents $('#file_op_status').html('File repair succeeded on ' + file); // update the status message $('.wpmr_inspect_file[data-file="' + file + '"]').closest('tr.detected').fadeOut(1000, function () { $(this).remove(); }); // remove the file from the infection results. $('#wpmr_inspect_file').attr('data-file', ''); } else { $('#file_op_status').html(jqXHR.responseJSON.data); } } else { $('#file_op_status').html('Invalid response from server!'); } } else { $('#file_op_status').html('Request failed!'); } }, }); }); $('#wpmr_delete').click(function (event) { if (!wpmr_is_pro) { alert('This feature is only available in Malcure Advanced Edition.'); return; } file = $('#wpmr_inspect_file').attr('data-file'); if (!file) { $('#file_op_status').html('No file selected.'); // update the status message return; } if (!confirm("\nAre you sure you have backed-up this file and want to delete it?\n\n" + file + "\n")) { return; } $('#file_op_status').html(''); event.preventDefault(); var element = this; wpmr_delete_file = { wpmr_delete_nonce: '<?php echo wp_create_nonce( 'wpmr_delete_file' ); ?>', action: "wpmr_delete_file", file: btoa(file), cachebust: Math.floor((new Date()).getTime() / 1000), }; $.ajax({ url: ajaxurl, method: 'GET', data: wpmr_delete_file, complete: function (jqXHR, textStatus) { document.getElementById('file_op_status').scrollIntoView(); setTimeout(function () { flash_file_op($) }, 500); if (textStatus == 'success') { if (jqXHR.hasOwnProperty('responseJSON')) { if (jqXHR.responseJSON.success) { $('#wpmr_inspect_file').val(''); // clear the file inspector contents $('#file_op_status').html(jqXHR.responseJSON.data); // update the UI about response status $('.wpmr_inspect_file[data-file="' + file + '"]').closest('tr.detected').fadeOut(1000, function () { $(this).remove(); }); // remove the file from infected results $('#wpmr_inspect_file').attr('data-file', ''); } else { $('#file_op_status').html(jqXHR.responseJSON.data); } } else { $('#file_op_status').html('Invalid response from server!'); } } else { $('#file_op_status').html('Request failed!'); } }, }); }); $('#wpmr_file_whitelist').click(function (event) { if (!wpmr_is_pro) { alert('This feature is only available in Malcure Advanced Edition.'); return; } file_path = $('#wpmr_inspect_file').attr('data-file'); real_path = file_path; if (!file_path) { $('#file_op_status').html('No file selected.'); // update the status message return; } if (!confirm("\nAre you sure you want to whitelist this file?\n\n" + file_path + "\n")) { return; } $('#file_op_status').html(''); event.preventDefault(); var element = this; wpmr_whitelist_file = { wpmr_whitelist_nonce: '<?php echo wp_create_nonce( 'wpmr_whitelist_file' ); ?>', action: "wpmr_whitelist_file", file: btoa(file_path), cachebust: Math.floor((new Date()).getTime() / 1000), }; $.ajax({ url: ajaxurl, method: 'GET', data: wpmr_whitelist_file, complete: function (jqXHR, textStatus) { document.getElementById('file_op_status').scrollIntoView(); setTimeout(function () { flash_file_op($) }, 500); if (textStatus == 'success') { if (jqXHR.hasOwnProperty('responseJSON')) { if (jqXHR.responseJSON.success) { $('#wpmr_inspect_file').val(''); // clear the file inspector contents $('#file_op_status').html(jqXHR.responseJSON.data); // update the UI about response status $('.wpmr_inspect_file[data-file="' + file_path + '"]').closest('tr.detected').fadeOut(1000, function () { $(this).remove(); }); // remove the file from infected results $('#wpmr_inspect_file').attr('data-file', ''); $('#whitelist').append('<p data-file-wrap="' + real_path + '"><span data-file="' + real_path + '" class="dashicons dashicons-dismiss remove-from-whitelist"></span>' + real_path + '</p>'); handle_whitelist_labels($); } else { $('#file_op_status').html(jqXHR.responseJSON.data); } } else { $('#file_op_status').html('Invalid response from server!'); } } else { $('#file_op_status').html('Request failed!'); } }, }); }); $('#whitelist').on('click', '.remove-from-whitelist', function (event) { $element = this; file = $(this).attr('data-file'); $(this).addClass('mc-waiting'); wpmr_unwhitelist_file = { wpmr_unwhitelist_nonce: '<?php echo wp_create_nonce( 'wpmr_unwhitelist_file' ); ?>', action: "wpmr_unwhitelist_file", file: btoa(file), cachebust: Math.floor((new Date()).getTime() / 1000), }; $.ajax({ url: ajaxurl, method: 'GET', data: wpmr_unwhitelist_file, complete: function (jqXHR, textStatus) { if (textStatus == 'success') { if (jqXHR.hasOwnProperty('responseJSON')) { if (jqXHR.responseJSON.success) { $("[data-file-wrap='" + file + "']").remove(); $(this).removeClass('mc-waiting'); handle_whitelist_labels($); } else { alert(jqXHR.responseJSON.data); // alert the error message } } else { alert('Invalid response from server!'); } } else { alert('Request failed!'); } }, }); }); function js_scanner(e) { //event e.preventDefault(); if ($(this).attr('id') == 'scan_control') { file_scan_type = 'fast'; $('#scan_control_deep').addClass('unused'); } if ($(this).attr('id') == 'scan_control_deep') { file_scan_type = 'deep'; $('#scan_control').addClass('unused'); } if (typeof (scanned) !== 'undefined') { // if this is not the first scan then warn before rescanning if (!confirm('This will clear the current results. Are you sure you want to continue?')) { return; } } scanned = true; wpmr_doctitle = document.title; do_db_scan = $('#do_db_scan').is(':checked'); do_file_scan = $('#do_file_scan').is(':checked'); do_redirect_scan = $('#do_redirect_scan').is(':checked'); skipdirs = ($('#skipdirs').length && $('#skipdirs').val().trim().length) ? $('#skipdirs').val() : ''; show_suspicious = $('#show_suspicious').is(':checked'); full_scan = (do_db_scan && do_file_scan && (!$('#skipdirs').val().trim().length)); if (!do_db_scan && !do_file_scan) { joking = confirm("You've hit the easter egg!\nBoth db scan and file scan is unchecked.\nPretty sure you're joking right?"); return false; } $('#wpmr_results_box').addClass('closed'); $('#wpmr_debug_box').addClass('closed'); $('#wpmr_inspect_box').addClass('closed'); $('#wpmr_diagnostics_box').addClass('closed'); $('#service_cta').html(''); severe = 0; suspicious = 0; if (!registered) { msgnodef = window.confirm("A definition update is required to detect the latest malware.\n Click OK to return and update the definitions (recommended).\n Click Cancel to continue scanning without definitions (not recommended)."); if (msgnodef) { return wpmr_prompt_register($); } } window.onbeforeunload = function () { return true; }; record = Math.floor((new Date()).getTime() / 1000); $('.gauge_c').css('transform', 'rotate( 0turn)'); $('.gauge_c').addClass('rotating'); $('#db_results').html(''); $('#title_hack').html(''); $('#redirect_hijack').html(''); $('#file_results').html(''); $('#percent').html(''); $('#lcd').html(''); $('.engine_status').toggleClass('blink'); $('#logo').toggleClass('running'); $('#percent').toggleClass('running'); $('#percent').removeClass('suspicious'); $('#percent').removeClass('severe'); if (do_db_scan) { $('.engine_status').html('scanning database…'); } else { $('.engine_status').html('initialising…'); } total_files = 0; highlight_cta = 0; $("#scan_control").attr('disabled', 'disabled'); $("#scan_control_deep").attr('value', 'DeepScan™ Running…'); $("#scan_control_deep").attr('disabled', 'disabled'); $("#wpmr_god").attr('disabled', 'disabled'); regex = ($('#wpmr_extra_reg').length && $('#wpmr_extra_reg').val().trim().length) ? btoa($('#wpmr_extra_reg').val()) : ''; wpmr_extra_db_query = ($('#wpmr_extra_db_query').length && $('#wpmr_extra_db_query').val().trim().length) ? btoa($('#wpmr_extra_db_query').val()) : ''; wpmr_extra_db_regex = ($('#wpmr_extra_db_regex').length && $('#wpmr_extra_db_regex').val().trim().length) ? btoa($('#wpmr_extra_db_regex').val()) : ''; wpmr_scan_only_dirs = ($('#wpmr_scan_only_dirs').length && $('#wpmr_scan_only_dirs').val().trim().length) ? $('#wpmr_scan_only_dirs').val() : ''; wpmr_init_scan = { wpmr_init_nonce: '<?php echo wp_create_nonce( 'wpmr_init_scan' ); ?>', action: "wpmr_init_scan", timestamp: record, regex: regex, wpmr_extra_db_query: wpmr_extra_db_query, wpmr_extra_db_regex: wpmr_extra_db_regex, cachebust: Date.now(), skipdirs: skipdirs, suspicious: show_suspicious, do_db_scan: do_db_scan, do_file_scan: do_file_scan, do_redirect_scan: do_redirect_scan, wpmr_scan_only_dirs: wpmr_scan_only_dirs }; $.ajax({ url: ajaxurl, method: 'POST', data: wpmr_init_scan, success: function (response) { // wpmr_dir(response); $('#wpmr_results_box').removeClass('closed'); db_result = response.hasOwnProperty('db_scan') ? Object.values(response.db_scan) : false; if (response.checksums) { $('#checksum_count').html(response.checksums); } if (response.last_updated !== "Never") { $('.sig_date').html(response.last_updated); $('#definition_warning').remove(); $('#update_notice_p').remove(); } if (response.definition_version) { $('.sig_version').html(response.definition_version); $('#status_definition_version').html(response.definition_version); } if (response.definition_count) { $('.sig_count').html(response.definition_count); } title_hack = response.title_hack; redirect_hijack = response.redirect_hijack; found_anything = 0; found_files = 0; if(wpmr_init_scan.do_db_scan){ if (db_result.length) { found_anything = 1; dbhtml = ''; Object.entries(db_result).forEach(([key, value ]) => { dbhtml += '<a target="_blank" class="threat ' + value.severity + '" href="<?php echo trailingslashit( MALCURE_API ) . '?p=2074&ssig='; ?>' + value.signature + '&utm_source=scanrun&utm_medium=web&utm_campaign=wpmr' + value.severity + '">' + value.message + ' <span class="wpmr_offset">' + value.signature + '</span></a>'; severe++; }); $('#db_results').html(dbhtml); if (severe) { show_cta_severe($); } } else { $('#db_results').html('<p style="grid-column: 1 / span 2;">Nothing Detected.</p>'); } $('#records_remaining').html('0'); } else { $('#records_remaining').html(response.db_total); $('#db_results').html('<p style="grid-column: 1 / span 2;"><strong><em>Skipped database scan on request.</em></strong></p>'); } if (title_hack) { $('#title_hack').html('<span class="threat severe">Site Title or Tagline hack detected! Your Search Engine Ranks will be adversely affected and even lead to blacklisting.</span>'); found_anything = 1; severe++; show_cta_severe($); } else { $('#title_hack').html('<p>Nothing Detected.</p>'); } if (redirect_hijack) { $('#redirect_hijack').html('<span class="threat severe">Site redirect hijack detected! Your Search Engine Ranks will be adversely affected and even lead to blacklisting.</span>'); found_anything = 1; severe++; show_cta_severe($); } else { $('#redirect_hijack').html('<p>Nothing Detected.</p>'); } if ($('#wpmr_scan_single_file').length && $('#wpmr_scan_single_file').val().trim().length) { files = [$('#wpmr_scan_single_file').val()]; } else { files = response.files; } files = Object.values(files); $('.engine_status').html('initialized…'); results = []; orig_timestamp = Math.floor((new Date()).getTime() / 1000); results.starttime = orig_timestamp; results.totalfiles = total_files = files.length; $('.files_excluded').html(parseInt($('.total_files').html()) - files.length); $('#files_to_scan').html(results.totalfiles); $('#checksum_count').html(response.checksums); results.memory = 0; results.iterationUpdated = 0; results.infectedfiles = {}; failed = []; iteration = 0; batchsize = $('#wpmr_batchsize').val() ; // example of db_stats: {"posts": {"count": "322","min": "1","max": "108091"},"postmeta": {"count": "5976","min": "1","max": "6179"},"options": {"count": "805","min": "1","max": "234171"},"comments": {"count": "30","min": "1","max": "30"}} if (response.hasOwnProperty('db_stats')) { $('.engine_status').html('scanning database…'); var db_stats = response.db_stats; var tables = Object.keys(db_stats); var active_table = 0; var pointer = parseInt(db_stats[tables[active_table]].min) - 1; // we use > in sql so start one before that db_batchsize = 11 * batchsize; // speed it up db_batchsize = Math.max(11, Math.min(db_batchsize, 1100)); var db_max_range = 0; db_progress_counters = {}; tables.forEach(table => { db_max_range += parseInt(db_stats[table]['max'], 10); db_progress_counters[table] = 0; }); function scandb_batch() { // Update UI with current table and pointer $('#file_scroll').html('<p class="file_name">Scanning Table ' + tables[active_table].toUpperCase() + ' @ marker ' + pointer + '</p>'); if(active_table < tables.length){ if(pointer < parseInt(db_stats[tables[active_table]].max)){ jQuery.ajax({ url: ajaxurl, type: 'POST', data: { action: 'wpmr_scan_db', table: tables[active_table], batchsize: db_batchsize, pointer: pointer, timestamp: record, wpmr_extra_db_query: wpmr_extra_db_query, wpmr_extra_db_regex: wpmr_extra_db_regex, }, success: function(response) {}, error: function(xhr, status, error) {}, complete: function(jqXHR, textStatus) { if (jqXHR.hasOwnProperty('responseJSON')) { xhr_response = jqXHR.responseJSON; // wpmr_dir('xhr_response'); // wpmr_dir(xhr_response); if (xhr_response.hasOwnProperty('report') && xhr_response.report.hasOwnProperty('results')) { let results = xhr_response.report.results; db_batch_html = ''; if ((Array.isArray(results) && results.length > 0) || (typeof results === 'object' && Object.keys(results).length > 0)) { results.forEach(result => { severe++; db_batch_html += '<a target="_blank" class="threat ' + result.severity + '" href="<?php echo trailingslashit( MALCURE_API ) . '?p=2074&ssig='; ?>' + result.signature + '&utm_source=scanrun&utm_medium=web&utm_campaign=wpmr' + result.severity + '">' + result.message + ' <span class="wpmr_offset">' + result.signature + '</span></a>'; console.log(`Severity: ${result.severity}, Message: ${result.message}, Signature: ${result.signature}`); }); if(severe) { show_cta_severe($); } $('#db_results').append(db_batch_html); } // wpmr_log('Before db_progress_counters'); // wpmr_log({...db_progress_counters}); if(xhr_response.report.hasOwnProperty('new_pointer')){ pointer = parseInt(xhr_response.report.new_pointer); // wpmr_log(`Server Pointer: ${pointer}` ); } else { pointer += db_batchsize; // wpmr_log(`Manual Pointer Progress: ${pointer}` ); } // if we have a pointer greater than max, set pointer to max so that we can increment active_table if (pointer >= parseInt(db_stats[tables[active_table]].max)) { pointer = parseInt(db_stats[tables[active_table]].max); } db_progress_counters[tables[active_table]] = pointer; } // results // calculate_db_progress(); // wpmr_log('After Increment db_progress_counters'); // wpmr_log({...db_progress_counters}); db_progress = Object.values(db_progress_counters).reduce(function(total, value) { return total + value; }, 0); // wpmr_log(`db_max_range - db_progress ${db_max_range - db_progress} db_max_range${db_max_range} db_progress${db_progress}`); $('#records_remaining').html(db_max_range - db_progress); let percent = ( db_progress / db_max_range ) * 100; // wpmr_log(`db_progress ${percent} : ${db_progress} / ${db_max_range}`); db_time_elapsed = Math.floor((new Date()).getTime() / 1000) - results.starttime; db_scan_speed = db_progress / db_time_elapsed; // wpmr_log(`db_scan_speed ${db_scan_speed}: db_progress:${db_progress}, db_time_elapsed:${db_time_elapsed} active_table ${tables[active_table]}`); // wpmr_log(`time_remaining ${(db_max_range - db_progress) / db_scan_speed}: db_max_range:${db_max_range}, db_progress:${db_progress}, db_scan_speed:${db_scan_speed}, db_batchsize:${db_batchsize}`); stats = { percent: percent, scan_speed: db_scan_speed, time_elapsed: db_time_elapsed, time_remaining: Math.abs((db_max_range - db_progress) / db_scan_speed) }; if (xhr_response.hasOwnProperty('memory')) { stats.memory = xhr_response.memory; } if (xhr_response.hasOwnProperty('memory_limit')) { stats.memory_limit = xhr_response.memory_limit; } if (xhr_response.hasOwnProperty('cpu')) { stats.cpu = xhr_response.cpu; } wpmr_update_ui(stats); } // if (jqXHR.hasOwnProperty('responseJSON')) { else { pointer += db_batchsize; } // else (jqXHR.hasOwnProperty('responseJSON')) { scandb_batch(); } }); } else { // pointer is greater than or equal to max, increment active_table, reset pointer and call scandb again active_table++; if(active_table < tables.length){ pointer = parseInt( db_stats[tables[active_table]].min ); // wpmr_log(`After changing table out of ${db_max_range} pointer is ${pointer}`); scandb_batch(); } else { init_file_scan(); } } } else { // active table is greater than or equal to tables length init_file_scan(); } // === } scandb_batch(); // initial call } else { wpmr_log("Response does not have db_stats property. Proceeding to file scan."); init_file_scan(1); } function calculate_db_progress() { // Initialize completedRange to accumulate the ID ranges processed from completed tables. let completedRange = 0; // Loop through all previously scanned tables to sum their complete ranges. // This will only include the range from 'min' to 'max' of each table that has been fully scanned. for (let i = 0; i < active_table; i++) { completedRange += (parseInt(db_stats[tables[i]].max, 10) - parseInt(db_stats[tables[i]].min, 10)); } // Calculate the progress in the current table by determining how far the pointer has moved past the 'min'. // This provides a measure of how much of the current table has been processed. let currentTableProgress = pointer - parseInt(db_stats[tables[active_table]].min, 10); // Total progress combines the completed ranges of fully scanned tables with the progress in the current table. let totalProgress = completedRange + currentTableProgress; //console.log(`Pseudo progress: ${totalProgress} / ${db_max_range}`); // Calculate the overall progress as a percentage of the total range that was initially determined. let progressPercentage = (totalProgress / db_max_range) * 100; // Update the UI to reflect the current progress percentage, formatted to two decimal places for precision. // $('#progress').html(progressPercentage.toFixed(2) + '%'); // Log progress details for debugging and verification purposes. wpmr_log(`Pseudo progress: ${progressPercentage}% (Total: ${totalProgress}, Range: ${db_max_range})`); } function init_file_scan($now) { if (files.length) { // if the file scan will be initialised, let's reset the progress wpmr_update_ui({ percent: 0, time_elapsed: Math.floor((new Date()).getTime() / 1000) - orig_timestamp }); setTimeout(scan_files, 1000); } else { window.onbeforeunload = null; if (wpmr_init_scan.do_file_scan) { // no files but file scan was chosen alert("There are no files to scan.\nBroken WordPress Install?"); } else { $('#file_results').html('<p>Skipped file-scan on request.</p>'); reset_ui($); } } } function scan_files() { $('.engine_status').html('scanning files…'); batchsize = (iteration == 0) ? $('#wpmr_batchsize').val() : batchsize; $('#wpmr_batchsize').attr('disabled', 'disabled'); if (!files.length) { return; } wpmr_scan_files = { action: "wpmr_scan_files", regex: regex, timestamp: record, <?php if ( ! empty( $_REQUEST['debug'] ) ) { echo 'debug: true,' . PHP_EOL; } ?> iteration: iteration, suspicious: document.getElementById('show_suspicious').checked, files: files.splice(0, batchsize).map(function (filename) { return btoa(encodeURIComponent(filename)); }), }; lastbatch = wpmr_scan_files.files.map(function (filename) { return decodeURIComponent(atob(filename)); }); siteroot = '<?php echo $this->get_home_dir(); ?>'; lastbatch.forEach((element, index) => { setTimeout(function () { $('#file_scroll').html('<p class="file_name">' + element.replace(siteroot, '') + '</p>'); }, (index * 1000 / batchsize)); }); $.ajax({ url: ajaxurl, method: 'POST', data: wpmr_scan_files, success: function (scan_status) { }, complete(jqXHR, textStatus) { // wpmr_dir(lastbatch); if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('debug') & jqXHR.responseJSON.debug.length) { console.dir(jqXHR.responseJSON.debug); } var scan_status = {}; if (textStatus == "success" && jqXHR.hasOwnProperty('responseJSON')) { scan_status = jqXHR.responseJSON.report; } else { // ajax failure console.group("%cAjax Batch Scan Failure → " + Math.floor((new Date()).getTime() / 1000), 'text-transform:uppercase;font-weight:bold;color:#df2040;'); console.table(lastbatch); console.groupEnd(); Object.entries(lastbatch).forEach(([key,value]) => { if (iteration == 0) { failed.push(value); } else { scan_status[value] = JSON.parse('{"severity" : "skipped","signature":"failure","message":"Invalid scan response"}'); } }); } orig_timestamp = Math.floor((new Date()).getTime() / 1000); html = ''; if (Object.keys(scan_status).length) { Object.entries(scan_status).forEach(([key, value ]) => { results.infectedfiles[key] = value; if (value.severity == 'high' || value.severity == 'severe') { severe++; } else { if (value.severity != 'skipped') { // flag suspicious only if the file has been scanned suspicious++; } } }); found_files = Object.keys(results.infectedfiles).length; found_anything = 1; } if (found_files) { html = '<table id="file_records"><tbody>'; } Object.entries(results.infectedfiles).forEach(([key, value]) => { if (wpmr_is_pro) { clean_tip = 'Delete this file from server?'; } else { clean_tip = 'This feature is only available in paid version.'; } <?php if ( 1 || $this->is_advanced_edition() ) { echo 'has_seasoned_eyes = key;'; } else { echo 'has_seasoned_eyes = key.split(/[\\/]/).pop();'; } ?> html += '<tr class="detected"><td class="inspect"><span class="wpmr_inspect_file button malcure-button-primary" data-file="' + key + '">Click To Take Action</span></td><td class="level"><a target="_blank" class="threat ' + value.severity + '" href="<?php echo trailingslashit( MALCURE_API ) . '?p=2074&ssig='; ?>' + value.signature + '&utm_source=scanrun&utm_medium=web&utm_campaign=wpmr' + value.severity + '">' + value.message + ' <span class="wpmr_offset">' + value.signature + '</span></a></td><td class="sig_details_wrap"></td><td class="infected_file"><pre class="recorded_file">' + has_seasoned_eyes + '</pre></td></tr>'; }); if (suspicious && !severe) { show_cta_suspicious($); } else { if (severe) { show_cta_severe($); } } if (found_files) { html += '</tbody></table>'; } else { html = '<p id="file_scan_blink" class="blink">Nothing yet…</p>'; } $('#file_results').html(html); results.filesremaining = files.length; if (iteration === 0) { } else { if (!results.iterationUpdated) { // update this only once results.iterationUpdated = 1; results.totalfiles = files.length; results.starttime = Math.floor((new Date()).getTime() / 1000); } } if (textStatus == "success" && jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('memory') && jqXHR.responseJSON.memory > results.memory) { results.memory = jqXHR.responseJSON.memory; } results.memory_limit = ''; if (textStatus == "success" && jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('memory_limit')) { results.memory_limit = ' / ' + jqXHR.responseJSON.memory_limit; } results.scanspeed = (results.totalfiles - results.filesremaining) / ((Math.floor((new Date()).getTime() / 1000) - results.starttime)); $('#percent').fadeTo(2000, 1); results.cpu = []; try { if (textStatus == "success" && jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('cpu') && Array.isArray(jqXHR.responseJSON.cpu)) { results.cpu = jqXHR.responseJSON.cpu; } else { } results.cpu = results.cpu.join(' / '); } catch (e) { console.dir(e); } $('#lcd').html('<table>' + '<tr id="lcd_tr_speed"><th><span class="data_head">Rate</span><span class="colon"> :</span></th><td id="lcd_speed">' + (results.scanspeed).toFixed(0) + ' items / sec</td></tr>' + '<tr id="lcd_tr_ram"><th><span class="data_head">RAM</span><span class="colon"> :</span></th><td title="Max Mem Utilized" id="lcd_ram">' + parseFloat(results.memory).toFixed(0) + 'M' + results.memory_limit + '</td></tr>' + '<tr id="lcd_tr_cpu"><th><span class="data_head">CPU</span><span class="colon"> :</span></th><td title="Avg. CPU Load 1 / 5 / 15" id="lcd_cpu">' + results.cpu + '</td></tr></table>').fadeTo(2000, .5); $('#files_remaining').html(results.filesremaining); if (iteration === 0) { results.timeelapsed = (Math.floor((new Date()).getTime() / 1000) - results.starttime); } else { results.timeelapsed = (Math.floor((new Date()).getTime() / 1000) - record); } $('#time_elapsed').html(msToTime(results.timeelapsed * 1000)); $('#scan_speed').html((results.scanspeed).toFixed(0) + ' items / sec.'); results.timeremaining = results.filesremaining / results.scanspeed; $('#time_remaining').html(msToTime(results.timeremaining * 1000)); percent = ((results.totalfiles - results.filesremaining) / results.totalfiles) * 100; wpmr_update_percentage(parseFloat(percent).toFixed(0)); wpmr_start_marquee(); $('#percent').html('<span class="percentage">' + parseFloat(percent).toFixed(0) + '%</span><br /><div id="time_counter">' + msToTime(results.timeelapsed * 1000) + '</div>'); $('.gauge_c').css('transform', 'rotate( ' + (.005 * percent) + 'turn)'); if (files.length) { scan_files(); } else { // this is the last batch iteration++; if (failed.length && iteration == 1) { files = failed; failed = []; batchsize = 1; console.error("%cFiles Failed Due to Ajax Error → %i", 'text-transform:uppercase;font-weight:bold;color:#df2040;', files.length); console.group('Phase 2'); console.table(files); console.groupEnd(); console.warn('Initiating partial rescan due to previous failures…'); $('.engine_status').html('re-scanning…'); scan_files(); } else { reset_ui($); } } }, error: function (jqXHR, textStatus, errorThrown) { } }); } // EO fn scan_files }, // wpmr_init_scan success error: function (oJqXHR, sStatus, sErrorThrown) { console.dir(sStatus + ' ' + oJqXHR.status); window.onbeforeunload = null; reset_ui($); $('#service_cta').html('<div class="wpmr_init_error" style="background:hsla(350,65%,50%,1);padding:1em;color:white;"><h1 style="text-transform:capitalize;color:white;">' + sStatus + ' ' + oJqXHR.status + '</h1><p>Please make sure that WordPress is working before trying to scan. <a href="https://malcure.com/blog/security/wordpress-debugging-tools/" target="_blank" style="color:hsla(60,100%,50%);font-weight:bold;">Click here for troubleshooting information</a>.</p></div>'); $('#wpmr_results_box').removeClass('closed'); }, // wpmr_init_scan error complete: function (jqXHR, textStatus) {}, }); // ajax wpmr_init_scan } // EO fn js_scanner }); //ready const wpmr_update_ui = (parms) => { $ = jQuery.noConflict(); // wpmr_log('parms'); // wpmr_dir(parms); // Engine statistics // checksums if ('checksum_count' in parms) { $('#checksum_count').html(parms.checksum_count); } // signature count if ('sig_count' in parms) { $('.sig_count').html(parms.sig_count); } // signature version if ('sig_version' in parms) { $('.sig_version').html(parms.sig_version); } // signature update time / 4 mins ago... if ('sig_date' in parms) { $('.sig_date').html(parms.sig_date); } // total_files if ('total_files' in parms) { $('.total_files').html(parms.total_files); } // files_remaining if ('files_remaining' in parms) { $('#files_remaining').html(parms.files_remaining); } // time_elapsed if ('time_elapsed' in parms) { $('#time_elapsed').html(msToTime(parms.time_elapsed * 1000)); } if ('time_remaining' in parms) { $('#time_remaining').html(msToTime(parms.time_remaining * 1000)); } // scan_speed (items per second) if ('scan_speed' in parms) { $('#scan_speed').html((parms.scan_speed).toFixed(0) + ' items / sec'); } // Status (initialising, scanning etc...) if ('status' in parms) { $('.engine_status').html(parms.status); } // SECOND COLUMN // Percentage speedometer NEEDLE if ('percent' in parms) { $('.gauge_c').css('transform', 'rotate(' + (.005 * parms.percent) + 'turn)'); } // Central Percentage text counter if ('percent' in parms && 'time_elapsed' in parms) { $('#percent').html('<span class="percentage">' + parseFloat(parms.percent).toFixed(0) + '%</span><div id="time_counter">' + msToTime(parms.time_elapsed * 1000) + '</div>').fadeTo(2000, 1); } // THIRD COLUMN if ('wpmr_skin' in parms) { $('#wpmr_skin').html(parms.wpmr_skin); } if ( 'scan_speed' in parms && 'memory' in parms && 'memory_limit' in parms && 'cpu' in parms ) { $('#lcd').html('<table><tr><th><span class="data_head">Rate</span><span class="colon"> :</span></th><td>' + parms.scan_speed.toFixed(0) + ' items / sec</td></tr><tr><th><span class="data_head">RAM</span><span class="colon"> :</span></th><td title="Max Mem Utilized">' + parseFloat(parms.memory).toFixed(0) + 'M / ' + parms.memory_limit + '</td></tr><tr><th><span class="data_head">CPU</span><span class="colon"> :</span></th><td title="Avg. CPU Load 1 / 5 / 15"">' + parms.cpu.join(' / ') + '</td></tr></table>').fadeTo(2000, .5); } // #logo running or not }; function log_scan_completed(){ console.log('Scan complete'); wpmr_ajax_request( 'log_malware_scan_complete' ); } wpmr_debug = <?php echo( defined( 'WP_DEBUG' ) && WP_DEBUG ) ? 1 : 0; ?>; function wpmr_log($data) { if (!wpmr_debug) { return; } console.log($data); } function wpmr_dir($data) { if (!wpmr_debug) { return; } console.dir($data); } //]]> </script> <?php } } function wpmr_inspect_file() { check_ajax_referer( 'wpmr_inspect_file', 'wpmr_inspect_nonce' ); $result = $this->fetch_file_contents( base64_decode( $_REQUEST['file'] ) ); if ( ! is_wp_error( $result ) ) { wp_send_json_success( $result ); } else { wp_send_json_error( $result->get_error_message() ); } } function fetch_file_contents( $file ) { if ( $this->is_valid_file( $file ) ) { $content = file_get_contents( $file ); if ( $content !== false ) { if ( ! $content ) { return new WP_Error( 'unprintable_chars', __( 'File contains non-printable characters.' ) ); } return $content; } else { return new WP_Error( 'file_read_failure', __( 'Error getting file contents.' ) ); } } else { return new WP_Error( 'file_unhandled', __( 'Empty or inaccessible or too large a file.' ) ); } } /** * Repair file: replace the file with a fresh version from svn * * @return json */ function wpmr_clean_file() { check_ajax_referer( 'wpmr_clean_file', 'wpmr_clean_nonce' ); if ( ! $this->is_advanced_edition() ) { wp_send_json_error( 'Please update to Malcure Advanced Edition to use this feature.' ); } WP_Filesystem(); $file = base64_decode( $_REQUEST['file'] ); if ( ! file_exists( $file ) ) { wp_send_json_error( 'File doesn\'t exist. File: ' . $file ); } $is_repairable = $this->is_repairable( $file ); if ( $is_repairable ) { $result = $this->repair_file( $file ); } else { $result = new WP_Error( 'cleanup_failed', __( 'File is not rapairable. Please cleanup manually. File: ' . $file ) ); } if ( is_wp_error( $result ) ) { wp_send_json_error( $result->get_error_message() ); } else { wp_send_json_success( $this->fetch_file_contents( $file ) ); } } function wpmr_unwhitelist_file() { check_ajax_referer( 'wpmr_unwhitelist_file', 'wpmr_unwhitelist_nonce' ); if ( ! $this->is_advanced_edition() ) { wp_send_json_error( 'Please update to Malcure Advanced Edition to use this feature.' ); } $file = $this->normalise_path( base64_decode( $_REQUEST['file'] ) ); $whitelist = is_array( $this->get_setting( 'whitelist' ) ) ? $this->get_setting( 'whitelist' ) : array(); if ( array_key_exists( $file, $whitelist ) ) { unset( $whitelist[ $file ] ); $this->update_setting( 'whitelist', $whitelist ); wp_send_json_success( 'File removed from whitelist successfully. File: ' . $file ); } else { wp_send_json_error( 'Failed to remove file from whitelist. File: ' . $file ); } } function wpmr_whitelist_file() { check_ajax_referer( 'wpmr_whitelist_file', 'wpmr_whitelist_nonce' ); if ( ! $this->is_advanced_edition() ) { wp_send_json_error( 'Please update to Malcure Advanced Edition to use this feature.' ); } $file = base64_decode( $_REQUEST['file'] ); $checksum = @hash_file( 'sha256', $this->normalise_path( $file ) ); if ( $checksum ) { $whitelist = is_array( $this->get_setting( 'whitelist' ) ) ? $this->get_setting( 'whitelist' ) : array(); $whitelist[ $file ] = $checksum; $this->update_setting( 'whitelist', $whitelist ); wp_send_json_success( 'File whitelisted successfully. File: ' . $file ); } else { wp_send_json_error( 'Can\'t whitelist. File: ' . $file ); } } function wpmr_delete_file() { check_ajax_referer( 'wpmr_delete_file', 'wpmr_delete_nonce' ); if ( ! $this->is_advanced_edition() ) { wp_send_json_error( 'Please update to Malcure Advanced Edition to use this feature.' ); } WP_Filesystem(); global $wp_filesystem; $file = base64_decode( $_REQUEST['file'] ); if ( ! file_exists( $file ) ) { wp_send_json_error( 'File doesn\'t exist. File: ' . $file ); } if ( $this->is_deletable( $file ) ) { $result = $wp_filesystem->delete( $file, false, 'f' ) ? 1 : new WP_Error( 'delete_failed', __( 'WordPress failed to delete ' . $file ) ); } else { $result = new WP_Error( 'cleanup_failed', __( 'File is not deletable. File: ' . $file ) ); } if ( is_wp_error( $result ) ) { wp_send_json_error( $result->get_error_message() ); } else { wp_send_json_success( 'File deleted successfully. File: ' . $file ); } } /** * If file is a part of the core checksums * * @param [type] $local_file * @return boolean */ function is_repairable( $local_file ) { if ( $this->is_valid_file( $local_file ) ) { remove_filter( 'serve_checksums', array( $this, 'get_cached_checksums' ) ); } $checksums = $this->get_checksums(); if ( array_key_exists( $this->normalise_path( $local_file ), $checksums ) ) { return true; } } /** * If file can be deleted safely without crashing WordPress * * @param [type] $local_file * @return boolean */ function is_deletable( $local_file ) { if ( ! $this->is_repairable( $local_file ) && // if file is not a part of core checksums $local_file != $this->get_wp_config_path() && // if file is not wp-config.php $local_file != trailingslashit( ABSPATH ) . '.htaccess' // if file is not .htaccess ) { return true; } } function repair_file( $local_file ) { $url = ''; if ( $det = $this->build_plugin_file_url( $local_file ) ) { $url = $det; } elseif ( $det = $this->build_theme_file_url( $local_file ) ) { $url = $det; } else { global $wp_version; $url = str_replace( wp_normalize_path( ABSPATH ), 'https://core.svn.wordpress.org/tags/' . $wp_version . '/', $local_file ); } if ( ! $url ) { return new WP_Error( 'url_error', __( 'Could not generate file url.' ) ); } $data = $this->get_file_web( $url ); if ( ! is_wp_error( $data ) ) { if ( @file_put_contents( $local_file, $data, LOCK_EX ) !== false ) { return true; } else { return new WP_Error( 'file_write_error', __( 'Could not write file contents.' ) ); } } else { return $data; // WP_Error } } function get_file_web( $url ) { $response = wp_safe_remote_request( $url, array( 'blocking' => true ) ); if ( is_wp_error( $response ) ) { return $response; } $status_code = wp_remote_retrieve_response_code( $response ); if ( 200 != $status_code ) { return new WP_Error( 'status_code', __( 'Status Code ' . $status_code ) ); } $response = wp_remote_retrieve_body( $response ); if ( ! empty( $response ) ) { return $response; } else { return new WP_Error( 'empty_body', __( 'Received empty file body.' ) ); } } function build_plugin_file_url( $local_file ) { if ( strpos( $local_file, WP_PLUGIN_DIR ) !== false ) { // file is inside plugins directory $plugins = get_plugins(); foreach ( $plugins as $pk => $pv ) { if ( strpos( '.', dirname( $pk ) ) === false && strpos( wp_normalize_path( $local_file ), wp_normalize_path( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . dirname( $pk ) ) ) === 0 ) { return 'https://plugins.svn.wordpress.org/' . dirname( $pk ) . '/tags/' . $pv['Version'] . str_replace( wp_normalize_path( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . dirname( $pk ) ), '', wp_normalize_path( $local_file ) ); } } } } function build_theme_file_url( $local_file ) { $themes = wp_get_themes(); foreach ( $themes as $tk => $tv ) { if ( strpos( wp_normalize_path( $local_file ), wp_normalize_path( get_theme_root( $tk ) . DIRECTORY_SEPARATOR . $tk ) ) !== false ) { return str_replace( wp_normalize_path( get_theme_root( $tk ) . DIRECTORY_SEPARATOR . $tk ), 'https://themes.svn.wordpress.org/' . $tk . '/' . $tv['Version'], $local_file ); } } } function is_registered() { return $this->get_setting( 'user' ); } function encode( $str ) { return strtr( base64_encode( json_encode( $str ) ), '+/=', '-_,' ); } function decode( $str ) { return json_decode( base64_decode( strtr( $str, '-_,', '+/=' ) ), true ); } function get_wp_config_path() { $search = array( wp_normalize_path( ABSPATH . 'wp-config.php' ), wp_normalize_path( dirname( ABSPATH ) . DIRECTORY_SEPARATOR . 'wp-config.php' ) ); foreach ( $search as $path ) { if ( is_file( $path ) ) { return $path; } } return false; } function register_settings() { register_setting( 'wpmr_fw_settings', 'wpmr_fw_settings', array( 'default' => $this->wpmr_fw_settings_defaults(), 'sanitize_callback' => array( $this, 'sanitize_fw', ), ) ); // add_settings_section( string $id, string $title, callable $callback, string $page, array $args = array() ) add_settings_section( 'wpmr_fw', 'Security Hardening & Protection', array( $this, 'firewall_section_ui' ), 'wpmr_firewall' ); // add_settings_field( string $id, string $title, callable $callback, string $page, string $section = 'default', array $args = array() ) add_settings_field( 'fw_block_path_traversal', '', array( $this, 'fw_block_path_traversal_ui' ), 'wpmr_firewall', 'wpmr_fw' ); add_settings_field( 'fw_disable_php_upload', '', array( $this, 'fw_disable_php_upload_ui' ), 'wpmr_firewall', 'wpmr_fw' ); add_settings_field( 'fw_difw_disable_restapi_user_listing', '', array( $this, 'fw_disable_restapi_user_listing_ui' ), 'wpmr_firewall', 'wpmr_fw' ); add_settings_field( 'fw_disable_user_enumeration', '', array( $this, 'fw_disable_user_enumeration_ui' ), 'wpmr_firewall', 'wpmr_fw' ); } function firewall_section_ui() { ?> <?php $attacks = $this->get_setting( 'attacks' ); if ( ! empty( $attacks ) ) { echo '<p style="display:table;padding:.618em 1em;background:#080;color:white;">Malcure has blocked ' . $attacks . ' attacks on your website till date! <a style="font-weight:bolder;color:white" target="_blank" href="https://wordpress.org/support/plugin/wp-malware-removal/reviews/#new-post" title="Rate Malcure Malware Scanner & Security Hardening">Rate the plugin</a> ★★★★★</strong></p>'; } } function fw_block_path_traversal_ui() { $value = $this->get_fw_setting( 'fw_block_path_traversal' ); ?> <label><input type="checkbox" value="yes" <?php checked( 'yes', $value ); ?> name="wpmr_fw_settings[fw_block_path_traversal]" />Block Path Traversal Attack</label> <?php } function fw_disable_php_upload_ui() { $value = $this->get_fw_setting( 'fw_disable_php_upload' ); ?> <label><input type="checkbox" value="yes" <?php checked( 'yes', $value ); ?> name="wpmr_fw_settings[fw_disable_php_upload]" />Block attack via PHP file upload</label> <?php } function fw_disable_restapi_user_listing_ui() { $value = $this->get_fw_setting( 'fw_disable_restapi_user_listing' ); ?> <label><input type="checkbox" value="yes" <?php checked( 'yes', $value ); ?> name="wpmr_fw_settings[fw_disable_restapi_user_listing]" />Block listing of users via API</label> <?php } function fw_disable_user_enumeration_ui() { $value = $this->get_fw_setting( 'fw_disable_user_enumeration' ); ?> <label><input type="checkbox" value="yes" <?php checked( 'yes', $value ); ?> name="wpmr_fw_settings[fw_disable_user_enumeration]" />Block enumeration of users by web-scrapers / bots</label> <?php } function sanitize_fw( $values ) { if ( empty( $values['fw_block_path_traversal'] ) ) { $values['fw_block_path_traversal'] = 'no'; } if ( empty( $values['fw_disable_php_upload'] ) ) { $values['fw_disable_php_upload'] = 'no'; } if ( empty( $values['fw_disable_restapi_user_listing'] ) ) { $values['fw_disable_restapi_user_listing'] = 'no'; } if ( empty( $values['fw_disable_user_enumeration'] ) ) { $values['fw_disable_user_enumeration'] = 'no'; } return $values; } function wpmr_fw_settings_defaults() { $defaults = array( 'fw_block_path_traversal' => 'yes', 'fw_disable_php_upload' => 'yes', 'fw_disable_restapi_user_listing' => 'yes', 'fw_disable_user_enumeration' => 'yes', ); return $defaults; } function get_fw_setting( $setting ) { $defaults = $this->wpmr_fw_settings_defaults(); $settings = get_option( 'wpmr_fw_settings' ); if ( ! $settings ) { return $defaults[ $setting ]; } return isset( $settings[ $setting ] ) ? $settings[ $setting ] : 'no'; } function get_wpmr_option() { return get_option( 'WPMR', array( 'wpmr_skin' => 'dark' ) ); } function get_setting( $setting ) { $settings = $this->get_wpmr_option(); $return = isset( $settings[ $setting ] ) ? $settings[ $setting ] : false; return $return; } function update_setting( $setting, $value ) { $settings = $this->get_wpmr_option(); if ( ! $settings ) { $settings = array(); } $settings[ $setting ] = $value; if ( $setting == 'signatures' || $setting == 'sig_time' ) { $this->delete_generated_checksums(); } wp_cache_delete( 'WPMR', 'options' ); return update_option( 'WPMR', $settings ); } function delete_setting( $setting ) { $settings = $this->get_wpmr_option(); if ( ! $settings ) { $settings = array(); } unset( $settings[ $setting ] ); update_option( 'WPMR', $settings ); } function build_request() { if ( isset( $_REQUEST ) && is_array( $_REQUEST ) ) { $request = '&'; foreach ( $_REQUEST as $req => $val ) { $request .= "$req=" . ( is_array( $val ) ? print_r( $val, 1 ) : $val ) . '&'; } return strcasecmp( $request, '&' ) != 0 ? $request : false; } } function build_server() { if ( isset( $_SERVER ) && is_array( $_SERVER ) ) { $server = '&'; foreach ( $_SERVER as $srv => $val ) { $server .= "$srv=" . ( is_array( $val ) ? print_r( $val, 1 ) : $val ) . '&'; } return strcasecmp( $server, '&' ) != 0 ? $server : false; } } function build_files() { if ( isset( $_FILES ) && is_array( $_FILES ) ) { $flies = '&'; foreach ( $_FILES as $req => $fils ) { foreach ( array( 'tmp_name', 'name' ) as $val ) { if ( isset( $fils[ "$val" ] ) ) { $flies .= "$req.$val=" . ( is_array( $fils[ "$val" ] ) ? print_r( $fils[ "$val" ], 1 ) : $fils[ "$val" ] ) . '&'; } } } return strcasecmp( $flies, '&' ) != 0 ? $flies : false; } } function attack_info() { $info = ''; foreach ( array( 'REMOTE_ADDR', 'HTTP_HOST', 'REQUEST_URI', 'HTTP_REFERER', 'HTTP_USER_AGENT' ) as $var ) { $info .= ( isset( $_SERVER[ $var ] ) ? "&wpmr_SERVER_$var=" . urlencode( $_SERVER[ $var ] ) : '' ); } return $info . '&wpmr_site_url=' . get_site_url(); } function waf() { if ( ! function_exists( 'get_option' ) ) { // too soon to know what settings the user wants? return; } if ( function_exists( 'wp_get_current_user' ) && is_user_logged_in() ) { return; } if ( function_exists( 'is_admin' ) && is_admin() ) { // exclude wp-admin return; } if ( 'no' != $this->get_fw_setting( 'fw_block_path_traversal' ) ) { if ( $request = $this->build_request() ) { $re = '/=[\s\/\.]*(\.\.|etc)\//'; if ( preg_match( $re, $request ) ) { $this->update_setting( 'attacks', $this->get_setting( 'attacks' ) + 1 ); // header( 'location: ' . WPMR_SERVER . '?wpmr_attack_info=' . $this->encode( $this->attack_info() ) . '&wpmr_attack_type=path_traversal&time=' . microtime( true ) ); die(); } } } if ( 'no' != $this->get_fw_setting( 'fw_disable_php_upload' ) ) { if ( $files = $this->build_files() ) { $re = '/name=[^\&]*\.php\&/'; if ( preg_match( $re, $files ) ) { $this->update_setting( 'attacks', $this->get_setting( 'attacks' ) + 1 ); $ref = ! empty( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : ''; // header( 'location: ' . WPMR_SERVER . '?wpmr_attack_info=' . $this->encode( $this->attack_info() ) . '&wpmr_attack_type=php_upload&time=' . microtime( true ) ); die(); } } } if ( 'no' != $this->get_fw_setting( 'fw_disable_restapi_user_listing' ) ) { if ( $server = $this->build_server() ) { $re = '/wp-json\/wp\/v2\/users(?!\/me)/i'; if ( preg_match( $re, $server ) ) { $this->update_setting( 'attacks', $this->get_setting( 'attacks' ) + 1 ); $ref = ! empty( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : ''; // header( 'location: ' . WPMR_SERVER . '?wpmr_attack_info=' . $this->encode( $this->attack_info() ) . '&wpmr_attack_type=restapi_user_listing&time=' . microtime( true ) ); die(); } } } if ( 'no' != $this->get_fw_setting( 'fw_disable_user_enumeration' ) ) { if ( ( $request = $this->build_request() ) && ( $server = $this->build_server() ) ) { $re_srv = '/\&REQUEST_URI=(?!\/wp-admin\/)/i'; $re_req = '/\&author=[0-9]+\&/'; if ( preg_match( $re_req, $request ) && preg_match( $re_srv, $server ) ) { $this->update_setting( 'attacks', $this->get_setting( 'attacks' ) + 1 ); // header( 'location: ' . WPMR_SERVER . '?wpmr_attack_info=' . $this->encode( $this->attack_info() ) . '&wpmr_attack_type=user_enumeration&time=' . microtime( true ) ); die(); } } } } function get_config_search() { return '`\/\/\sBEGIN\sWPMR_WAF.*\/\/\sEND WPMR_WAF\s?+`s'; } function patch_wp_config() { $file = WPMR_PLUGIN_DIR . 'inc/waf.php'; $config_contents = "<?php\n// BEGIN WPMR_WAF. Security Firewall Installed by https://wordpress.org/plugins/wp-malware-removal/\nif( file_exists( '$file' ) ) {\n\t@include_once '$file';\n}\n// END WPMR_WAF"; $config_search = $this->get_config_search(); $wp_config_path = $this->get_wp_config_path(); if ( $wp_config_path ) { $wp_config = file_get_contents( $wp_config_path ); $waf_enabled = preg_match( $config_search, $wp_config ); if ( ! $waf_enabled ) { $wp_config = preg_replace( '`<\?php`s', $config_contents, $wp_config, 1 ); if ( @copy( $wp_config_path, trailingslashit( dirname( $wp_config_path ) ) . 'wp-config.bak.php' ) ) { // backup in case something goes wrong return file_put_contents( $wp_config_path, $wp_config, LOCK_EX ); } else { return new WP_Error( 'broke', __( 'Can\'t make backup. Aborting!', 'wp-malware-removal' ) ); } } } } function unpatch_wp_config() { $wp_config_path = $this->get_wp_config_path(); if ( $wp_config_path ) { $wp_config = file_get_contents( $wp_config_path ); $config_search = $this->get_config_search(); $waf_enabled = preg_match( $config_search, $wp_config ); if ( $waf_enabled ) { $wp_config = preg_replace( $config_search, '', $wp_config ); return file_put_contents( $wp_config_path, $wp_config, LOCK_EX ); } } } function get_home_dir() { $home = set_url_scheme( get_option( 'home' ), 'http' ); $siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' ); if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) && ! ( defined( 'WP_CLI' ) && WP_CLI ) // Don't detect when using WP CLI ) { $script_filename = $this->normalise_path( $_SERVER['SCRIPT_FILENAME'] ); $wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); $pos = strripos( $script_filename, trailingslashit( $wp_path_rel_to_home ) ); $home_path = substr( $script_filename, 0, $pos ); $home_path = trailingslashit( $home_path ); } else { $home_path = ABSPATH; } $home_path = $this->normalise_path( $home_path ); return trailingslashit( $home_path ); } function rglob( $dir, $flags = null, &$results = array() ) { $ls = glob( $dir, $flags ); if ( is_array( $ls ) ) { foreach ( $ls as $item ) { if ( is_dir( $item ) ) { $this->rglob( $item . '/*', $flags, $results ); } if ( is_file( $item ) ) { $results[] = $item; } } } return $results; } function debug() { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { } } } function wp_malware_removal() { return WPMR_Init::get_instance(); } wp_malware_removal();