This commit is contained in:
Yuri Karamian
2026-01-26 11:23:23 +01:00
parent 0fc65148a7
commit e5e50ecbb7
25 changed files with 74 additions and 14 deletions
@@ -31,6 +31,7 @@ class WPSP_Admin {
add_action( 'wp_ajax_wpsp_clear_logs', array( $this, 'ajax_clear_logs' ) );
add_action( 'wp_ajax_wpsp_export_logs', array( $this, 'ajax_export_logs' ) );
add_action( 'wp_ajax_wpsp_whitelist_ip', array( $this, 'ajax_whitelist_ip' ) );
add_action( 'wp_ajax_wpsp_unblock_ip', array( $this, 'ajax_unblock_ip' ) );
add_action( 'wp_ajax_wpsp_clear_lockouts', array( $this, 'ajax_clear_lockouts' ) );
add_action( 'wp_ajax_wpsp_run_file_scan', array( $this, 'ajax_run_file_scan' ) );
add_action( 'wp_ajax_wpsp_run_malware_scan', array( $this, 'ajax_run_malware_scan' ) );
@@ -3018,6 +3019,30 @@ class WPSP_Admin {
wp_send_json_success( array( 'csv' => implode( "\n", $csv_lines ) ) );
}
/**
* AJAX: Unblock a single IP.
*/
public function ajax_unblock_ip() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$ip = isset( $_POST['ip'] ) ? sanitize_text_field( wp_unslash( $_POST['ip'] ) ) : '';
if ( empty( $ip ) || ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid IP address.', 'wp-security-pack' ) ) );
}
$ip_control = wpsp()->get_component( 'ip_control' );
if ( $ip_control ) {
$ip_control->unblock_ip( $ip );
}
wp_send_json_success();
}
/**
* AJAX: Clear all lockouts.
*/
@@ -38,7 +38,6 @@ class WPSP_Login_Protection {
// Priority 10 runs BEFORE WordPress's authenticate (priority 20) to block locked-out IPs early.
add_filter( 'authenticate', array( $this, 'check_lockout' ), 10, 3 );
// Successful login tracking.
add_action( 'wp_login', array( $this, 'handle_successful_login' ), 10, 2 );
@@ -46,7 +45,29 @@ class WPSP_Login_Protection {
// This runs on login_init which fires before the login form is displayed.
add_action( 'login_init', array( $this, 'block_locked_out_on_login_page' ), 1 );
// Honeypot field.
// NOTE: Must be registered BEFORE handle_custom_login_slug_early() which
// may load wp-login.php and exit, preventing any later hook registrations.
if ( WP_Security_Pack::get_setting( 'honeypot_enabled', true ) ) {
add_action( 'login_form', array( $this, 'add_honeypot_field' ) );
add_action( 'register_form', array( $this, 'add_honeypot_field' ) );
add_filter( 'authenticate', array( $this, 'check_honeypot' ), 1, 3 );
add_filter( 'registration_errors', array( $this, 'check_honeypot_registration' ), 10, 3 );
}
// Hide login error messages (prevents username enumeration).
// NOTE: Must be registered BEFORE handle_custom_login_slug_early() which
// may load wp-login.php and exit, preventing any later hook registrations.
if ( WP_Security_Pack::get_setting( 'hide_login_errors', true ) ) {
// Replace specific auth errors at the source (runs after WP's authenticate at priority 20).
add_filter( 'authenticate', array( $this, 'genericize_auth_error' ), PHP_INT_MAX, 3 );
// Also filter the rendered error string as a safety net.
add_filter( 'login_errors', array( $this, 'hide_login_errors' ), PHP_INT_MAX );
}
// Custom login URL - must run very early before WordPress processes the request.
// WARNING: handle_custom_login_slug_early() may require wp-login.php and exit.
// All login-related hooks MUST be registered above this point.
if ( WP_Security_Pack::get_setting( 'login_rename_enabled', false ) ) {
$this->custom_login_slug = WP_Security_Pack::get_setting( 'login_custom_url', '' );
if ( ! empty( $this->custom_login_slug ) ) {
@@ -67,19 +88,6 @@ class WPSP_Login_Protection {
$this->block_wp_admin_early();
add_action( 'init', array( $this, 'hide_wp_admin' ), 1 );
}
// Honeypot field.
if ( WP_Security_Pack::get_setting( 'honeypot_enabled', true ) ) {
add_action( 'login_form', array( $this, 'add_honeypot_field' ) );
add_action( 'register_form', array( $this, 'add_honeypot_field' ) );
add_filter( 'authenticate', array( $this, 'check_honeypot' ), 1, 3 );
add_filter( 'registration_errors', array( $this, 'check_honeypot_registration' ), 10, 3 );
}
// Hide login error messages (prevents username enumeration).
if ( WP_Security_Pack::get_setting( 'hide_login_errors', true ) ) {
add_filter( 'login_errors', array( $this, 'hide_login_errors' ) );
}
}
/**
@@ -358,6 +366,33 @@ class WPSP_Login_Protection {
return __( 'Invalid username or password.', 'wp-security-pack' );
}
/**
* Replace specific authentication errors with a generic message.
*
* Runs at very late priority on the authenticate filter to catch WordPress's
* own specific error messages (invalid_username, incorrect_password, etc.)
* and replace them before they reach the login page rendering.
*
* @param WP_User|WP_Error|null $user User object or error.
* @param string $username Username.
* @param string $password Password.
* @return WP_User|WP_Error|null
*/
public function genericize_auth_error( $user, $username, $password ) {
if ( is_wp_error( $user ) ) {
$specific_codes = array( 'invalid_username', 'invalid_email', 'incorrect_password', 'invalidcombo' );
if ( in_array( $user->get_error_code(), $specific_codes, true ) ) {
return new WP_Error(
'authentication_failed',
__( 'Invalid username or password.', 'wp-security-pack' )
);
}
}
return $user;
}
/**
* Check if IP is locked out before authentication.
*

Before

Width:  |  Height:  |  Size: 667 KiB

After

Width:  |  Height:  |  Size: 667 KiB