PluginProbe ʕ •ᴥ•ʔ
Admin and Site Enhancements (ASE) / 5.2.3
Admin and Site Enhancements (ASE) v5.2.3
9.1.0 9.0.2 9.0.1 9.0.0 8.9.2 8.9.1 8.9.0 8.8.8 8.8.7 8.8.6 8.8.5 8.8.4 8.8.3 8.8.2 8.8.1 8.8.0 8.7.3 8.7.2 8.7.1 8.2.1 8.2.2 8.2.3 8.3.0 8.3.1 8.3.2 8.4.0 8.4.1 8.4.2 8.5.0 8.5.1 8.5.2 8.6.0 8.6.1 8.6.2 8.7.0 5.0.1 5.0.2 5.0.2.1 5.0.2.2 5.0.2.3 5.0.2.4 5.1.0 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.8.0 5.8.1 6.0.0 6.0.3 6.0.4 6.0.5 6.0.5.1 6.0.6 6.0.7 6.0.8.1 6.1.0 6.1.3 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.3.0 6.3.1 6.3.2 6.4.0 6.5.0 6.5.1 6.6.0 6.7.0 6.8.0 6.8.2 6.8.3 6.9.0 6.9.1 6.9.10 6.9.11 6.9.12 6.9.13.1 6.9.13.2 6.9.2 6.9.3 6.9.4 6.9.5 6.9.6 6.9.7 6.9.8 6.9.9 7.0.0 7.0.2 7.0.2.1 7.0.2.2 7.0.2.3 7.0.3 7.1.0 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2.0 7.2.1 7.3.0 7.3.1 7.3.2 7.3.3 7.4.0 7.4.2 7.4.4 7.4.5 7.4.6 7.4.7 7.4.8 7.5.0 7.5.1 7.5.2 7.5.3 7.5.4 7.6.0 7.6.1 7.6.1.1 7.6.10 7.6.11 7.6.2 7.6.3 7.6.4 7.6.5 7.6.6 7.6.7 7.6.7.1 7.6.8 7.6.9 7.7.0 7.8.0 7.8.1 7.8.10 7.8.11 7.8.12 7.8.13 7.8.14 7.8.15 7.8.16 7.8.17 7.8.18 7.8.2 7.8.3 7.8.4 7.8.5 7.8.5.1 7.8.6 7.8.7 7.8.8 7.8.9 7.9.0 7.9.1 7.9.10 7.9.11 7.9.2 7.9.3 7.9.4 7.9.5 7.9.6 7.9.7 7.9.8 7.9.9 8.0.0 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.0.6 8.0.7 8.0.8 8.1.0 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 trunk 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 1.9.0 2.0.0 2.1.0 2.2.0 2.3.0 2.4.0 2.5.0 2.6.0 2.7.0 2.8.0 2.8.1 2.8.2 2.8.3 2.9.0 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.2.0 3.3.0 3.4.0 3.5.0 3.6.1 3.7.0 3.8.0 3.9.0 3.9.1 3.9.2 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.1 4.8.2 4.8.3 4.9.0 4.9.1 4.9.2 4.9.3 5.0.0
admin-site-enhancements / classes / class-security.php
admin-site-enhancements / classes Last commit date
class-activation.php 3 years ago class-admin-interface.php 3 years ago class-common-methods.php 3 years ago class-content-management.php 3 years ago class-custom-code.php 3 years ago class-deactivation.php 3 years ago class-disable-components.php 3 years ago class-login-logout.php 3 years ago class-optimizations.php 3 years ago class-security.php 3 years ago class-settings-fields-render.php 3 years ago class-settings-sanitization.php 3 years ago class-settings-sections-fields.php 3 years ago class-utilities.php 3 years ago
class-security.php
560 lines
1 <?php
2
3 namespace ASENHA\Classes;
4 use WP_Error;
5
6 /**
7 * Class related to Security features
8 *
9 * @since 1.4.0
10 */
11 class Security {
12
13 /**
14 * Maybe allow login if not locked out. Should return WP_Error object if not allowed to login.
15 *
16 * @since 2.5.0
17 */
18 public function maybe_allow_login( $user_or_error, $username, $password ) {
19
20 global $wpdb, $asenha_limit_login;
21
22 $table_name = $wpdb->prefix . 'asenha_failed_logins';
23
24 // Maybe create table if it does not exist yet, e.g. upgraded from previous version of plugin, so, no activation methods are fired
25 $query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table_name ) );
26
27 if ( $wpdb->get_var( $query ) === $table_name ) {
28 // Table already exists, do nothing.
29 } else {
30 $activation = new Activation;
31 $activation->create_failed_logins_log_table();
32 }
33
34 // Get values from options needed to do various checks
35 $options = get_option( ASENHA_SLUG_U );
36 $login_fails_allowed = $options['login_fails_allowed'];
37 $login_lockout_maxcount = $options['login_lockout_maxcount'];
38 $change_login_url = $options['change_login_url'];
39 $custom_login_slug = $options['custom_login_slug'];
40
41 // Instantiate object to access common methods
42 $common_methods = new Common_Methods;
43
44 // Get user/visitor IP address
45 $ip_address = $common_methods->get_user_ip_address();
46
47 // Check if IP address has failed login attempts recorded in the DB log
48 $sql = $wpdb->prepare("SELECT * FROM `" . $table_name . "` Where `ip_address` = %s", $ip_address);
49 $result = $wpdb->get_results( $sql, ARRAY_A );
50
51 $result_count = count( $result );
52
53 if ( $result_count > 0 ) { // IP address has been recorded in the database.
54
55 // Custom Login URL is enabled
56 if ( array_key_exists( 'change_login_url', $options ) && $options['change_login_url'] ) {
57 $fail_count = $result[0]['fail_count'];
58 } else {
59 $fail_count = $result[0]['fail_count'] + 1;
60 }
61
62 $lockout_count = $result[0]['lockout_count'];
63 $last_fail_on = $result[0]['unixtime'];
64
65 } else {
66
67 $fail_count = 0;
68 $lockout_count = 0;
69 $last_fail_on = '';
70
71 }
72
73 // Initialize the global variable
74 $asenha_limit_login = array (
75 'ip_address' => $ip_address,
76 'request_uri' => sanitize_text_field( $_SERVER['REQUEST_URI'] ),
77 'ip_address_log' => $result,
78 'maybe_lockout' => false,
79 'extended_lockout' => false,
80 'within_lockout_period' => false,
81 'lockout_period' => 0,
82 'lockout_period_remaining' => 0,
83 'login_fails_allowed' => $login_fails_allowed,
84 'login_lockout_maxcount' => $login_lockout_maxcount,
85 // 'default_lockout_period' => 60, // 1 minutes in seconds
86 'default_lockout_period' => 60*15, // 15 minutes in seconds
87 // 'extended_lockout_period' => 3*60, // 3 minutes in seconds
88 'extended_lockout_period' => 24*60*60, // 24 hours in seconds
89 'change_login_url' => $change_login_url, // is custom login URL enabled?
90 'custom_login_slug' => $custom_login_slug,
91 );
92
93 if ( $result_count > 0 ) { // IP address has been recorded in the database.
94
95 // Failed attempts have been recorded and fulfills lockout condition
96 if ( ! empty( $fail_count ) && ( ( $fail_count ) % $login_fails_allowed == 0 ) ) {
97
98 $asenha_limit_login['maybe_lockout'] = true;
99
100 // Has reached max / gone beyond number of lockouts allowed?
101 if ( $lockout_count >= $login_lockout_maxcount ) {
102 $asenha_limit_login['extended_lockout'] = true;
103 $lockout_period = $asenha_limit_login['extended_lockout_period'];
104 } else {
105 $asenha_limit_login['extended_lockout'] = false;
106 $lockout_period = $asenha_limit_login['default_lockout_period'];
107 }
108
109 $asenha_limit_login['lockout_period'] = $lockout_period;
110
111 // User/visitor is still within the lockout period
112 if ( ( time() - $last_fail_on ) <= $asenha_limit_login['lockout_period'] ) {
113
114 $asenha_limit_login['within_lockout_period'] = true;
115 $asenha_limit_login['lockout_period_remaining'] = $asenha_limit_login['lockout_period'] - ( time() - $last_fail_on );
116
117 if ( $asenha_limit_login['lockout_period_remaining'] <= 60 ) {
118
119 // Get remaining lockout period in minutes and seconds
120 $lockout_period_remaining = $asenha_limit_login['lockout_period_remaining'] . ' seconds';
121
122 } elseif ( $asenha_limit_login['lockout_period_remaining'] <= 60*60 ) {
123
124 // Get remaining lockout period in minutes and seconds
125 $lockout_period_remaining = $common_methods->seconds_to_period( $asenha_limit_login['lockout_period_remaining'], 'to-minutes-seconds' );
126
127 } elseif ( $asenha_limit_login['lockout_period_remaining'] > 60*60 && $asenha_limit_login['lockout_period_remaining'] <= 24*60*60 ) {
128
129 // Get remaining lockout period in minutes and seconds
130 $lockout_period_remaining = $common_methods->seconds_to_period( $asenha_limit_login['lockout_period_remaining'], 'to-hours-minutes-seconds' );
131
132 } elseif ( $asenha_limit_login['lockout_period_remaining'] > 24*60*60 ) {
133
134 // Get remaining lockout period in minutes and seconds
135 $lockout_period_remaining = $common_methods->seconds_to_period( $asenha_limit_login['lockout_period_remaining'], 'to-days-hours-minutes-seconds' );
136
137 }
138
139 $error = new WP_Error( 'ip_address_blocked', '<b>WARNING:</b> You\'ve been locked out. You can login again in ' . $lockout_period_remaining . '.' );
140
141 return $error;
142
143 } else { // User/visitor is no longer within the lockout period
144
145 $asenha_limit_login['within_lockout_period'] = false;
146
147 if ( $lockout_count == $login_lockout_maxcount ) {
148
149 // Remove the DB log entry for the current IP address. i.e. release from extended lockout
150
151 $where = array( 'ip_address' => $ip_address );
152 $where_format = array( '%s' );
153
154 // Delete existing data in the database
155 $wpdb->delete(
156 $table_name,
157 $where,
158 $where_format
159 );
160
161 }
162
163 return $user_or_error;
164
165 }
166
167 } else {
168
169 $asenha_limit_login['maybe_lockout'] = false;
170
171 return $user_or_error;
172
173 }
174
175 } else { // IP address has not been recorded in the database.
176
177 return $user_or_error;
178
179 }
180
181 }
182
183 /**
184 * Disable login form inputs via javascript
185 *
186 * @since 2.5.0
187 */
188 public function maybe_hide_login_form() {
189
190 global $asenha_limit_login;
191
192 if ( $asenha_limit_login['within_lockout_period'] ) {
193
194 // Hide logo, login form and the links below it
195 ?>
196 <style type="text/css">
197
198 body.login {
199 background:#f6d6d7;
200 }
201
202 #login h1,
203 #loginform,
204 #login #nav,
205 #backtoblog {
206 display: none;
207 }
208
209 @media screen and (max-height: 550px) {
210
211 #login {
212 padding: 80px 0 20px !important;
213 }
214
215 }
216
217 </style>
218 <?php
219 }
220
221 }
222
223 /**
224 * Log failed login attempts
225 *
226 * @since 2.5.0
227 */
228 public function log_failed_login( $username ) {
229
230 global $wpdb, $asenha_limit_login;
231
232 $table_name = $wpdb->prefix . 'asenha_failed_logins';
233
234 // Check if the IP address has been used in a failed login attempt before, i.e. has it been recorded in the database?
235 $sql = $wpdb->prepare( "SELECT * FROM `" . $table_name . "` WHERE `ip_address` = %s", $asenha_limit_login['ip_address'] );
236 $result = $wpdb->get_results( $sql, ARRAY_A );
237 $result_count = count( $result );
238
239 // Update logged info for the IP address in the global variable
240 $asenha_limit_login['ip_address_log'] = $result;
241
242 if ( $result_count == 0 ) { // IP address has not been recorded in the database.
243
244 $new_fail_count = 1;
245 $new_lockout_count = 0;
246
247 } else { // IP address has been recorded in the database.
248
249 $new_fail_count = $result[0]['fail_count'] + 1;
250 $new_lockout_count = floor( ( $result[0]['fail_count'] + 1 ) / $asenha_limit_login['login_fails_allowed'] );
251
252 }
253
254 // Get the URL where login failed, i.e. where brute force attack might be happening
255 // $login_url = ( ! empty( $_SERVER['HTTPS'] ) ? 'https://' : 'http://') . sanitize_text_field( $_SERVER['HTTP_HOST'] ) . sanitize_text_field( $_SERVER['REQUEST_URI'] );
256
257 // Time stamps
258 $unixtime = time();
259 if ( function_exists( 'wp_date' ) ) {
260 $datetime_wp = wp_date( 'Y-m-d H:i:s', $unixtime );
261 } else {
262 $datetime_wp = date_i18n( 'Y-m-d H:i:s', $unixtime );
263 }
264
265 $data = array(
266 'ip_address' => $asenha_limit_login['ip_address'],
267 'username' => $username,
268 'fail_count' => $new_fail_count,
269 'lockout_count' => $new_lockout_count,
270 'request_uri' => $asenha_limit_login['request_uri'],
271 'unixtime' => $unixtime,
272 'datetime_wp' => $datetime_wp,
273 'info' => '',
274 );
275
276 $data_format = array(
277 '%s', // string
278 '%s', // string
279 '%d', // integer
280 '%d', // integer
281 '%s', // string
282 '%d', // integer
283 '%s', // string
284 '%s', // string
285 );
286
287 if ( $result_count == 0 ) {
288
289 // Insert into the database
290 $result = $wpdb->insert(
291 $table_name,
292 $data,
293 $data_format
294 );
295
296 } else {
297
298 // $options = get_option( ASENHA_SLUG_U );
299 // $login_fails_allowed = $options['login_fails_allowed'];
300
301 $fail_count = $result[0]['fail_count'];
302 $lockout_count = $result[0]['lockout_count'];
303 $last_fail_on = $result[0]['unixtime'];
304
305 $where = array( 'ip_address' => $asenha_limit_login['ip_address'] );
306 $where_format = array( '%s' );
307
308 // Failed attempts have been recorded and fulfills lockout condition
309 if ( ! empty( $fail_count ) && ( $fail_count % $asenha_limit_login['login_fails_allowed'] == 0 ) ) {
310
311 // Has reached max / gone beyond number of lockouts allowed?
312 if ( $lockout_count >= $asenha_limit_login['login_lockout_maxcount'] ) {
313 $asenha_limit_login['extended_lockout'] = true;
314 $lockout_period = $asenha_limit_login['extended_lockout_period'];
315 } else {
316 $asenha_limit_login['extended_lockout'] = false;
317 $lockout_period = $asenha_limit_login['default_lockout_period'];
318 }
319
320 $asenha_limit_login['lockout_period'] = $lockout_period;
321
322 // User/visitor is still within the lockout period
323 if ( ( time() - $last_fail_on ) <= $asenha_limit_login['lockout_period'] ) {
324
325 // Do nothing
326
327 } else {
328
329 if ( $lockout_count < $asenha_limit_login['login_lockout_maxcount'] ) {
330
331 // Update existing data in the database
332 $wpdb->update(
333 $table_name,
334 $data,
335 $where,
336 $data_format,
337 $where_format
338 );
339
340 }
341
342 }
343
344 } else {
345
346 // Update existing data in the database
347 $wpdb->update(
348 $table_name,
349 $data,
350 $where,
351 $data_format,
352 $where_format
353 );
354
355 }
356
357 }
358
359 }
360
361 /**
362 * Handle login errors
363 *
364 * @link https://developer.wordpress.org/reference/classes/wp_error/#methods
365 * @since 2.5.0
366 */
367 public function login_error_handler( $errors, $redirect_to ) {
368
369 global $asenha_limit_login;
370
371 if ( is_wp_error( $errors ) ) {
372
373 $error_codes = $errors->get_error_codes();
374
375 foreach ( $error_codes as $error_code ) {
376
377 if ( $error_code == 'invalid_username' || $error_code == 'incorrect_password' ) {
378
379 // Remove default error messages that may give out valueable info to hackers
380
381 $errors->remove( 'invalid_username' ); // Outputs info that says username does not exist. May encourage login attempt with a different username instead.
382
383 $errors->remove( 'incorrect_password' ); // Outputs info that implies username exist. May encourage login attempt with a different password.
384
385 // Add a new error message that does not provide useful clues to hackers
386 $errors->add( 'invalid_username_or_incorrect_password', '<b>Error:</b> Invalid username or incorrect password.' );
387
388 // $errors->add( 'another_error_code', 'The error message.' );
389
390 }
391
392 }
393
394 }
395
396 return $errors;
397
398 }
399
400 /**
401 * Add login error message on top of the login form
402 *
403 * @since 2.5.0
404 */
405 public function add_failed_login_message( $message ) {
406
407 global $asenha_limit_login;
408
409 if ( isset( $_REQUEST['failed_login'] ) && $_REQUEST['failed_login'] == 'true' ) {
410
411 if ( ! $asenha_limit_login['within_lockout_period'] ) {
412
413 $message = '<div id="login_error"><b>Error:</b> Invalid username or incorrect password.</div>';
414
415 }
416
417 }
418
419 return $message;
420
421 }
422
423 /**
424 * Clear failed login attempts log after successful login
425 *
426 * @since 2.5.0
427 */
428 public function clear_failed_login_log() {
429
430 global $wpdb, $asenha_limit_login;
431
432 $table_name = $wpdb->prefix . 'asenha_failed_logins';
433
434 // Remove the DB log entry for the current IP address.
435
436 $where = array( 'ip_address' => $asenha_limit_login['ip_address'] );
437 $where_format = array( '%s' );
438
439 $wpdb->delete(
440 $table_name,
441 $where,
442 $where_format
443 );
444
445 }
446
447 /**
448 * If an author name is queried, decrypt it. Used by pre_get_posts action.
449 *
450 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/tags/4.0.2/inc/class-smart-user-slug-hider.php
451 * @since 2.1.0
452 */
453 function alter_author_query( $query ) {
454
455 // Check if it's a query for author data, and that 'author_name' is not empty
456 if ( $query->is_author() && $query->query_vars['author_name'] != '' ) {
457
458 // Check for character(s) representing a hexadecimal digit
459 if ( ctype_xdigit( $query->query_vars['author_name'] ) ) {
460
461 // Get user by the decrypted user ID
462 $user = get_user_by( 'id', $this->decrypt( $query->query_vars['author_name'] ) );
463
464 if ( $user ) {
465
466 $query->set( 'author_name', $user->user_nicename );
467
468 } else {
469
470 // No user found
471 $query->is_404 = true;
472 $query->is_author = false;
473 $query->is_archive = false;
474
475 }
476
477 } else {
478
479 // No hexadecimal digit detected in URL, i.e. someone is trying to access URL with original author slug
480 $query->is_404 = true;
481 $query->is_author = false;
482 $query->is_archive = false;
483
484 }
485
486 }
487
488 return;
489 }
490
491 /**
492 * Replace author slug in author link to encrypted value. Used by author_link filter.
493 *
494 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/tags/4.0.2/inc/class-smart-user-slug-hider.php
495 * @since 2.1.0
496 */
497 function alter_author_link( $link, $user_id, $author_slug ) {
498
499 $encrypted_author_slug = $this->encrypt( $user_id );
500
501 return str_replace ( '/' . $author_slug, '/' . $encrypted_author_slug, $link );
502
503 }
504
505 /**
506 * Replace author slug in REST API /users/ endpoint to encrypted value. Used by rest_prepare_user filter.
507 *
508 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/tags/4.0.2/inc/class-smart-user-slug-hider.php
509 * @since 2.1.0
510 */
511 function alter_json_users($response, $user, $request) {
512
513 $data = $response->get_data();
514 $data['slug'] = $this->encrypt($data['id']);
515 $response->set_data($data);
516
517 return $response;
518
519 }
520
521 /**
522 * Helper function to return an encrypted user ID, which will then be used to replace the author slug.
523 *
524 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/trunk/inc/class-smart-user-slug-hider.php
525 * @since 2.1.0
526 */
527 private function encrypt( $user_id ) {
528
529 // Returns encrypted encrypted author slug from user ID, e.g. encrypt user ID 3 to author slug 4e3062d8c8626a14
530 return bin2hex( openssl_encrypt( base_convert( $user_id, 10, 36 ), 'DES-EDE3', md5( sanitize_text_field( $_SERVER['SERVER_ADDR'] ) . ASENHA_URL ), OPENSSL_RAW_DATA ) );
531
532 }
533
534
535 /**
536 * Helper function to decrypt an (encrypted) author slug and returns the user ID
537 *
538 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/trunk/inc/class-smart-user-slug-hider.php
539 * @since 2.1.0
540 */
541 private function decrypt( $encrypted_author_slug ) {
542
543 // Returns user ID, e.g. decrypts author slug 4e3062d8c8626a14 into user ID 3
544 return base_convert( openssl_decrypt( pack('H*', $encrypted_author_slug), 'DES-EDE3', md5( sanitize_text_field( $_SERVER['SERVER_ADDR'] ) . ASENHA_URL ), OPENSSL_RAW_DATA ), 36, 10 );
545
546 }
547
548 /**
549 * Disable the XML-RPC component
550 *
551 * @since 2.2.0
552 */
553 public function maybe_disable_xmlrpc( $data ) {
554
555 http_response_code(403);
556 exit('You don\'t have permission to access this file.');
557
558 }
559
560 }