PluginProbe ʕ •ᴥ•ʔ
Limit Login Attempts Security – Login Security, 2FA, Firewall, Brute Force Prevention / 3.2.4
Limit Login Attempts Security – Login Security, 2FA, Firewall, Brute Force Prevention v3.2.4
3.3.2 3.3.1 3.3.0 3.2.4 3.2.3 3.2.2 3.2.1 3.2.0 trunk 2.0.0 2.1.0 2.10.0 2.10.1 2.11.0 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.14.0 2.15.0 2.15.1 2.15.2 2.16.0 2.17.0 2.17.1 2.17.2 2.17.3 2.17.4 2.18.0 2.19.0 2.19.1 2.19.2 2.2.0 2.20.0 2.20.1 2.20.2 2.20.3 2.20.4 2.20.5 2.20.6 2.21.0 2.21.1 2.22.0 2.22.1 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.25.0 2.25.1 2.25.10 2.25.11 2.25.12 2.25.13 2.25.14 2.25.15 2.25.16 2.25.17 2.25.18 2.25.19 2.25.2 2.25.20 2.25.21 2.25.22 2.25.23 2.25.24 2.25.25 2.25.26 2.25.27 2.25.28 2.25.29 2.25.3 2.25.4 2.25.5 2.25.6 2.25.7 2.25.8 2.25.9 2.26.0 2.26.1 2.26.10 2.26.11 2.26.12 2.26.13 2.26.14 2.26.15 2.26.16 2.26.17 2.26.18 2.26.19 2.26.2 2.26.20 2.26.21 2.26.22 2.26.23 2.26.24 2.26.25 2.26.26 2.26.27 2.26.28 2.26.3 2.26.4 2.26.5 2.26.6 2.26.7 2.26.8 2.26.9 2.3.0 2.4.0 2.5.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.8.0 2.8.1 2.9.0 3.0.0 3.0.1 3.0.2 3.1.0
limit-login-attempts-reloaded / limit-login-attempts-reloaded.php
limit-login-attempts-reloaded Last commit date
assets 1 month ago core 1 month ago languages 1 month ago lib 1 month ago resources 1 month ago views 1 month ago autoload.php 1 month ago changelog.txt 1 month ago limit-login-attempts-reloaded.php 1 month ago readme.txt 1 month ago
limit-login-attempts-reloaded.php
318 lines
1 <?php
2 /*
3 Plugin Name: Limit Login Attempts Reloaded
4 Description: Block excessive login attempts and protect your site against brute force attacks. Simple, yet powerful tools to improve site performance.
5 Author: Limit Login Attempts Reloaded
6 Author URI: https://www.limitloginattempts.com/
7 Text Domain: limit-login-attempts-reloaded
8 Version: 3.2.4
9
10 Copyright 2008-2012 Johan Eenfeldt, 2016–present Limit Login Attempts Reloaded
11 */
12
13 if ( !defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /***************************************************************************************
18 * Constants
19 **************************************************************************************/
20 define( 'LLA_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
21 define( 'LLA_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
22 define( 'LLA_PLUGIN_FILE', __FILE__ );
23 define( 'LLA_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
24
25 /**
26 * Default risk widget config (bounds, colors, level rules).
27 *
28 * @return array
29 */
30 function llar_get_risk_config_defaults() {
31 return array(
32 'bounds' => array(
33 'low_upper' => 100,
34 'medium_upper' => 300,
35 ),
36 'colors' => array(
37 'green' => '#97F6C8',
38 'yellow' => '#FFE066',
39 'orange' => '#FFA34C',
40 'red' => '#FF6633',
41 ),
42 'levels' => array(
43 'local' => array(
44 array(
45 'exact' => 0,
46 'title' => 'zero_title',
47 'color' => 'green',
48 ),
49 array(
50 'max_exclusive' => 100,
51 'count_title' => true,
52 'desc' => 'desc_low',
53 'color' => 'yellow',
54 ),
55 array(
56 'max_exclusive' => 300,
57 'count_title' => true,
58 'desc' => 'desc_medium',
59 /* Same recommendation block as high (red); threshold moved to 300+. */
60 'recommendation' => true,
61 'color' => 'orange',
62 ),
63 array(
64 'min_inclusive' => 300,
65 'default' => true,
66 'warning_title' => true,
67 'recommendation' => true,
68 'color' => 'red',
69 ),
70 ),
71 ),
72 );
73 }
74
75 /**
76 * Merge filtered config with defaults so colors/levels/bounds always exist.
77 *
78 * @param array $defaults Default config.
79 * @param mixed $cfg Filtered value.
80 *
81 * @return array
82 */
83 function llar_normalize_risk_config( $defaults, $cfg ) {
84 if ( ! is_array( $cfg ) ) {
85 return $defaults;
86 }
87
88 $out = $cfg;
89 foreach ( array( 'bounds', 'colors', 'levels' ) as $key ) {
90 if ( ! isset( $out[ $key ] ) || ! is_array( $out[ $key ] ) ) {
91 $out[ $key ] = $defaults[ $key ];
92 }
93 }
94
95 if ( ! isset( $out['levels']['local'] ) || ! is_array( $out['levels']['local'] ) ) {
96 $out['levels']['local'] = $defaults['levels']['local'];
97 }
98
99 return $out;
100 }
101
102 /**
103 * Risk widget config (colors, level rules). Cached per request; overridable via llar_risk_config filter.
104 *
105 * @return array
106 */
107 function llar_get_risk_config() {
108 static $cached = null;
109
110 if ( null !== $cached ) {
111 return $cached;
112 }
113
114 $defaults = llar_get_risk_config_defaults();
115 $merged = apply_filters( 'llar_risk_config', $defaults );
116 $cached = llar_normalize_risk_config( $defaults, $merged );
117
118 return $cached;
119 }
120
121 /**
122 * Warm risk config on init (after translations load).
123 *
124 * @return void
125 */
126 function llar_define_risk_config() {
127 llar_get_risk_config();
128 }
129
130 add_action( 'init', 'llar_define_risk_config', 1 );
131
132 /***************************************************************************************
133 * Different ways to get remote address: direct & behind proxy
134 **************************************************************************************/
135 define( 'LLA_DIRECT_ADDR', 'REMOTE_ADDR' );
136 define( 'LLA_PROXY_ADDR', 'HTTP_X_FORWARDED_FOR' );
137
138 /* Notify value checked against these in limit_login_sanitize_variables() */
139 define( 'LLA_LOCKOUT_NOTIFY_ALLOWED', 'log,email' );
140
141 /** Regex: valid email for obfuscation (1=first, 2=middle, 3=last, 4=domain). */
142 define( 'LLA_EMAIL_OBFUSCATE_REGEX', '/^(.)([^@]*)(.?)@(.*)$/' );
143 /** Regex: one char in local part to mask (not first, not last). (?<=.) = at least one char before; [^@*] avoids re-matching asterisks. */
144 define( 'LLA_EMAIL_OBFUSCATE_LOCAL', '/(?<=.)[^@*](?=[^@]+@)/' );
145 /** Regex: one char in domain to mask (non-dot). */
146 define( 'LLA_EMAIL_OBFUSCATE_DOMAIN', '/(?<=^[^@]*@.*)[^.]/' );
147
148 /***************************************************************************************
149 * MFA constants (rescue codes, rate limiting, transients).
150 * Overridable: define in wp-config.php before plugin load to override defaults.
151 **************************************************************************************/
152 defined( 'LLA_MFA_CODE_LENGTH' ) || define( 'LLA_MFA_CODE_LENGTH', 64 );
153 defined( 'LLA_MFA_RESCUE_TOKEN_LENGTH' ) || define( 'LLA_MFA_RESCUE_TOKEN_LENGTH', 32 );
154 defined( 'LLA_MFA_CODE_COUNT' ) || define( 'LLA_MFA_CODE_COUNT', 10 );
155 /* Rescue link payload storage TTL (WordPress transients). Default 10 years; links are one-time (payload deleted on use). RESCUE_NOTICE_THRESHOLD is for admin warning; with a long TTL, "near expiry" is rare and missing/invalid payloads is the main trigger. */
156 defined( 'LLA_MFA_RESCUE_LINK_TTL' ) || define( 'LLA_MFA_RESCUE_LINK_TTL', 10 * YEAR_IN_SECONDS );
157 defined( 'LLA_MFA_RESCUE_NOTICE_THRESHOLD' ) || define( 'LLA_MFA_RESCUE_NOTICE_THRESHOLD', 5 * DAY_IN_SECONDS );
158 defined( 'LLA_MFA_DISABLE_DURATION' ) || define( 'LLA_MFA_DISABLE_DURATION', 3600 );
159 defined( 'LLA_MFA_RATE_LIMIT_PERIOD' ) || define( 'LLA_MFA_RATE_LIMIT_PERIOD', 3600 );
160 defined( 'LLA_MFA_RESCUE_USE_COOLDOWN' ) || define( 'LLA_MFA_RESCUE_USE_COOLDOWN', 60 );
161 defined( 'LLA_MFA_TRANSIENT_RESCUE_PREFIX' ) || define( 'LLA_MFA_TRANSIENT_RESCUE_PREFIX', 'llar_mfa_rescue_' );
162 defined( 'LLA_MFA_TRANSIENT_RESCUE_LAST_USE' ) || define( 'LLA_MFA_TRANSIENT_RESCUE_LAST_USE', 'llar_rescue_last_use' );
163 defined( 'LLA_MFA_TRANSIENT_MFA_DISABLED' ) || define( 'LLA_MFA_TRANSIENT_MFA_DISABLED', 'llar_mfa_temporarily_disabled' );
164 defined( 'LLA_MFA_TRANSIENT_CHECKBOX_STATE' ) || define( 'LLA_MFA_TRANSIENT_CHECKBOX_STATE', 'llar_mfa_checkbox_state' );
165 defined( 'LLA_MFA_CHECKBOX_STATE_TTL' ) || define( 'LLA_MFA_CHECKBOX_STATE_TTL', 300 );
166 defined( 'LLA_MFA_PDF_RATE_LIMIT_MAX' ) || define( 'LLA_MFA_PDF_RATE_LIMIT_MAX', 5 );
167 defined( 'LLA_MFA_PDF_RATE_LIMIT_PERIOD' ) || define( 'LLA_MFA_PDF_RATE_LIMIT_PERIOD', 60 );
168 defined( 'LLA_MFA_WP_SALT_SCHEME_FALLBACK' ) || define( 'LLA_MFA_WP_SALT_SCHEME_FALLBACK', 'auth' );
169 defined( 'LLA_MFA_BLOCK_REASON_SSL' ) || define( 'LLA_MFA_BLOCK_REASON_SSL', 'ssl' );
170 defined( 'LLA_MFA_BLOCK_REASON_SALT' ) || define( 'LLA_MFA_BLOCK_REASON_SALT', 'salt' );
171 defined( 'LLA_MFA_BLOCK_REASON_OPENSSL' ) || define( 'LLA_MFA_BLOCK_REASON_OPENSSL', 'openssl' );
172
173 /** MFA Flow: session and OTP transients (after failed login handshake). */
174 defined( 'LLA_MFA_FLOW_TRANSIENT_SESSION_PREFIX' ) || define( 'LLA_MFA_FLOW_TRANSIENT_SESSION_PREFIX', 'llar_mfa_session_' );
175 defined( 'LLA_MFA_FLOW_TRANSIENT_OTP_PREFIX' ) || define( 'LLA_MFA_FLOW_TRANSIENT_OTP_PREFIX', 'llar_mfa_otp_' );
176 defined( 'LLA_MFA_FLOW_TRANSIENT_SEND_SECRET_PREFIX' ) || define( 'LLA_MFA_FLOW_TRANSIENT_SEND_SECRET_PREFIX', 'llar_mfa_send_secret_' );
177 defined( 'LLA_MFA_FLOW_TRANSIENT_STATE_PREFIX' ) || define( 'LLA_MFA_FLOW_TRANSIENT_STATE_PREFIX', 'llar_mfa_state_' );
178 defined( 'LLA_MFA_FLOW_OTP_TTL' ) || define( 'LLA_MFA_FLOW_OTP_TTL', 180 );
179 defined( 'LLA_MFA_FLOW_HANDSHAKE_RATE_LIMIT_PERIOD' ) || define( 'LLA_MFA_FLOW_HANDSHAKE_RATE_LIMIT_PERIOD', 60 );
180 defined( 'LLA_MFA_FLOW_HANDSHAKE_RATE_LIMIT_MAX' ) || define( 'LLA_MFA_FLOW_HANDSHAKE_RATE_LIMIT_MAX', 5 );
181 defined( 'LLA_MFA_FLOW_LOG_PREFIX' ) || define( 'LLA_MFA_FLOW_LOG_PREFIX', 'LLAR MFA Flow: ' );
182 /* POST field name for confirming a suspected-prefetch rescue request (value 1 + WP nonce). */
183 defined( 'LLA_MFA_RESCUE_PREFETCH_BYPASS_ARG' ) || define( 'LLA_MFA_RESCUE_PREFETCH_BYPASS_ARG', 'llar_rescue_confirm' );
184
185 /** MFA Flow: API and session (values from constants, no UI settings). */
186 defined( 'LLA_MFA_API_BASE_URL' ) || define( 'LLA_MFA_API_BASE_URL', 'https://api.limitloginattempts.com' );
187 defined( 'LLA_MFA_API_PATH' ) || define( 'LLA_MFA_API_PATH', '/mfa' );
188 defined( 'LLA_MFA_SESSION_TTL' ) || define( 'LLA_MFA_SESSION_TTL', 600 ); /* seconds, 10 minutes */
189 defined( 'LLA_MFA_PROVIDER' ) || define( 'LLA_MFA_PROVIDER', 'llar' );
190
191 $um_limit_login_failed = false;
192 $limit_login_my_error_shown = false; /* have we shown our stuff? */
193 $limit_login_just_lockedout = false; /* started this pageload??? */
194 $limit_login_nonempty_credentials = false; /* user and pwd nonempty */
195
196 if ( file_exists( LLA_PLUGIN_DIR . 'autoload.php' ) ) {
197
198 require_once LLA_PLUGIN_DIR . 'autoload.php';
199
200 add_action(
201 'plugins_loaded',
202 function () {
203 ( new LLAR\Core\LimitLoginAttempts() );
204 },
205 9999
206 );
207
208 /**
209 * Activation hook: Cleanup old cron events and transients
210 */
211 register_activation_hook( __FILE__, 'llar_mfa_activation_cleanup' );
212
213 function llar_mfa_activation_cleanup() {
214 // Clear old rescue transients
215 llar_mfa_cleanup_rescue_transients();
216
217 // Schedule daily cleanup if not already scheduled
218 if ( ! wp_next_scheduled( 'llar_mfa_daily_cleanup' ) ) {
219 wp_schedule_event( time(), 'daily', 'llar_mfa_daily_cleanup' );
220 }
221
222 if ( class_exists( 'LLAR\\Core\\Helpers' ) ) {
223 \LLAR\Core\Helpers::persist_stored_plugin_version();
224 }
225 }
226
227 /**
228 * Deactivation hook: Cleanup cron events and transients (CRITICAL)
229 */
230 register_deactivation_hook( __FILE__, 'llar_mfa_deactivation_cleanup' );
231
232 function llar_mfa_deactivation_cleanup() {
233 // Clear all scheduled events
234 wp_clear_scheduled_hook( 'llar_mfa_daily_cleanup' );
235
236 // Clear all rescue transients
237 llar_mfa_cleanup_rescue_transients();
238 }
239
240 /**
241 * Daily cleanup: Remove old transients (prevents DB accumulation)
242 */
243 add_action( 'llar_mfa_daily_cleanup', 'llar_mfa_daily_cleanup' );
244
245 function llar_mfa_daily_cleanup() {
246 $keys = llar_mfa_get_expired_rescue_transient_keys();
247 foreach ( $keys as $key ) {
248 delete_transient( $key );
249 }
250 }
251
252 /**
253 * Get transient keys for rescue transients that are older than 1 day.
254 * Uses _transient_timeout_* where option_value is the expiration timestamp.
255 *
256 * @return array List of transient keys (e.g. llar_mfa_rescue_xxx).
257 */
258 function llar_mfa_get_expired_rescue_transient_keys() {
259 global $wpdb;
260 $prefix = LLA_MFA_TRANSIENT_RESCUE_PREFIX;
261 $cutoff = time() - DAY_IN_SECONDS;
262 $like = $wpdb->esc_like( '_transient_timeout_' . $prefix ) . '%';
263 $names = $wpdb->get_col(
264 $wpdb->prepare(
265 'SELECT option_name FROM ' . $wpdb->options . ' WHERE option_name LIKE %s AND option_value < %d',
266 $like,
267 $cutoff
268 )
269 );
270 if ( ! is_array( $names ) ) {
271 return array();
272 }
273 $prefix_len = strlen( '_transient_timeout_' );
274 $keys = array();
275 foreach ( $names as $name ) {
276 $keys[] = substr( $name, $prefix_len );
277 }
278 return $keys;
279 }
280
281 /**
282 * Helper: delete all rescue transients (e.g. on deactivation).
283 * Uses delete_transient() so object cache stays in sync.
284 */
285 function llar_mfa_cleanup_rescue_transients() {
286 $keys = llar_mfa_get_all_rescue_transient_keys();
287 foreach ( $keys as $key ) {
288 delete_transient( $key );
289 }
290 }
291
292 /**
293 * Get all rescue transient keys (for full cleanup).
294 *
295 * @return array List of transient keys.
296 */
297 function llar_mfa_get_all_rescue_transient_keys() {
298 global $wpdb;
299 $prefix = LLA_MFA_TRANSIENT_RESCUE_PREFIX;
300 $like = $wpdb->esc_like( '_transient_timeout_' . $prefix ) . '%';
301 $names = $wpdb->get_col(
302 $wpdb->prepare(
303 'SELECT option_name FROM ' . $wpdb->options . ' WHERE option_name LIKE %s',
304 $like
305 )
306 );
307 if ( ! is_array( $names ) ) {
308 return array();
309 }
310 $prefix_len = strlen( '_transient_timeout_' );
311 $keys = array();
312 foreach ( $names as $name ) {
313 $keys[] = substr( $name, $prefix_len );
314 }
315 return $keys;
316 }
317 }
318