PluginProbe ʕ •ᴥ•ʔ
Admin and Site Enhancements (ASE) / 4.2.1
Admin and Site Enhancements (ASE) v4.2.1
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 $table_name = $wpdb->prefix . 'asenha_failed_logins';
22
23 // Maybe create table if it does not exist yet, e.g. upgraded from previous version of plugin, so, no activation methods are fired
24 $query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table_name ) );
25
26 if ( $wpdb->get_var( $query ) === $table_name ) {
27 // Table already exists, do nothing.
28 } else {
29 $activation = new Activation;
30 $activation->create_failed_logins_log_table();
31 }
32
33 // Get values from options needed to do various checks
34 $options = get_option( ASENHA_SLUG_U );
35 $login_fails_allowed = $options['login_fails_allowed'];
36 $login_lockout_maxcount = $options['login_lockout_maxcount'];
37 $change_login_url = $options['change_login_url'];
38 $custom_login_slug = $options['custom_login_slug'];
39
40 // Instantiate object to access common methods
41 $common_methods = new Common_Methods;
42
43 // Get user/visitor IP address
44 $ip_address = $common_methods->get_user_ip_address();
45
46 // Check if IP address has failed login attempts recorded in the DB log
47 $sql = $wpdb->prepare("SELECT * FROM `" . $table_name . "` Where `ip_address` = %s", $ip_address);
48 $result = $wpdb->get_results( $sql, ARRAY_A );
49
50 $result_count = count( $result );
51
52 if ( $result_count > 0 ) { // IP address has been recorded in the database.
53
54 // Custom Login URL is enabled
55 if ( array_key_exists( 'change_login_url', $options ) && $options['change_login_url'] ) {
56 $fail_count = $result[0]['fail_count'];
57 } else {
58 $fail_count = $result[0]['fail_count'] + 1;
59 }
60
61 $lockout_count = $result[0]['lockout_count'];
62 $last_fail_on = $result[0]['unixtime'];
63
64 } else {
65
66 $fail_count = 0;
67 $lockout_count = 0;
68 $last_fail_on = '';
69
70 }
71
72 // Initialize the global variable
73 $asenha_limit_login = array (
74 'ip_address' => $ip_address,
75 'request_uri' => sanitize_text_field( $_SERVER['REQUEST_URI'] ),
76 'ip_address_log' => $result,
77 'maybe_lockout' => false,
78 'extended_lockout' => false,
79 'within_lockout_period' => false,
80 'lockout_period' => 0,
81 'lockout_period_remaining' => 0,
82 'login_fails_allowed' => $login_fails_allowed,
83 'login_lockout_maxcount' => $login_lockout_maxcount,
84 // 'default_lockout_period' => 60, // 1 minutes in seconds
85 'default_lockout_period' => 60*15, // 15 minutes in seconds
86 // 'extended_lockout_period' => 3*60, // 3 minutes in seconds
87 'extended_lockout_period' => 24*60*60, // 24 hours in seconds
88 'change_login_url' => $change_login_url, // is custom login URL enabled?
89 'custom_login_slug' => $custom_login_slug,
90 );
91
92 if ( $result_count > 0 ) { // IP address has been recorded in the database.
93
94 // Failed attempts have been recorded and fulfills lockout condition
95 if ( ! empty( $fail_count ) && ( ( $fail_count ) % $login_fails_allowed == 0 ) ) {
96
97 $asenha_limit_login['maybe_lockout'] = true;
98
99 // Has reached max / gone beyond number of lockouts allowed?
100 if ( $lockout_count >= $login_lockout_maxcount ) {
101 $asenha_limit_login['extended_lockout'] = true;
102 $lockout_period = $asenha_limit_login['extended_lockout_period'];
103 } else {
104 $asenha_limit_login['extended_lockout'] = false;
105 $lockout_period = $asenha_limit_login['default_lockout_period'];
106 }
107
108 $asenha_limit_login['lockout_period'] = $lockout_period;
109
110 // User/visitor is still within the lockout period
111 if ( ( time() - $last_fail_on ) <= $asenha_limit_login['lockout_period'] ) {
112
113 $asenha_limit_login['within_lockout_period'] = true;
114 $asenha_limit_login['lockout_period_remaining'] = $asenha_limit_login['lockout_period'] - ( time() - $last_fail_on );
115
116 if ( $asenha_limit_login['lockout_period_remaining'] <= 60 ) {
117
118 // Get remaining lockout period in minutes and seconds
119 $lockout_period_remaining = $asenha_limit_login['lockout_period_remaining'] . ' seconds';
120
121 } elseif ( $asenha_limit_login['lockout_period_remaining'] <= 60*60 ) {
122
123 // Get remaining lockout period in minutes and seconds
124 $lockout_period_remaining = $common_methods->seconds_to_period( $asenha_limit_login['lockout_period_remaining'], 'to-minutes-seconds' );
125
126 } elseif ( $asenha_limit_login['lockout_period_remaining'] > 60*60 && $asenha_limit_login['lockout_period_remaining'] <= 24*60*60 ) {
127
128 // Get remaining lockout period in minutes and seconds
129 $lockout_period_remaining = $common_methods->seconds_to_period( $asenha_limit_login['lockout_period_remaining'], 'to-hours-minutes-seconds' );
130
131 } elseif ( $asenha_limit_login['lockout_period_remaining'] > 24*60*60 ) {
132
133 // Get remaining lockout period in minutes and seconds
134 $lockout_period_remaining = $common_methods->seconds_to_period( $asenha_limit_login['lockout_period_remaining'], 'to-days-hours-minutes-seconds' );
135
136 }
137
138 $error = new WP_Error( 'ip_address_blocked', '<b>WARNING:</b> You\'ve been locked out. You can login again in ' . $lockout_period_remaining . '.' );
139
140 // Prevent redirection loop
141 remove_action( 'wp_login_failed', [ $this, 'redirect_to_custom_login_url_on_login_fail' ] );
142
143 return $error;
144
145 } else { // User/visitor is no longer within the lockout period
146
147 $asenha_limit_login['within_lockout_period'] = false;
148
149 if ( $lockout_count == $login_lockout_maxcount ) {
150
151 // Remove the DB log entry for the current IP address. i.e. release from extended lockout
152
153 $where = array( 'ip_address' => $ip_address );
154 $where_format = array( '%s' );
155
156 // Delete existing data in the database
157 $wpdb->delete(
158 $table_name,
159 $where,
160 $where_format
161 );
162
163 }
164
165 return $user_or_error;
166
167 }
168
169 } else {
170
171 $asenha_limit_login['maybe_lockout'] = false;
172
173 return $user_or_error;
174
175 }
176
177 } else { // IP address has not been recorded in the database.
178
179 return $user_or_error;
180
181 }
182
183 }
184
185 /**
186 * Disable login form inputs via javascript
187 *
188 * @since 2.5.0
189 */
190 public function maybe_hide_login_form() {
191
192 global $asenha_limit_login;
193
194 if ( $asenha_limit_login['within_lockout_period'] ) {
195
196 // Hide logo, login form and the links below it
197 ?>
198 <style type="text/css">
199
200 body.login {
201 background:#f6d6d7;
202 }
203
204 #login h1,
205 #loginform,
206 #login #nav,
207 #backtoblog {
208 display: none;
209 }
210
211 @media screen and (max-height: 550px) {
212
213 #login {
214 padding: 80px 0 20px !important;
215 }
216
217 }
218
219 </style>
220 <?php
221 }
222
223 }
224
225 /**
226 * Log failed login attempts
227 *
228 * @since 2.5.0
229 */
230 public function log_failed_login( $username ) {
231
232 global $wpdb, $asenha_limit_login;
233 $table_name = $wpdb->prefix . 'asenha_failed_logins';
234
235 // Check if the IP address has been used in a failed login attempt before, i.e. has it been recorded in the database?
236 $sql = $wpdb->prepare( "SELECT * FROM `" . $table_name . "` WHERE `ip_address` = %s", $asenha_limit_login['ip_address'] );
237 $result = $wpdb->get_results( $sql, ARRAY_A );
238 $result_count = count( $result );
239
240 // Update logged info for the IP address in the global variable
241 $asenha_limit_login['ip_address_log'] = $result;
242
243 if ( $result_count == 0 ) { // IP address has not been recorded in the database.
244
245 $new_fail_count = 1;
246 $new_lockout_count = 0;
247
248 } else { // IP address has been recorded in the database.
249
250 $new_fail_count = $result[0]['fail_count'] + 1;
251 $new_lockout_count = floor( ( $result[0]['fail_count'] + 1 ) / $asenha_limit_login['login_fails_allowed'] );
252
253 }
254
255 // Get the URL where login failed, i.e. where brute force attack might be happening
256 // $login_url = ( ! empty( $_SERVER['HTTPS'] ) ? 'https://' : 'http://') . sanitize_text_field( $_SERVER['HTTP_HOST'] ) . sanitize_text_field( $_SERVER['REQUEST_URI'] );
257
258 // Time stamps
259 $unixtime = time();
260 if ( function_exists( 'wp_date' ) ) {
261 $datetime_wp = wp_date( 'Y-m-d H:i:s', $unixtime );
262 } else {
263 $datetime_wp = date_i18n( 'Y-m-d H:i:s', $unixtime );
264 }
265
266 $data = array(
267 'ip_address' => $asenha_limit_login['ip_address'],
268 'username' => $username,
269 'fail_count' => $new_fail_count,
270 'lockout_count' => $new_lockout_count,
271 'request_uri' => $asenha_limit_login['request_uri'],
272 'unixtime' => $unixtime,
273 'datetime_wp' => $datetime_wp,
274 'info' => '',
275 );
276
277 $data_format = array(
278 '%s', // string
279 '%s', // string
280 '%d', // integer
281 '%d', // integer
282 '%s', // string
283 '%d', // integer
284 '%s', // string
285 '%s', // string
286 );
287
288 if ( $result_count == 0 ) {
289
290 // Insert into the database
291 $result = $wpdb->insert(
292 $table_name,
293 $data,
294 $data_format
295 );
296
297 } else {
298
299 // $options = get_option( ASENHA_SLUG_U );
300 // $login_fails_allowed = $options['login_fails_allowed'];
301
302 $fail_count = $result[0]['fail_count'];
303 $lockout_count = $result[0]['lockout_count'];
304 $last_fail_on = $result[0]['unixtime'];
305
306 $where = array( 'ip_address' => $asenha_limit_login['ip_address'] );
307 $where_format = array( '%s' );
308
309 // Failed attempts have been recorded and fulfills lockout condition
310 if ( ! empty( $fail_count ) && ( $fail_count % $asenha_limit_login['login_fails_allowed'] == 0 ) ) {
311
312 // Has reached max / gone beyond number of lockouts allowed?
313 if ( $lockout_count >= $asenha_limit_login['login_lockout_maxcount'] ) {
314 $asenha_limit_login['extended_lockout'] = true;
315 $lockout_period = $asenha_limit_login['extended_lockout_period'];
316 } else {
317 $asenha_limit_login['extended_lockout'] = false;
318 $lockout_period = $asenha_limit_login['default_lockout_period'];
319 }
320
321 $asenha_limit_login['lockout_period'] = $lockout_period;
322
323 // User/visitor is still within the lockout period
324 if ( ( time() - $last_fail_on ) <= $asenha_limit_login['lockout_period'] ) {
325
326 // Do nothing
327
328 } else {
329
330 if ( $lockout_count < $asenha_limit_login['login_lockout_maxcount'] ) {
331
332 // Update existing data in the database
333 $wpdb->update(
334 $table_name,
335 $data,
336 $where,
337 $data_format,
338 $where_format
339 );
340
341 }
342
343 }
344
345 } else {
346
347 // Update existing data in the database
348 $wpdb->update(
349 $table_name,
350 $data,
351 $where,
352 $data_format,
353 $where_format
354 );
355
356 }
357
358 }
359
360 }
361
362 /**
363 * Handle login errors
364 *
365 * @link https://developer.wordpress.org/reference/classes/wp_error/#methods
366 * @since 2.5.0
367 */
368 public function login_error_handler( $errors, $redirect_to ) {
369
370 global $asenha_limit_login;
371
372 if ( is_wp_error( $errors ) ) {
373
374 $error_codes = $errors->get_error_codes();
375
376 foreach ( $error_codes as $error_code ) {
377
378 if ( $error_code == 'invalid_username' || $error_code == 'incorrect_password' ) {
379
380 // Remove default error messages that may give out valueable info to hackers
381
382 $errors->remove( 'invalid_username' ); // Outputs info that says username does not exist. May encourage login attempt with a different username instead.
383
384 $errors->remove( 'incorrect_password' ); // Outputs info that implies username exist. May encourage login attempt with a different password.
385
386 // Add a new error message that does not provide useful clues to hackers
387 $errors->add( 'invalid_username_or_incorrect_password', '<b>Error:</b> Invalid username or incorrect password.' );
388
389 // $errors->add( 'another_error_code', 'The error message.' );
390
391 }
392
393 }
394
395 }
396
397 return $errors;
398
399 }
400
401 /**
402 * Add login error message on top of the login form
403 *
404 * @since 2.5.0
405 */
406 public function add_failed_login_message( $message ) {
407
408 global $asenha_limit_login;
409
410 if ( isset( $_REQUEST['failed_login'] ) && $_REQUEST['failed_login'] == 'true' ) {
411
412 if ( ! $asenha_limit_login['within_lockout_period'] ) {
413
414 $message = '<div id="login_error"><b>Error:</b> Invalid username or incorrect password.</div>';
415
416 }
417
418 }
419
420 return $message;
421
422 }
423
424 /**
425 * Clear failed login attempts log after successful login
426 *
427 * @since 2.5.0
428 */
429 public function clear_failed_login_log() {
430
431 global $wpdb, $asenha_limit_login;
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 }