File: /home/zwafgyp/dodolodge-net/wp-content/plugins/wp-malware-removal/traits/wpmr_helpers.php
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
trait WPMR_Helpers {
/**
* Get sanitized remote IP address
*
* @return string Sanitized IP address or 'unknown'
*/
function get_remote_ip() {
return isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( $_SERVER['REMOTE_ADDR'] ) : 'unknown'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- IP address doesn't need unslashing, sanitized below
}
function get_memory_limit_in_mb() {
$memory_limit = ini_get( 'memory_limit' );
if ( $memory_limit === '-1' ) {
// No limit
return PHP_INT_MAX;
}
$unit = strtolower( substr( $memory_limit, -1 ) );
$value = (int) $memory_limit;
switch ( $unit ) {
case 'g':
$value *= 1024;
break;
case 'k':
$value /= 1024;
break;
case 'b':
$value /= 1048576; // 1024 * 1024
break;
}
return $value;
}
function get_remote_timeout() {
$buffer = 5;
$fallback = 25;
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
$raw = @ini_get( 'max_execution_time' ); // string|false
if ( $raw === false || $raw === '' || $raw === null ) {
return $fallback;
}
$met = (int) trim( (string) $raw ); // 0 => unlimited
if ( $met <= $buffer ) {
return $fallback;
}
return $met - $buffer;
}
function raise_limits_conditionally() {
if ( strpos( ini_get( 'disable_functions' ), 'ini_set' ) === false ) {
$current_limit = $this->get_memory_limit_in_mb();
if ( $current_limit < $this->mem ) {
@ini_set( 'memory_limit', $this->mem . 'M' ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Necessary for memory management during scanning
}
}
if ( defined( 'WP_CLI' ) && WP_CLI ) {
// Do WP-CLI specific things.
@ini_set( 'max_execution_time', 0 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Necessary for CLI execution time management
} else {
@ini_set( 'max_execution_time', max( (int) @ini_get( 'max_execution_time' ), 90 ) ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Necessary for execution time management during scanning
}
}
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 encode( $str ) {
return strtr( base64_encode( json_encode( $str ) ), '+/=', '-_,' );
}
function decode( $str ) {
return json_decode( base64_decode( strtr( $str, '-_,', '+/=' ) ), true );
}
function mc_get_bool( $var ) {
return filter_var( $var, FILTER_VALIDATE_BOOLEAN );
}
function unleadingslashit( $string ) {
return ltrim( $string, '/\\' );
}
function decode_filename( $filename ) {
return urldecode( base64_decode( $filename ) );
}
function wpmr_iscli() {
return defined( 'WP_CLI' ) && WP_CLI;
}
function deactivate() {
wp_clear_scheduled_hook( 'wpmr_daily' );
wp_clear_scheduled_hook( 'wpmr_hourly' );
// Silently deactivate license without user messages
$this->deactivate_license( true );
}
function reset( $reset_logs = false ) {
if ( wp_doing_ajax() ) {
check_ajax_referer( 'wpmr_reset', 'wpmr_reset_nonce' );
if ( ! current_user_can( $this->cap ) ) {
return;
}
}
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->clear_license_status();
if ( wp_doing_ajax() ) {
return wp_send_json_success( 'Reset Successful!' );
} else {
return 'Reset Successful!';
}
}
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 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 = isset( $_SERVER['SCRIPT_FILENAME'] ) ? $this->normalise_path( sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_FILENAME'] ) ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- SCRIPT_FILENAME validated, unslashed and sanitized
$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 build_files() {
if ( isset( $_FILES ) && is_array( $_FILES ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Debug function for logging file upload data
$flies = '&';
foreach ( $_FILES as $req => $fils ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Debug function for logging file upload data
foreach ( array( 'tmp_name', 'name' ) as $val ) {
if ( isset( $fils[ "$val" ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Debug function for logging file upload data
$flies .= "$req.$val=" . ( is_array( $fils[ "$val" ] ) ? print_r( $fils[ "$val" ], 1 ) : $fils[ "$val" ] ) . '&'; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug function for logging
}
}
}
return strcasecmp( $flies, '&' ) != 0 ? $flies : false;
}
}
function build_request() {
if ( isset( $_REQUEST ) && is_array( $_REQUEST ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Debug function for logging request data
$request = '&';
foreach ( $_REQUEST as $req => $val ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Debug function for logging request data
$request .= "$req=" . ( is_array( $val ) ? print_r( $val, 1 ) : $val ) . '&'; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug function for logging
}
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 ) . '&'; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug function for logging
}
return strcasecmp( $server, '&' ) != 0 ? $server : false;
}
}
function get_diag_data() {
global $wp_version;
$current_user = wp_get_current_user();
$data = array(
'php' => phpversion(),
'web_server' => isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : 'unknown',
'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_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 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'] ) {
$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 init() {
$this->dir = trailingslashit( plugin_dir_path( $this->normalise_path( __FILE__ ) ) );
$this->url = trailingslashit( plugin_dir_url( __FILE__ ) );
$this->timeout = $this->get_remote_timeout();
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_refresh_checksums', array( $this, 'wpmr_refresh_checksums' ) );
add_action( 'wp_ajax_nopriv_wpmr_refresh_checksums', array( $this, '__return_false' ) );
add_action( 'wp_ajax_wpmr_def_auto_update_enabled', array( $this, 'update_wpmr_def_auto_update' ) );
add_action( 'wp_ajax_wpmr_license_action', array( $this, 'wpmr_license_action' ) );
add_action( 'wp_ajax_wpmr_fetch_license_status', array( $this, 'ajax_get_license_status' ) );
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, 'operations_overlay' ) );
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 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 refresh_checksums_async() {
if ( ! current_user_can( $this->cap ) ) {
$this->flog( 'Unauthorized attempt to refresh checksums in ' . __FUNCTION__ );
return false;
}
// make an ajax request to admin-ajax.php to trigger the refresh_checksums function
$ajax_url = admin_url( 'admin-ajax.php' );
$args = array(
'timeout' => $this->timeout,
'blocking' => false,
'body' => array(
'action' => 'wpmr_refresh_checksums',
'nonce' => wp_create_nonce( 'wpmr_refresh_checksums' ),
),
);
$start_time = microtime( true );
// Use POST instead of GET to avoid nonce in URL
$response = wp_remote_post( $ajax_url, $args );
$this->flog( 'Async checksum refresh triggered in ' . ( microtime( true ) - $start_time ) . ' seconds' );
if ( is_wp_error( $response ) ) {
$this->flog( 'WPMR: Failed to trigger async checksum refresh: ' . print_r( $response, true ) );
return false;
} else {
$this->flog( 'WPMR: Async checksum refresh request sent successfully.' . print_r( $response, true ) );
}
return true;
}
function set_plugin_data() {
$this->plugin_data = $this->get_plugin_data( WPMR_PLUGIN, false, false );
$this->plugin_data['Slug'] = WPMR_SLUG;
}
function wpmr_license_action() {
check_ajax_referer( 'wpmr_license_action', 'wpmr_license_action_nonce' );
if ( ! current_user_can( $this->cap ) ) {
wp_send_json_error( 'Insufficient permissions.' );
}
$license_action = isset( $_REQUEST['license_action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['license_action'] ) ) : '';
if ( $license_action === 'deactivate' ) {
// Handle deactivation
$key = $this->get_setting( 'license_key' );
if ( ! $key ) {
wp_send_json_error( 'No active license found to deactivate.' );
}
$response = $this->get_license_api_response( 'deactivate_license', $key );
$this->flog( 'License ' . $key . ' deactivation attempt via AJAX : ' . print_r( $_REQUEST, 1 ) . '. API response: ' . print_r( $response, true ) );
if ( is_wp_error( $response ) ) {
wp_send_json_error( $response->get_error_message() );
}
if ( ! empty( $response['success'] ) ) {
$this->delete_setting( 'license_key' );
$this->clear_license_status();
wp_send_json_success(
array(
'message' => 'License deactivated successfully!',
'reload' => true,
)
);
} else {
wp_send_json_error( 'License deactivation failed.' );
}
} else {
// Handle activation
if ( ! isset( $_REQUEST['license_key'] ) || empty( $_REQUEST['license_key'] ) ) {
wp_send_json_error( 'License key is required.' );
}
$license_key = sanitize_text_field( wp_unslash( $_REQUEST['license_key'] ) );
$response = $this->get_license_api_response( 'activate_license', $license_key );
if ( is_wp_error( $response ) ) {
wp_send_json_error( $response->get_error_message() );
}
if ( ! empty( $response['success'] ) ) {
$this->flog( 'License activation successful via AJAX. API response: ' . print_r( $response, true ) );
$this->update_setting( 'license_key', $license_key );
$status = $this->save_license_status( $response );
if ( ! $this->is_registered() ) {
$name = $status['customer_name'];
$name = array_filter( explode( ' ', $name ) );
$email = $status['customer_email'];
$fn = empty( $name ) ? explode( '@', $email )[0] : array_shift( $name );
$ln = empty( $name ) ? explode( '@', $email )[0] : array_shift( $name );
$this->flog( 'User is not registered. Proceeding to register via API.' );
$start_time = microtime( true );
$this->wpmr_cli_register( $email, $fn, $ln, false );
$this->flog( 'User registration completed in ' . ( microtime( true ) - $start_time ) . ' seconds.' );
if ( ! $this->get_setting( 'sig_time' ) ) {
$this->flog( 'No signature time found. Proceeding to update definitions via API.' );
$start_time = microtime( true );
$update = $this->update_definitions_cli( false );
$this->flog( 'Definitions update completed in ' . ( microtime( true ) - $start_time ) . ' seconds. Update response: ' . print_r( $update, true ) );
} else {
$this->flog( 'Signature time found. Skipping definitions update via API.' );
}
} else {
$this->flog( 'User already registered. Skipping registration via API.' );
}
wp_send_json_success(
array(
'message' => 'License activated successfully!',
'status' => $status,
'reload' => true,
)
);
} else {
$this->flog( 'License activation ' . print_r( $_REQUEST, 1 ) . ' failed via AJAX. API response: ' . print_r( $response, true ) );
wp_send_json_error( 'License activation failed. Please check your license key.' );
}
}
}
function wpmr_refresh_checksums() {
$this->flog( 'Starting checksum refresh via AJAX' );
check_ajax_referer( 'wpmr_refresh_checksums', 'nonce' );
$this->flog( 'check_ajax_referer passed.' );
if ( ! current_user_can( $this->cap ) ) {
$this->flog( 'Unauthorized attempt to refresh checksums in ' . __FUNCTION__ );
wp_send_json( array( 'error' => 'Unauthorized' ) );
}
$start_time = microtime( true );
$this->get_checksums();
$this->flog( 'Checksums refreshed in ' . ( microtime( true ) - $start_time ) . ' seconds' );
wp_send_json_success( array( 'message' => 'Checksums refreshed' ) );
}
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>';
return $links;
}
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>';
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 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 = gmdate( 'Ymd-G:i:s' ) . '-' . microtime( true );
file_put_contents( $file, $date . PHP_EOL, FILE_APPEND | LOCK_EX );
}
$data = print_r( $data, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Used for logging in malware scanner debug mode
$result = file_put_contents( $file, $data . PHP_EOL, FILE_APPEND | LOCK_EX );
if ( $result === false ) {
error_log( 'Failed to write to log file: ' . $file ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Used for logging scanner errors
}
}
}
function llog( $str, $echo = true ) {
if ( $echo ) {
echo '<pre>';
print_r( $str ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug logging function for development use
echo '</pre>';
} else {
return print_r( $str, 1 ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug logging function for development use
}
}
function get_fileext( $filename ) {
$nameparts = explode( '.', ".$filename" );
return strtolower( $nameparts[ ( count( $nameparts ) - 1 ) ] );
}
function set_status( $severity, $msg, $ver ) {
$msg = wp_strip_all_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 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;
}
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_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query needed to find plugin-specific log options by pattern, no prepared statement needed for static query, caching not beneficial for cleanup operation.
// Loop through the options and delete each one
foreach ( $option_names as $option_name ) {
delete_option( $option_name );
}
}
function wpmr_enqueue_js_dependencies() {
wp_enqueue_script( 'jquery' );
wp_enqueue_script( 'common' );
wp_enqueue_script( 'wp-lists' );
wp_enqueue_script( 'postbox' );
}
function update_wpmr_def_auto_update() {
check_ajax_referer( 'wpmr_def_auto_update_enabled', 'wpmr_def_auto_update_enabled_nonce' );
if ( ! current_user_can( $this->cap ) ) {
return;
}
$enabled = isset( $_REQUEST['enabled'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['enabled'] ) ) : '';
if ( $enabled == 'false' ) {
$this->update_setting( 'def_auto_update_enabled', false );
}
if ( $enabled == 'true' ) {
$this->update_setting( 'def_auto_update_enabled', true );
}
wp_send_json_success( $this->get_setting( 'def_auto_update_enabled' ) );
}
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 reset_flog() {
$file = $this->dir . 'log.log';
file_put_contents( $file, '', LOCK_EX );
}
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, array( 'timeout' => $this->timeout ) );
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">' . wp_kses_post( $upgrade_notice ) . '</span>';
}
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/', array( 'timeout' => $this->timeout ) );
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;
}
function wpmr_inspect_file() {
check_ajax_referer( 'wpmr_inspect_file', 'wpmr_inspect_nonce' );
if ( ! current_user_can( $this->cap ) ) {
wp_send_json_error( 'Unauthorized access' );
return;
}
$file = base64_decode( sanitize_text_field( wp_unslash( $_REQUEST['file'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via check_ajax_referer
// Additional security check: validate file path to prevent path traversal
if ( ! $this->is_safe_file_path( $file ) ) {
wp_send_json_error( 'Invalid file path detected.' );
return;
}
$result = $this->fetch_file_contents( $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 ) {
// Security check: validate file path to prevent path traversal
if ( ! $this->is_safe_file_path( $file ) ) {
return new WP_Error( 'invalid_path', 'Invalid file path detected.' );
}
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.' );
}
}
function wpmr_unwhitelist_file() {
check_ajax_referer( 'wpmr_unwhitelist_file', 'wpmr_unwhitelist_nonce' );
if ( ! current_user_can( $this->cap ) ) {
return;
}
$file = $this->normalise_path( base64_decode( sanitize_text_field( wp_unslash( $_REQUEST['file'] ) ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via check_ajax_referer
$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 ( ! current_user_can( $this->cap ) ) {
return;
}
$file = base64_decode( sanitize_text_field( wp_unslash( $_REQUEST['file'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via check_ajax_referer
// Security check: validate file path to prevent path traversal
if ( ! $this->is_safe_file_path( $file ) ) {
wp_send_json_error( 'Invalid file path detected.' );
}
$normalized_file = $this->normalise_path( $file );
if ( $this->is_in_core_wp_dir( $normalized_file ) ) {
wp_send_json_error( 'Error: Whitelisting core WordPress files is a security risk. File: ' . $normalized_file );
}
if ( ! file_exists( $normalized_file ) ) {
wp_send_json_error( 'File doesn\'t exist. File: ' . $normalized_file );
}
// Request whitelist action from SaaS control plane (license REQUIRED)
$response = $this->request_saas_action( 'saas_whitelist_file', $normalized_file );
if ( is_wp_error( $response ) ) {
$error_data = $response->get_error_data();
if ( ! empty( $error_data ) ) {
wp_send_json_error( $error_data );
}
wp_send_json_error( $response->get_error_message() );
}
// Validate response signature
// Validation is now handled inside request_saas_action
// $validation = $this->validate_saas_response( $response );
// if ( ! $validation['valid'] ) {
// wp_send_json_error( $validation['error'] );
// }
if ( isset( $response['payload']['message'] ) ) {
$response['payload']['message'] = $this->sanitize_saas_reason_html( $response['payload']['message'] );
}
// Check if action is available
if ( empty( $response['payload']['available'] ) ) {
wp_send_json_error( isset( $response['payload']['message'] ) ? $response['payload']['message'] : 'Action unavailable' );
}
// Execute whitelist action
$result = $this->perform_whitelist_action( $response, $normalized_file );
if ( is_wp_error( $result ) ) {
wp_send_json_error( $result->get_error_message() );
}
wp_send_json_success( 'File whitelisted successfully. File: ' . $normalized_file );
}
function is_safe_file_path( $file_path ) {
if ( empty( $file_path ) ) {
return false;
}
// Normalize the path
$normalized_path = $this->normalise_path( $file_path );
// Check for path traversal attempts and block outright
if ( strpos( $normalized_path, '../' ) !== false || strpos( $normalized_path, '..\/' ) !== false ) {
return false;
}
// Resolve the real path to handle symbolic links and relative paths
$real_path = realpath( $normalized_path );
if ( $real_path === false ) {
// If realpath fails, the file might not exist, but we can still validate the path structure
$real_path = $normalized_path;
} else {
$real_path = $this->normalise_path( $real_path );
}
// Define allowed base directories
$allowed_dirs = array(
$this->normalise_path( ABSPATH ),
$this->normalise_path( WP_CONTENT_DIR ),
$this->normalise_path( WP_PLUGIN_DIR ),
$this->normalise_path( get_theme_root() ),
);
$uploads = wp_get_upload_dir();
if ( ! empty( $uploads['basedir'] ) && $this->is_valid_dir( $uploads['basedir'] ) ) {
$allowed_dirs[] = $this->normalise_path( $uploads['basedir'] );
}
// Check if the file is within any of the allowed directories
$is_within_allowed = false;
foreach ( $allowed_dirs as $allowed_dir ) {
if ( strpos( $normalized_path, $allowed_dir ) === 0 ) {
$is_within_allowed = true;
break;
}
}
return $is_within_allowed;
}
function is_repairable( $local_file ) {
// Security check: validate file path to prevent path traversal
if ( ! $this->is_safe_file_path( $local_file ) ) {
return false;
}
if ( $this->is_valid_file( $local_file ) ) {
remove_filter( 'serve_checksums', array( $this, 'get_cached_checksums' ), 11 );
remove_filter( 'serve_checksums', array( $this, 'whitelist' ), 9999 );
$checksums = $this->get_checksums();
if ( array_key_exists( $this->normalise_path( $local_file ), $checksums ) ) {
return true;
}
}
}
function wpmr_clean_file() {
check_ajax_referer( 'wpmr_clean_file', 'wpmr_clean_nonce' );
if ( ! current_user_can( $this->cap ) ) {
return;
}
WP_Filesystem();
$file = base64_decode( sanitize_text_field( wp_unslash( $_REQUEST['file'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via check_ajax_referer
// Security check: validate file path to prevent path traversal
if ( ! $this->is_safe_file_path( $file ) ) {
wp_send_json_error( 'Invalid file path detected.' );
}
if ( ! file_exists( $file ) ) {
wp_send_json_error( 'File doesn\'t exist. File: ' . $file );
}
if ( ! $this->is_valid_file( $file ) || ! $this->is_repairable( $file ) ) {
$result = new WP_Error( 'cleanup_failed', 'File is not repairable. Please cleanup manually. File: ' . $file );
wp_send_json_error( $result->get_error_message() );
}
// Request repair action from SaaS control plane
$response = $this->request_saas_action( 'saas_repair_file', $file );
if ( is_wp_error( $response ) ) {
$error_data = $response->get_error_data();
if ( ! empty( $error_data ) ) {
wp_send_json_error( $error_data );
}
wp_send_json_error( $response->get_error_message() );
}
// Validate response signature
// Validation is now handled inside request_saas_action
// $validation = $this->validate_saas_response( $response );
// if ( ! $validation['valid'] ) {
// wp_send_json_error( $validation['error'] );
// }
if ( isset( $response['payload']['message'] ) ) {
$response['payload']['message'] = $this->sanitize_saas_reason_html( $response['payload']['message'] );
}
// Check if action is available
if ( empty( $response['payload']['available'] ) ) {
wp_send_json_error( isset( $response['payload']['message'] ) ? $response['payload']['message'] : 'Action unavailable' );
}
// Execute repair action
$result = $this->perform_repair_action( $response, $file );
if ( is_wp_error( $result ) ) {
wp_send_json_error( $result->get_error_message() );
}
wp_send_json_success( $this->fetch_file_contents( $file ) );
}
function is_deletable( $local_file ) {
// Security check: validate file path to prevent path traversal
if ( ! $this->is_safe_file_path( $local_file ) ) {
$this->flog( 'is_deletable: Unsafe file path detected: ' . $local_file );
return false;
}
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 wpmr_delete_file() {
check_ajax_referer( 'wpmr_delete_file', 'wpmr_delete_nonce' );
if ( ! current_user_can( $this->cap ) ) {
return;
}
WP_Filesystem();
$file = base64_decode( sanitize_text_field( wp_unslash( $_REQUEST['file'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via check_ajax_referer
if ( ! $this->is_valid_file( $file ) ) {
wp_send_json_error( 'File is empty or inaccessible or too large a file. File: ' . $file );
}
// Security check: validate file path to prevent path traversal
if ( ! $this->is_safe_file_path( $file ) ) {
wp_send_json_error( 'Invalid file path detected.' );
}
if ( ! file_exists( $file ) ) {
wp_send_json_error( 'File doesn\'t exist. File: ' . $file );
}
// Local safety check (fail-fast for critical files)
if ( ! $this->is_deletable( $file ) ) {
wp_send_json_error( 'File is repairable. Skipping deletion. File: ' . $file );
}
// Request delete action from SaaS control plane (NO license required)
$response = $this->request_saas_action( 'saas_delete_file', $file );
if ( is_wp_error( $response ) ) {
$error_data = $response->get_error_data();
if ( ! empty( $error_data ) ) {
wp_send_json_error( $error_data );
}
wp_send_json_error( $response->get_error_message() );
}
// Validation is now handled inside request_saas_action
// $validation = $this->validate_saas_response( $response );
// if ( ! $validation['valid'] ) {
// wp_send_json_error( $validation['error'] );
// wp_send_json_error( $validation['error'] );
// }
if ( isset( $response['payload']['message'] ) ) {
$response['payload']['message'] = $this->sanitize_saas_reason_html( $response['payload']['message'] );
}
// Check if file is repairable - suggest repair instead
if ( empty( $response['payload']['available'] ) && ! empty( $response['payload']['is_repairable'] ) ) {
wp_send_json_error( isset( $response['payload']['message'] ) ? $response['payload']['message'] : 'File is repairable' );
}
// Check if action is available
if ( empty( $response['payload']['available'] ) ) {
wp_send_json_error( isset( $response['payload']['message'] ) ? $response['payload']['message'] : 'Action unavailable' );
}
// Execute delete action
$result = $this->perform_delete_action( $response, $file );
if ( is_wp_error( $result ) ) {
wp_send_json_error( $result->get_error_message() );
}
wp_send_json_success( 'File deleted successfully. File: ' . $file );
}
function get_remote_response( $url ) {
$response = wp_safe_remote_request( $url, array( 'timeout' => $this->timeout ) );
if ( 200 != wp_remote_retrieve_response_code( $response ) ) {
$this->flog( 'Failed to get remote response for URL: ' . $url . ' with response code: ' . wp_remote_retrieve_response_code( $response ) );
return;
}
if ( is_wp_error( $response ) ) {
$this->flog( 'Error retrieving remote response: ' . $response->get_error_message() );
return;
}
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 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
if ( ! is_array( $raw_checksums ) ) {
$raw_checksums = array();
}
$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 get_core_relative_path( $file ) {
$file = $this->normalise_path( $file );
// Core files: relative to ABSPATH (e.g., wp-admin/index.php)
// Plugins: relative to WP_PLUGIN_DIR (e.g., plugin-name/plugin.php)
// Themes: relative to theme root (e.g., theme-name/style.css)
// Everything else: relative to ABSPATH
return str_replace( trailingslashit( $this->normalise_path( ABSPATH ) ), '', $file );
}
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 );
}
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 );
// File is scannable if: exists, is a file, is within size limit, and either has content OR is an empty core file
$return = ( file_exists( $file ) && is_file( $file ) && ( filesize( $file ) || ( ! filesize( $file ) && $this->is_core_wp_file( $file ) ) || ! is_readable( $file ) ) && filesize( $file ) <= $this->maxsize );
return $return;
}
function is_valid_file( $file ) {
return ! $this->is_invalid_file( $file );
}
/** Checks if a file is invalid for scanning based on several criteria:
* - File does not exist
* - File is not readable
* - Path is not a file (could be a directory)
* - File is empty (0 bytes) and not a core WordPress file
* - File exceeds maximum allowed size for scanning
*/
function is_invalid_file( $file ) {
$file = $this->normalise_path( $file );
return ( ! file_exists( $file ) // Check if file doesn't exist
|| ! is_readable( $file ) // Check if file is not readable
|| ! is_file( $file ) // Check if path is not a file (could be a directory)
|| ( ! filesize( $file ) && ! $this->is_core_wp_file( $file ) ) // Check if file is empty (0 bytes) - empty files in core dirs need special handling
|| filesize( $file ) > $this->maxsize // Check if file exceeds maximum allowed size for scanning
);
}
function is_valid_dir( $dir ) {
$dir = $this->normalise_path( $dir );
return ( file_exists( $dir ) && is_dir( $dir ) && is_readable( $dir ) );
}
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 ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug function for development use
print_r( $v ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug function for development use
}
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;
}
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;
}
}
}
}
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 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 ] );
$result = update_option( 'WPMR', $settings ); // assignment gets around an opcache bug
return $result;
}
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 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 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;
}
/**
* 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,
sanitize_text_field( wp_unslash( $_REQUEST['request'][0] ) ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via is_callable check
)
) ) {
$this->flog( 'not callable', false, false, true );
$this->flog( sanitize_text_field( wp_unslash( $_REQUEST['request'][0] ) ), false, false, true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via is_callable check
wp_send_json_error( 'Not sure what to do.', false, false, true );
}
if ( ! empty( $_REQUEST['request'][1] ) ) {
if ( ! is_array( wp_unslash( $_REQUEST['request'][1] ) ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Unslashing and type checking for array validation
$this->flog( 'Arguments must be an array. You passed ' . gettype( wp_unslash( $_REQUEST['request'][1] ) ) . ' "' . print_r( wp_unslash( $_REQUEST['request'][1] ), 1 ) . '"', false, false, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r,WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Debug logging for malware scanner
wp_send_json_error( 'Arguments must be an array. You passed ' . gettype( wp_unslash( $_REQUEST['request'][1] ) ) . ' "' . print_r( wp_unslash( $_REQUEST['request'][1] ), 1 ) . '"' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r,WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Debug logging for malware scanner
}
$result = call_user_func_array(
array( $this, sanitize_text_field( wp_unslash( $_REQUEST['request'][0] ) ) ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via is_callable check
wp_unslash( $_REQUEST['request'][1] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Arguments are unslashed before function call
);
} else {
$result = call_user_func( array( $this, sanitize_text_field( wp_unslash( $_REQUEST['request'][0] ) ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via is_callable check
}
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 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 reset_logs() {
global $wpdb;
$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for cleanup operation
$wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
'_transient_WPMR\_log\_%',
'_transient\_timeout\_WPMR\_log\_%'
)
);
}
function normalize_host( $host ) {
return preg_replace( '/^www\./', '', $host );
}
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 get_users_loggedin() {
return get_users(
array(
'meta_key' => 'session_tokens', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'meta_compare' => 'EXISTS',
)
);
}
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_' . esc_html( $user->ID ) . '">';
echo '<tr><th class="user_details_id">User ID</th><td>' . esc_html( $user->ID ) . '</td></tr>';
echo '<tr><th class="user_details_roles">User Roles</th><td>' . esc_html( implode( ',', $user->roles ) ) . '</td></tr>';
echo '<tr><th class="user_details_user_login">User Login</th><td>' . esc_html( $user->user_login ) . '</td></tr>';
echo '<tr><th class="user_details_user_email">User Email</th><td>' . esc_html( $user->user_email ) . '</td></tr>';
echo '<tr><th class="user_details_display_name">Display Name</th><td>' . esc_html( $user->display_name ) . '</td></tr>';
echo '<tr><th class="user_details_user_registered">Registered</th><td>' . esc_html( 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>' . esc_html( $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>' . esc_html( $s_detail['ua'] ) . '</td></tr>';
echo '<tr><th class="wpmr_user_details_session_login">Session Start</th><td>' . esc_html( 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>' . esc_html( gmdate( 'Y-m-d\TH:i:s\Z', $s_detail['expiration'] ) ) . '</td></tr>';
echo '</table>';
}
echo '</td></tr>';
echo '</table>';
}
?>
</td></tr>
<?php
}
function debug() {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
}
}
}