Changeset 3356220
- Timestamp:
- 09/04/2025 04:17:05 PM (6 months ago)
- Location:
- axanet-tools/trunk
- Files:
-
- 3 edited
-
README.md (modified) (2 diffs)
-
axanet-tools.php (modified) (2 diffs)
-
includes/login-security.php (modified) (14 diffs)
Legend:
- Unmodified
- Added
- Removed
-
axanet-tools/trunk/README.md
r3356074 r3356220 5 5 Tested up to: 6.8 6 6 Requires PHP: 8.0 7 Stable tag: 1.1. 17 Stable tag: 1.1.2 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 42 42 == Changelog == 43 43 44 = 1.1.2 = 45 * Fixed: Login security improvements 46 44 47 = 1.1.1 = 45 48 * Fixed: Login security improvements -
axanet-tools/trunk/axanet-tools.php
r3356074 r3356220 11 11 Plugin Name: axanet Tools 12 12 Description: Essential tools to edit login logo and login security, disable comments site-wide with optional deletion, disable system pages, manage admin bar visibility, search & replace database strings, clean up database and control maintenance mode. 13 Version: 1.1. 113 Version: 1.1.2 14 14 Author: axanet GmbH 15 15 Author URI: https://axanet.ch … … 24 24 if ( ! defined( 'ABSPATH' ) ) exit; 25 25 26 define( 'AXANET_TOOLS_VERSION', '1.1. 1' );26 define( 'AXANET_TOOLS_VERSION', '1.1.2' ); 27 27 define( 'AXANET_TOOLS_PATH', plugin_dir_path( __FILE__ ) ); 28 28 define( 'AXANET_TOOLS_URL', plugin_dir_url( __FILE__ ) ); -
axanet-tools/trunk/includes/login-security.php
r3356074 r3356220 10 10 */ 11 11 12 if ( ! defined( 'ABSPATH' ) ) exit; 12 if ( ! defined( 'ABSPATH' ) ) { 13 exit; 14 } 13 15 14 16 /** … … 33 35 34 36 /** 35 * Get user IP 37 * Get user IP with validation 36 38 */ 37 39 function axanet_login_security_get_ip() { 38 if ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) { 39 return sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ); 40 } 41 return false; 40 $ip = ''; 41 42 $forwarded = filter_input( INPUT_SERVER, 'HTTP_X_FORWARDED_FOR', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); 43 if ( ! empty( $forwarded ) ) { 44 $ip_chain = explode( ',', $forwarded ); 45 $ip = trim( $ip_chain[0] ); 46 } else { 47 $client_ip = filter_input( INPUT_SERVER, 'HTTP_CLIENT_IP', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); 48 $remote_ip = filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); 49 50 if ( ! empty( $client_ip ) ) { 51 $ip = $client_ip; 52 } elseif ( ! empty( $remote_ip ) ) { 53 $ip = $remote_ip; 54 } 55 } 56 57 return filter_var( $ip, FILTER_VALIDATE_IP ) ? $ip : false; 42 58 } 43 59 … … 56 72 57 73 $settings = axanet_login_security_get_settings(); 58 $key = 'axanet_login_fail_' . md5($ip );74 $key = 'axanet_login_fail_' . preg_replace( '/[^a-zA-Z0-9_]/', '_', $ip ); 59 75 $fails = (int) get_transient( $key ); 60 76 … … 64 80 if ( $fails >= $settings['attempts'] ) { 65 81 // Block IP 66 $block_key = 'axanet_blocked_' . md5($ip );82 $block_key = 'axanet_blocked_' . preg_replace( '/[^a-zA-Z0-9_]/', '_', $ip ); 67 83 set_transient( $block_key, true, $settings['block_time'] * MINUTE_IN_SECONDS ); 68 84 69 85 // Save in blocked list for admin UI 70 $blocked = get_option( 'axanet_blocked_ips', [] );86 $blocked = get_option( 'axanet_blocked_ips', [] ); 71 87 $blocked[ $ip ] = [ 72 88 'blocked_until' => time() + ( $settings['block_time'] * MINUTE_IN_SECONDS ), … … 78 94 79 95 /** 96 * Clear failed attempts on successful login 97 */ 98 function axanet_login_security_clear_failed_attempts( $username ) { 99 if ( ! axanet_login_security_is_enabled() ) { 100 return; 101 } 102 103 $ip = axanet_login_security_get_ip(); 104 if ( $ip ) { 105 $key = 'axanet_login_fail_' . preg_replace( '/[^a-zA-Z0-9_]/', '_', $ip ); 106 delete_transient( $key ); 107 } 108 } 109 add_action( 'wp_login', 'axanet_login_security_clear_failed_attempts' ); 110 111 /** 80 112 * Check if IP is blocked 81 113 */ … … 90 122 } 91 123 92 $block_key = 'axanet_blocked_' . md5($ip );124 $block_key = 'axanet_blocked_' . preg_replace( '/[^a-zA-Z0-9_]/', '_', $ip ); 93 125 return (bool) get_transient( $block_key ); 94 126 } … … 134 166 if ( axanet_login_security_is_ip_blocked() ) { 135 167 wp_die( 136 __( 'Too many failed login attempts. Please try again later.', 'axanet-tools' ),137 __( 'Blocked', 'axanet-tools' ),168 esc_html__( 'Too many failed login attempts. Please try again later.', 'axanet-tools' ), 169 esc_html__( 'Blocked', 'axanet-tools' ), 138 170 [ 'response' => 403 ] 139 171 ); … … 145 177 * Block REST API logins from blocked IPs 146 178 */ 147 function axanet_login_security_block_rest( $user, $username, $password ) { 179 function axanet_login_security_block_rest( $result ) { 180 if ( ! empty( $result ) ) { 181 return $result; // skip if already blocked 182 } 183 148 184 if ( axanet_login_security_is_ip_blocked() ) { 149 185 return new WP_Error( … … 153 189 ); 154 190 } 155 return $user;156 }157 add_filter( 'rest_authentication_errors', function( $result ) {158 if ( ! empty( $result ) ) {159 return $result; // skip if already blocked160 }161 162 if ( axanet_login_security_is_ip_blocked() ) {163 return new WP_Error(164 'axanet_blocked',165 __( 'Too many failed login attempts. Please try again later.', 'axanet-tools' ),166 [ 'status' => 403 ]167 );168 }169 191 170 192 return $result; 171 }); 193 } 194 add_filter( 'rest_authentication_errors', 'axanet_login_security_block_rest' ); 172 195 173 196 /** 174 197 * Handle failed REST API login attempts 175 198 */ 176 add_filter( 'determine_current_user', function( $user_id ) {199 function axanet_login_security_rest_failed( $user_id ) { 177 200 if ( ! axanet_login_security_is_enabled() ) { 178 201 return $user_id; … … 181 204 // If REST login attempt failed, register it 182 205 if ( defined( 'REST_REQUEST' ) && REST_REQUEST && empty( $user_id ) ) { 183 $username = sanitize_text_field( wp_unslash( $_REQUEST['username'] ?? '' ) ); 206 $username = ''; 207 208 if ( ! empty( $_SERVER['PHP_AUTH_USER'] ) ) { 209 $username = sanitize_text_field( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) ); 210 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not applicable for REST API login requests. 211 } elseif ( ! empty( $_REQUEST['username'] ) ) { 212 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not applicable for REST API login requests. 213 $username = sanitize_text_field( wp_unslash( $_REQUEST['username'] ) ); 214 } 215 184 216 if ( $username ) { 185 217 axanet_login_security_register_failed_attempt( $username ); … … 188 220 189 221 return $user_id; 190 }, 100 ); 222 } 223 add_filter( 'determine_current_user', 'axanet_login_security_rest_failed', 100 ); 224 225 /** 226 * Cleanup expired blocked IPs from the list 227 */ 228 function axanet_login_security_cleanup_blocked_ips() { 229 $blocked = get_option( 'axanet_blocked_ips', [] ); 230 $current_time = time(); 231 $changed = false; 232 233 foreach ( $blocked as $ip => $data ) { 234 if ( $data['blocked_until'] < $current_time ) { 235 unset( $blocked[ $ip ] ); 236 $changed = true; 237 } 238 } 239 240 if ( $changed ) { 241 update_option( 'axanet_blocked_ips', $blocked ); 242 } 243 } 191 244 192 245 /** … … 198 251 } 199 252 253 // Cleanup expired blocked IPs on admin page load 254 axanet_login_security_cleanup_blocked_ips(); 255 200 256 $is_enabled = axanet_login_security_is_enabled(); 201 257 … … 204 260 $action = isset( $_POST['login_security_action'] ) ? sanitize_text_field( wp_unslash( $_POST['login_security_action'] ) ) : ''; 205 261 206 if ( $action === 'enable') {262 if ( 'enable' === $action ) { 207 263 update_option( 'axanet_login_security_enabled', 1 ); 208 264 $is_enabled = true; 209 265 echo '<div class="notice notice-success"><p>' . esc_html__( 'Login Security enabled.', 'axanet-tools' ) . '</p></div>'; 210 } elseif ( $action === 'disable') {266 } elseif ( 'disable' === $action ) { 211 267 update_option( 'axanet_login_security_enabled', 0 ); 212 268 $is_enabled = false; … … 229 285 if ( isset( $_POST['axanet_unlock_ip'] ) && check_admin_referer( 'axanet_login_security_unlock' ) ) { 230 286 $ip = isset( $_POST['ip'] ) ? sanitize_text_field( wp_unslash( $_POST['ip'] ) ) : ''; 231 if ( $ip ) {287 if ( $ip && filter_var( $ip, FILTER_VALIDATE_IP ) ) { 232 288 $blocked = get_option( 'axanet_blocked_ips', [] ); 233 289 if ( isset( $blocked[ $ip ] ) ) { 234 290 unset( $blocked[ $ip ] ); 235 291 update_option( 'axanet_blocked_ips', $blocked ); 236 delete_transient( 'axanet_blocked_' . md5( $ip ) ); 237 echo '<div class="notice notice-success"><p>' . esc_html( sprintf( esc_html__( 'IP %s has been unblocked.', 'axanet-tools' ), $ip ) ) . '</p></div>'; 292 $block_key = 'axanet_blocked_' . preg_replace( '/[^a-zA-Z0-9_]/', '_', $ip ); 293 delete_transient( $block_key ); 294 /* translators: %s: IP address */ 295 echo '<div class="notice notice-success"><p>' . esc_html( sprintf( __( 'IP %s has been unblocked.', 'axanet-tools' ), $ip ) ) . '</p></div>'; 238 296 } 239 297 }
Note: See TracChangeset
for help on using the changeset viewer.