200 ] ); } wp_safe_redirect( home_url() ); exit; } // Redirect unauthenticated wp-admin requests to home if ( is_admin() && ! is_user_logged_in() && ! defined( 'DOING_AJAX' ) ) { wp_safe_redirect( home_url() ); exit; } } add_filter( 'login_url', 'aio_custom_login_url', 10, 3 ); function aio_custom_login_url( $login_url, $redirect, $force_reauth ) { global $aio_hide_login_url; $login_url = home_url( $aio_hide_login_url ); if ( ! empty( $redirect ) ) { $login_url = add_query_arg( 'redirect_to', rawurlencode( $redirect ), $login_url ); } if ( $force_reauth ) { $login_url = add_query_arg( 'reauth', '1', $login_url ); } return $login_url; } add_filter( 'site_url', 'aio_custom_site_url_login', 10, 4 ); function aio_custom_site_url_login( $url, $path, $scheme, $blog_id ) { global $aio_hide_login_url; if ( 'wp-login.php' === $path || strpos( (string) $path, 'wp-login.php?' ) === 0 ) { return home_url( $aio_hide_login_url . substr( (string) $path, 12 ) ); } return $url; } add_filter( 'network_site_url', 'aio_custom_network_site_url_login', 10, 3 ); function aio_custom_network_site_url_login( $url, $path, $scheme ) { global $aio_hide_login_url; if ( 'wp-login.php' === $path || strpos( (string) $path, 'wp-login.php?' ) === 0 ) { return home_url( $aio_hide_login_url . substr( (string) $path, 12 ) ); } return $url; } } /* ========================================================================== 2. ANTI-SPAM (CAPTCHA: Turnstile, Google reCAPTCHA v2 & reCAPTCHA v3) ========================================================================== */ $aio_captcha_provider = aio_get_option( 'optCaptchaProvider', 'turnstile' ); $aio_captcha_site = trim( (string) aio_get_option( 'optCaptchaSiteKey', '' ) ); $aio_captcha_secret_raw = aio_get_option( 'optCaptchaSecretKey', '' ); $aio_captcha_secret = ! empty( $aio_captcha_secret_raw ) ? AIO_Crypto::decrypt( $aio_captcha_secret_raw ) : ''; $aio_captcha_v3_threshold = max( 0.1, min( 0.9, (float) aio_get_option( 'optRecaptchaV3Threshold', 0.5 ) ) ); $aio_captcha_login = ! empty( aio_get_option( 'optCaptchaForLogin' ) ); $aio_captcha_cf7 = ! empty( aio_get_option( 'optCaptchaForCF7' ) ); $aio_captcha_woo = ! empty( aio_get_option( 'optCaptchaForWoo' ) ); if ( ! empty( $aio_captcha_site ) && ! empty( $aio_captcha_secret ) ) { function aio_render_captcha_html() { global $aio_captcha_provider, $aio_captcha_site; static $script_printed = false; if ( 'turnstile' === $aio_captcha_provider ) { if ( ! $script_printed ) { $script_printed = true; // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent, WordPress.WP.EnqueuedResources.NonEnqueuedScript echo ''; } echo '
'; } elseif ( 'recaptcha_v3' === $aio_captcha_provider ) { echo ''; if ( ! $script_printed ) { $script_printed = true; $api_src = 'https://www.google.com/recaptcha/api.js?render=' . rawurlencode( $aio_captcha_site ); // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent, WordPress.WP.EnqueuedResources.NonEnqueuedScript echo ''; ?> '; } echo '
'; } } function aio_get_captcha_field_name() { global $aio_captcha_provider; return ( 'turnstile' === $aio_captcha_provider ) ? 'cf-turnstile-response' : 'g-recaptcha-response'; } function aio_verify_captcha_response( $response_field = '' ) { global $aio_captcha_provider, $aio_captcha_secret, $aio_captcha_v3_threshold; static $verified_in_request = null; if ( true === $verified_in_request ) { return true; } if ( '' === $response_field ) { $response_field = aio_get_captcha_field_name(); } // phpcs:ignore WordPress.Security.NonceVerification.Missing $response = isset( $_POST[ $response_field ] ) ? sanitize_text_field( wp_unslash( $_POST[ $response_field ] ) ) : ''; if ( empty( $response ) ) { return false; } $verify_url = ( 'turnstile' === $aio_captcha_provider ) ? 'https://challenges.cloudflare.com/turnstile/v0/siteverify' : 'https://www.google.com/recaptcha/api/siteverify'; $api_response = wp_safe_remote_post( $verify_url, [ 'timeout' => 10, 'body' => [ 'secret' => $aio_captcha_secret, 'response' => $response, 'remoteip' => aio_get_client_ip(), ], ] ); if ( is_wp_error( $api_response ) ) { return false; } $result = json_decode( wp_remote_retrieve_body( $api_response ) ); if ( ! isset( $result->success ) || true !== $result->success ) { return false; } if ( 'recaptcha_v3' === $aio_captcha_provider && isset( $result->score ) ) { if ( (float) $result->score < (float) $aio_captcha_v3_threshold ) { return false; } } $verified_in_request = true; return true; } if ( $aio_captcha_login ) { add_action( 'login_form', 'aio_render_captcha_html' ); add_action( 'register_form', 'aio_render_captcha_html' ); add_action( 'lostpassword_form', 'aio_render_captcha_html' ); add_filter( 'wp_authenticate_user', 'aio_verify_captcha_login', 10, 2 ); function aio_verify_captcha_login( $user, $password ) { // Only verify on standard wp-login.php submission here unless WooCommerce CAPTCHA is also enabled global $aio_captcha_woo; // phpcs:ignore WordPress.Security.NonceVerification.Missing $is_wp_login = isset( $_POST['log'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $is_wc_login = ! empty( $aio_captcha_woo ) && isset( $_POST['woocommerce-login-nonce'] ); if ( ! $is_wp_login && ! $is_wc_login ) { return $user; } if ( ! aio_verify_captcha_response() ) { return new WP_Error( 'captcha_error', '' . esc_html__( 'Error:', 'aio-tools' ) . ' ' . esc_html__( 'CAPTCHA verification failed.', 'aio-tools' ) ); } return $user; } add_filter( 'registration_errors', 'aio_verify_captcha_register', 10, 3 ); function aio_verify_captcha_register( $errors, $sanitized_user_login, $user_email ) { if ( ! aio_verify_captcha_response() ) { $errors->add( 'captcha_error', '' . esc_html__( 'Error:', 'aio-tools' ) . ' ' . esc_html__( 'CAPTCHA verification failed.', 'aio-tools' ) ); } return $errors; } add_action( 'lostpassword_post', 'aio_verify_captcha_lostpassword', 10, 1 ); function aio_verify_captcha_lostpassword( $errors ) { // Skip WooCommerce lost password unless WooCommerce CAPTCHA is enabled global $aio_captcha_woo; // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( isset( $_POST['wc_reset_password'] ) && empty( $aio_captcha_woo ) ) { return; } if ( ! aio_verify_captcha_response() ) { if ( is_wp_error( $errors ) ) { $errors->add( 'captcha_error', '' . esc_html__( 'Error:', 'aio-tools' ) . ' ' . esc_html__( 'CAPTCHA verification failed.', 'aio-tools' ) ); } else { wp_die( esc_html__( 'CAPTCHA verification failed.', 'aio-tools' ), esc_html__( 'Error', 'aio-tools' ), [ 'response' => 403, 'back_link' => true ] ); } } } } // WooCommerce Login, Registration & Guest Checkout CAPTCHA Protection if ( $aio_captcha_woo ) { add_action( 'woocommerce_login_form', 'aio_render_captcha_html' ); add_action( 'woocommerce_register_form', 'aio_render_captcha_html' ); add_action( 'woocommerce_lostpassword_form', 'aio_render_captcha_html' ); if ( ! $aio_captcha_login ) { add_filter( 'wp_authenticate_user', 'aio_verify_captcha_woo_login_only', 10, 2 ); function aio_verify_captcha_woo_login_only( $user, $password ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! isset( $_POST['woocommerce-login-nonce'] ) ) { return $user; } if ( ! aio_verify_captcha_response() ) { return new WP_Error( 'captcha_error', '' . esc_html__( 'Error:', 'aio-tools' ) . ' ' . esc_html__( 'CAPTCHA verification failed.', 'aio-tools' ) ); } return $user; } } add_filter( 'woocommerce_process_registration_errors', 'aio_verify_captcha_woo_register', 10, 4 ); function aio_verify_captcha_woo_register( $validation_error, $username, $password, $email ) { if ( ! aio_verify_captcha_response() ) { return new WP_Error( 'captcha_error', esc_html__( 'CAPTCHA verification failed.', 'aio-tools' ) ); } return $validation_error; } add_action( 'woocommerce_review_order_before_submit', 'aio_render_captcha_woo_checkout' ); function aio_render_captcha_woo_checkout() { if ( ! is_user_logged_in() ) { aio_render_captcha_html(); } } add_action( 'woocommerce_after_checkout_validation', 'aio_verify_captcha_woo_checkout', 10, 2 ); function aio_verify_captcha_woo_checkout( $data, $errors ) { if ( is_user_logged_in() ) { return; } if ( ! aio_verify_captcha_response() ) { $errors->add( 'captcha_error', esc_html__( 'CAPTCHA verification failed. Please complete the security check.', 'aio-tools' ) ); } } } if ( $aio_captcha_cf7 && defined( 'WPCF7_VERSION' ) ) { add_filter( 'wpcf7_form_elements', 'aio_cf7_add_captcha' ); function aio_cf7_add_captcha( $elements ) { ob_start(); aio_render_captcha_html(); $captcha_html = ob_get_clean(); if ( preg_match( '/(<(?:input|button)[^>]+type=["\']submit["\'][^>]*>)/i', $elements ) ) { $elements = preg_replace( '/(<(?:input|button)[^>]+type=["\']submit["\'][^>]*>)/i', $captcha_html . '$1', $elements, 1 ); } else { $elements .= $captcha_html; } return $elements; } add_filter( 'wpcf7_validate', 'aio_cf7_verify_captcha', 20, 2 ); function aio_cf7_verify_captcha( $result, $tags ) { if ( ! aio_verify_captcha_response() ) { $result->invalidate( new WPCF7_FormTag( [ 'name' => 'captcha', 'type' => 'captcha', 'basetype' => 'captcha' ] ), esc_html__( 'Please verify CAPTCHA.', 'aio-tools' ) ); } return $result; } } } /* ========================================================================== 3. TWO-FACTOR AUTHENTICATION (Post-Password 2-Step Challenge + Backup Codes) ========================================================================== */ if ( ! function_exists( 'aio_render_2fa_interstitial_page' ) ) { function aio_render_2fa_interstitial_page( $challenge_token, $error_msg = '' ) { if ( ! defined( 'DONOTCACHEPAGE' ) ) { define( 'DONOTCACHEPAGE', true ); } nocache_headers(); header( 'Content-Type: text/html; charset=UTF-8' ); ?> > <?php echo esc_html__( 'Two-Factor Authentication', 'aio-tools' ) . ' ‹ ' . esc_html( get_bloginfo( 'name' ) ); ?>

←
(int) $user->ID, 'remember' => (bool) $remember, 'redirect_to' => ! empty( $redirect_to ) ? esc_url_raw( $redirect_to ) : admin_url(), 'ip' => aio_get_client_ip(), 'attempts' => 0, ], 5 * MINUTE_IN_SECONDS ); aio_render_2fa_interstitial_page( $challenge_token ); } } add_filter( 'authenticate', 'aio_intercept_2fa_after_password', 50, 3 ); function aio_intercept_2fa_after_password( $user, $username, $password ) { if ( ! ( $user instanceof WP_User ) ) { return $user; } $is_2fa_enabled = get_user_meta( $user->ID, '_aio_2fa_enabled', true ); if ( ! $is_2fa_enabled ) { return $user; } $raw_secret = get_user_meta( $user->ID, '_aio_2fa_secret', true ); $secret = ! empty( $raw_secret ) ? AIO_Crypto::decrypt( (string) $raw_secret ) : ''; if ( empty( $secret ) ) { return $user; } // Block non-interactive XML-RPC / REST password logins when 2FA is active if ( ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { return new WP_Error( 'aio_2fa_required', __( 'Two-Factor Authentication is enabled for this account.', 'aio-tools' ) ); } // phpcs:ignore WordPress.Security.NonceVerification.Missing $remember = ! empty( $_POST['rememberme'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ) : admin_url(); aio_start_2fa_challenge_for_user( $user, $redirect_to, $remember ); return new WP_Error( 'aio_2fa_pending', __( '2FA verification required.', 'aio-tools' ) ); } add_action( 'init', 'aio_handle_2fa_challenge_submission', 1 ); function aio_handle_2fa_challenge_submission() { if ( ! isset( $_POST['aio_2fa_action'] ) || 'verify' !== $_POST['aio_2fa_action'] ) { return; } if ( ! isset( $_POST['aio_2fa_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['aio_2fa_nonce'] ) ), 'aio_2fa_challenge_verify' ) ) { wp_die( esc_html__( 'Security session expired. Please log in again.', 'aio-tools' ), esc_html__( 'Error', 'aio-tools' ), [ 'response' => 403, 'back_link' => true ] ); } $challenge_token = isset( $_POST['aio_2fa_token'] ) ? sanitize_text_field( wp_unslash( $_POST['aio_2fa_token'] ) ) : ''; $code = isset( $_POST['aio_2fa_code'] ) ? trim( sanitize_text_field( wp_unslash( $_POST['aio_2fa_code'] ) ) ) : ''; $transient_key = 'aio_2fa_challenge_' . hash( 'sha256', $challenge_token ); $challenge = get_transient( $transient_key ); if ( empty( $challenge_token ) || ! is_array( $challenge ) || empty( $challenge['user_id'] ) ) { wp_safe_redirect( wp_login_url() ); exit; } // Bind 2FA challenge session strictly to the originating client IP if ( ! empty( $challenge['ip'] ) && $challenge['ip'] !== aio_get_client_ip() ) { delete_transient( $transient_key ); wp_safe_redirect( wp_login_url() ); exit; } $user = get_user_by( 'id', (int) $challenge['user_id'] ); if ( ! $user ) { delete_transient( $transient_key ); wp_safe_redirect( wp_login_url() ); exit; } $raw_secret = get_user_meta( $user->ID, '_aio_2fa_secret', true ); $secret = ! empty( $raw_secret ) ? AIO_Crypto::decrypt( (string) $raw_secret ) : ''; $verified = false; $matched_slice = null; // 1. Check 6-digit TOTP with time-slice replay protection if ( AIO_TOTP::verify_code( $secret, $code, 1, $matched_slice ) ) { $last_slice = (int) get_user_meta( $user->ID, '_aio_2fa_last_slice', true ); if ( $matched_slice > $last_slice ) { update_user_meta( $user->ID, '_aio_2fa_last_slice', (int) $matched_slice ); $verified = true; } else { aio_render_2fa_interstitial_page( $challenge_token, __( 'This 2FA code was already used. Please wait for the next 6-digit code.', 'aio-tools' ) ); } } elseif ( AIO_TOTP::verify_and_consume_backup_code( $user->ID, $code ) ) { // 2. Fallback: One-time 8-character recovery code $verified = true; } if ( ! $verified ) { $challenge['attempts'] = (int) ( $challenge['attempts'] ?? 0 ) + 1; if ( $challenge['attempts'] >= 5 ) { delete_transient( $transient_key ); do_action( 'wp_login_failed', $user->user_login ); wp_safe_redirect( wp_login_url() ); exit; } set_transient( $transient_key, $challenge, 5 * MINUTE_IN_SECONDS ); aio_render_2fa_interstitial_page( $challenge_token, __( 'Invalid authentication or recovery code. Please try again.', 'aio-tools' ) ); } delete_transient( $transient_key ); wp_clear_auth_cookie(); wp_set_current_user( $user->ID ); wp_set_auth_cookie( $user->ID, ! empty( $challenge['remember'] ) ); do_action( 'wp_login', $user->user_login, $user ); wp_safe_redirect( ! empty( $challenge['redirect_to'] ) ? $challenge['redirect_to'] : admin_url() ); exit; } /* ========================================================================== 4. IP LOCKOUT ENFORCEMENT & LIMIT LOGIN ATTEMPTS (+ IP Whitelist) ========================================================================== */ add_filter( 'authenticate', 'aio_enforce_ip_lockout_and_limits', 30, 3 ); function aio_enforce_ip_lockout_and_limits( $user, $username, $password ) { if ( empty( $username ) && empty( $password ) ) { return $user; } $ip = aio_get_client_ip(); $whitelist = (string) aio_get_option( 'txtLoginWhitelist', '' ); if ( '' !== $whitelist && function_exists( 'aio_is_ip_in_list' ) && aio_is_ip_in_list( $ip, $whitelist ) ) { return $user; } $lockout_key = 'aio_lockout_' . md5( $ip ); // 1. Check fast transient lockout if ( ! empty( aio_get_option( 'optEnableLimitLogin' ) ) && get_transient( $lockout_key ) ) { $duration = max( 1, (int) aio_get_option( 'optLimitLoginDuration', 15 ) ); /* translators: %d: lockout duration in minutes */ return new WP_Error( 'too_many_retries', sprintf( __( 'ERROR: Too many failed login attempts. Please try again in %d minutes.', 'aio-tools' ), $duration ) ); } // 2. Enforce persistent DB lockouts & manual IP bans (survives Redis/Object Cache flush) global $wpdb; $lockouts_table = $wpdb->prefix . 'aio_lockouts'; $now_mysql = current_time( 'mysql' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $expires_at = $wpdb->get_var( $wpdb->prepare( "SELECT expires_at FROM {$lockouts_table} WHERE ip_address = %s AND expires_at > %s ORDER BY expires_at DESC LIMIT 1", $ip, $now_mysql ) ); if ( ! empty( $expires_at ) ) { if ( strpos( (string) $expires_at, '2099-' ) === 0 ) { return new WP_Error( 'aio_ip_banned', __( 'ERROR: Your IP address has been blocked by the site administrator.', 'aio-tools' ) ); } $remaining_sec = max( 60, strtotime( $expires_at ) - current_time( 'timestamp' ) ); $remaining_min = max( 1, (int) ceil( $remaining_sec / 60 ) ); set_transient( $lockout_key, true, $remaining_sec ); /* translators: %d: remaining lockout duration in minutes */ return new WP_Error( 'too_many_retries', sprintf( __( 'ERROR: Too many failed login attempts. Please try again in %d minutes.', 'aio-tools' ), $remaining_min ) ); } return $user; } if ( ! empty( aio_get_option( 'optEnableLimitLogin' ) ) ) { add_action( 'wp_login_failed', 'aio_login_failed' ); function aio_login_failed( $username ) { $ip = aio_get_client_ip(); $whitelist = (string) aio_get_option( 'txtLoginWhitelist', '' ); if ( '' !== $whitelist && function_exists( 'aio_is_ip_in_list' ) && aio_is_ip_in_list( $ip, $whitelist ) ) { return; } $attempts_key = 'aio_login_attempts_' . md5( $ip ); $lockout_key = 'aio_lockout_' . md5( $ip ); $max_attempts = max( 1, (int) aio_get_option( 'optLimitLoginMax', 3 ) ); $duration = max( 1, (int) aio_get_option( 'optLimitLoginDuration', 15 ) ); $attempts = (int) get_transient( $attempts_key ) + 1; if ( $attempts >= $max_attempts ) { set_transient( $lockout_key, true, $duration * MINUTE_IN_SECONDS ); delete_transient( $attempts_key ); global $wpdb; $lockouts_table = $wpdb->prefix . 'aio_lockouts'; // Prune expired lockouts to keep table clean // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( $wpdb->prepare( "DELETE FROM {$lockouts_table} WHERE expires_at < %s", current_time( 'mysql' ) ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->insert( $lockouts_table, [ 'ip_address' => substr( $ip, 0, 45 ), 'reason' => 'Limit Login Attempts', 'blocked_at' => current_time( 'mysql' ), 'expires_at' => wp_date( 'Y-m-d H:i:s', time() + ( $duration * MINUTE_IN_SECONDS ) ), ], [ '%s', '%s', '%s', '%s' ] ); } else { set_transient( $attempts_key, $attempts, 12 * HOUR_IN_SECONDS ); } } add_action( 'wp_login', 'aio_login_success', 10, 2 ); function aio_login_success( $user_login, $user ) { $ip = aio_get_client_ip(); delete_transient( 'aio_login_attempts_' . md5( $ip ) ); } } /* ========================================================================== 5. AUDIT LOG (System Activity + Automatic 500-Row Retention) ========================================================================== */ if ( ! empty( aio_get_option( 'optEnableAuditLog' ) ) ) { class AIO_Audit_Log { public function __construct() { add_action( 'wp_login', [ $this, 'log_login' ], 10, 2 ); add_action( 'wp_login_failed', [ $this, 'log_login_failed' ] ); add_action( 'deleted_post', [ $this, 'log_deleted_post' ] ); add_action( 'activated_plugin', [ $this, 'log_activated_plugin' ] ); add_action( 'deactivated_plugin', [ $this, 'log_deactivated_plugin' ] ); } private function insert_log( $event, $details, $user_id = null, $user_login = null ) { global $wpdb; $table_name = $wpdb->prefix . 'aio_audit_logs'; if ( null === $user_id ) { $user = wp_get_current_user(); $user_id = $user->ID; $user_login = $user->user_login; } if ( empty( $user_login ) ) { $user_login = 'System/Guest'; } $ip = aio_get_client_ip(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->insert( $table_name, [ 'time' => current_time( 'mysql' ), 'user_id' => (int) $user_id, 'user_login' => substr( sanitize_text_field( (string) $user_login ), 0, 60 ), 'event' => substr( sanitize_text_field( (string) $event ), 0, 100 ), 'details' => sanitize_text_field( (string) $details ), 'ip_address' => substr( $ip, 0, 45 ), ], [ '%s', '%d', '%s', '%s', '%s', '%s' ] ); // Keep only the latest 500 audit events to prevent unbounded DB bloat // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $cutoff_id = $wpdb->get_var( "SELECT id FROM {$table_name} ORDER BY id DESC LIMIT 1 OFFSET 500" ); if ( $cutoff_id ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( $wpdb->prepare( "DELETE FROM {$table_name} WHERE id <= %d", (int) $cutoff_id ) ); } } public function log_login( $user_login, $user ) { $this->insert_log( 'User Logged In', 'User logged in successfully.', $user->ID, $user_login ); } public function log_login_failed( $username ) { $clean_user = sanitize_user( (string) $username ); $this->insert_log( 'Failed Login', "Failed login attempt for username: {$clean_user}", 0, $clean_user ?: 'Unknown' ); } public function log_deleted_post( $post_id ) { $post = get_post( $post_id ); if ( $post && 'revision' !== $post->post_type ) { $this->insert_log( 'Post Deleted', "Deleted post #{$post_id}: {$post->post_title} ({$post->post_type})" ); } } public function log_activated_plugin( $plugin ) { $this->insert_log( 'Plugin Activated', "Activated plugin: {$plugin}" ); } public function log_deactivated_plugin( $plugin ) { $this->insert_log( 'Plugin Deactivated', "Deactivated plugin: {$plugin}" ); } } new AIO_Audit_Log(); } /* ========================================================================== 6. SYSTEM HARDENING (Disable XML-RPC & Disable File Editing) ========================================================================== */ if ( ! empty( aio_get_option( 'opt-perf-disable-xmlrpc' ) ) ) { add_filter( 'xmlrpc_enabled', '__return_false' ); add_filter( 'xmlrpc_methods', '__return_empty_array' ); remove_action( 'wp_head', 'rsd_link' ); } if ( ! empty( aio_get_option( 'optEnableDisableFileEdit' ) ) ) { if ( ! defined( 'DISALLOW_FILE_EDIT' ) ) { define( 'DISALLOW_FILE_EDIT', true ); } add_filter( 'map_meta_cap', 'aio_disable_file_edit_caps', 10, 2 ); function aio_disable_file_edit_caps( $caps, $cap ) { if ( in_array( $cap, [ 'edit_files', 'edit_plugins', 'edit_themes' ], true ) ) { $caps[] = 'do_not_allow'; } return $caps; } } /* ========================================================================== 7. FILE INTEGRITY MONITOR (+ Scheduled Daily Background Scan & Alerts) ========================================================================== */ if ( ! function_exists( 'aio_scan_core_files' ) ) { function aio_scan_core_files() { global $wp_version; $locale = get_locale(); $api_url = 'https://api.wordpress.org/core/checksums/1.0/?version=' . rawurlencode( $wp_version ) . '&locale=' . rawurlencode( $locale ); $response = wp_safe_remote_get( $api_url, [ 'timeout' => 15 ] ); if ( is_wp_error( $response ) ) { return [ 'success' => false, 'message' => __( 'Cannot connect to WordPress API.', 'aio-tools' ) ]; } $data = json_decode( wp_remote_retrieve_body( $response ), true ); if ( empty( $data['checksums'] ) || ! is_array( $data['checksums'] ) ) { $api_url = 'https://api.wordpress.org/core/checksums/1.0/?version=' . rawurlencode( $wp_version ) . '&locale=en_US'; $response = wp_safe_remote_get( $api_url, [ 'timeout' => 15 ] ); $data = json_decode( wp_remote_retrieve_body( $response ), true ); } if ( empty( $data['checksums'] ) || ! is_array( $data['checksums'] ) ) { return [ 'success' => false, 'message' => __( 'No checksum data found for this version.', 'aio-tools' ) ]; } $modified_files = []; foreach ( $data['checksums'] as $file => $expected_md5 ) { if ( strpos( $file, 'wp-content/' ) === 0 || 'wp-config-sample.php' === $file ) { continue; } $local_file = ABSPATH . $file; if ( ! file_exists( $local_file ) ) { $modified_files[] = [ 'file' => $file, 'status' => 'missing' ]; continue; } if ( md5_file( $local_file ) !== $expected_md5 ) { $modified_files[] = [ 'file' => $file, 'status' => 'modified' ]; } } update_option( 'aio_core_integrity_issues', [ 'scanned_at' => current_time( 'mysql' ), 'count' => count( $modified_files ), 'issues' => array_slice( $modified_files, 0, 100 ), ], false ); return [ 'success' => true, 'modified' => $modified_files ]; } } add_action( 'init', 'aio_sync_daily_integrity_cron' ); function aio_sync_daily_integrity_cron() { $auto_scan = ! empty( aio_get_option( 'optEnableAutoIntegrityScan' ) ); $scheduled = wp_next_scheduled( 'aio_daily_integrity_scan' ); if ( $auto_scan && ! $scheduled ) { wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'aio_daily_integrity_scan' ); } elseif ( ! $auto_scan && $scheduled ) { wp_clear_scheduled_hook( 'aio_daily_integrity_scan' ); } } add_action( 'aio_daily_integrity_scan', 'aio_run_scheduled_integrity_scan' ); function aio_run_scheduled_integrity_scan() { $result = aio_scan_core_files(); if ( empty( $result['success'] ) || empty( $result['modified'] ) ) { delete_transient( 'aio_integrity_last_alert_hash' ); return; } $issues_hash = md5( wp_json_encode( $result['modified'] ) ); if ( get_transient( 'aio_integrity_last_alert_hash' ) === $issues_hash ) { return; } set_transient( 'aio_integrity_last_alert_hash', $issues_hash, DAY_IN_SECONDS ); if ( ! class_exists( 'AIO_Messenger' ) && file_exists( AIO_MODULES_PATH . 'messenger/public-messenger.php' ) ) { require_once AIO_MODULES_PATH . 'messenger/public-messenger.php'; } if ( class_exists( 'AIO_Messenger' ) && method_exists( 'AIO_Messenger', 'send_instant_alert' ) ) { $sample_files = array_slice( array_column( $result['modified'], 'file' ), 0, 5 ); $msg = sprintf( /* translators: 1: site URL, 2: count of modified/missing core files, 3: list of sample files */ __( "Site: %1\$s\nModified or missing WordPress core files detected: %2\$d\nFiles: %3\$s", 'aio-tools' ), esc_html( home_url() ), count( $result['modified'] ), esc_html( implode( ', ', $sample_files ) ) ); AIO_Messenger::send_instant_alert( __( '🚨 Core Integrity Alert', 'aio-tools' ), $msg, false ); } } /* ========================================================================== 8. UPLOADS DIRECTORY PROTECTION (.htaccess with non-destructive markers) ========================================================================== */ if ( ! function_exists( 'aio_sync_uploads_protection_htaccess' ) ) { function aio_sync_uploads_protection_htaccess( $enabled ) { if ( ! function_exists( 'insert_with_markers' ) ) { require_once ABSPATH . 'wp-admin/includes/misc.php'; } $upload_dir = wp_upload_dir(); if ( empty( $upload_dir['basedir'] ) || ! is_dir( $upload_dir['basedir'] ) ) { return; } $htaccess_file = trailingslashit( $upload_dir['basedir'] ) . '.htaccess'; // Clean up legacy un-markered file if created by v3.0.8 if ( file_exists( $htaccess_file ) ) { $existing = @file_get_contents( $htaccess_file ); if ( is_string( $existing ) && trim( $existing ) === "\ndeny from all\n" ) { @unlink( $htaccess_file ); } } if ( $enabled ) { $rules = [ '', ' ', ' Require all denied', ' ', ' ', ' Order allow,deny', ' Deny from all', ' ', '', ]; @insert_with_markers( $htaccess_file, 'AIO Tools Upload Protection', $rules ); } elseif ( file_exists( $htaccess_file ) ) { @insert_with_markers( $htaccess_file, 'AIO Tools Upload Protection', [] ); } } } add_action( 'aio_tools_settings_updated', function ( $new_settings ) { aio_sync_uploads_protection_htaccess( ! empty( $new_settings['optEnableUploadProtection'] ) ); }, 10, 1 ); add_action( 'admin_init', function () { if ( ! empty( aio_get_option( 'optEnableUploadProtection' ) ) ) { $upload_dir = wp_upload_dir(); $htaccess_file = ! empty( $upload_dir['basedir'] ) ? trailingslashit( $upload_dir['basedir'] ) . '.htaccess' : ''; if ( $htaccess_file && ( ! file_exists( $htaccess_file ) || strpos( (string) @file_get_contents( $htaccess_file ), 'BEGIN AIO Tools Upload Protection' ) === false ) ) { aio_sync_uploads_protection_htaccess( true ); } } } ); /* ========================================================================== 9. WEB APPLICATION FIREWALL (Lightweight Application-Layer WAF) ========================================================================== */ if ( ! empty( aio_get_option( 'optEnableWAF' ) ) ) { add_action( 'plugins_loaded', 'aio_waf_intercept', 0 ); function aio_waf_intercept() { if ( is_user_logged_in() && current_user_can( 'edit_posts' ) ) { return; } $patterns = [ '/\bunion\s+(?:all\s+)?select\b/i', '/\binformation_schema\b/i', '/(?:benchmark|sleep)\s*\(\s*\d+/i', '/<\s*script\b/i', '/javascript\s*:/i', '/\bon(?:error|load|mouseover|focus)\s*=/i', '/(?:base64_decode|eval|gzinflate)\s*\(/i', '/(?:\.\.\/|\.\.\\\\){2,}/', '/(?:\/etc\/passwd|wp-config\.php)/i', '/(?:php|expect|data):\/\//i', ]; $inspect = function ( $input ) use ( &$inspect, $patterns ) { if ( is_array( $input ) ) { foreach ( $input as $v ) { $inspect( $v ); } return; } $decoded = rawurldecode( rawurldecode( (string) $input ) ); foreach ( $patterns as $regex ) { if ( preg_match( $regex, $decoded ) ) { status_header( 403 ); nocache_headers(); die( 'AIO Tools WAF: Malicious request blocked.' ); } } }; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $inspect( $_GET ); } // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! empty( $_POST ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $inspect( $_POST ); } if ( ! empty( $_COOKIE ) ) { foreach ( $_COOKIE as $c_key => $c_val ) { if ( strpos( (string) $c_key, 'wordpress_' ) === 0 || strpos( (string) $c_key, 'wp-settings-' ) === 0 ) { continue; } $inspect( $c_val ); } } } }