PluginProbe ʕ •ᴥ•ʔ
Admin and Site Enhancements (ASE) / 5.5.2
Admin and Site Enhancements (ASE) v5.5.2
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
518 lines
1 <?php
2
3 namespace ASENHA\Classes;
4
5 use WP_Error ;
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 // Maybe create table if it does not exist yet, e.g. upgraded from previous version of plugin, so, no activation methods are fired
23 $query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table_name ) );
24
25 if ( $wpdb->get_var( $query ) === $table_name ) {
26 // Table already exists, do nothing.
27 } else {
28 $activation = new Activation();
29 $activation->create_failed_logins_log_table();
30 }
31
32 // Get values from options needed to do various checks
33 $options = get_option( ASENHA_SLUG_U );
34 $login_fails_allowed = $options['login_fails_allowed'];
35 $login_lockout_maxcount = $options['login_lockout_maxcount'];
36 $change_login_url = $options['change_login_url'];
37 $custom_login_slug = $options['custom_login_slug'];
38 // Instantiate object to access common methods
39 $common_methods = new Common_Methods();
40 // Get user/visitor IP address
41 $ip_address = $common_methods->get_user_ip_address();
42 // Check if IP address has failed login attempts recorded in the DB log
43 $sql = $wpdb->prepare( "SELECT * FROM `" . $table_name . "` Where `ip_address` = %s", $ip_address );
44 $result = $wpdb->get_results( $sql, ARRAY_A );
45 $result_count = count( $result );
46
47 if ( $result_count > 0 ) {
48 // IP address has been recorded in the database.
49 // Custom Login URL is enabled
50
51 if ( array_key_exists( 'change_login_url', $options ) && $options['change_login_url'] ) {
52 $fail_count = $result[0]['fail_count'];
53 } else {
54 $fail_count = $result[0]['fail_count'] + 1;
55 }
56
57 $lockout_count = $result[0]['lockout_count'];
58 $last_fail_on = $result[0]['unixtime'];
59 } else {
60 $fail_count = 0;
61 $lockout_count = 0;
62 $last_fail_on = '';
63 }
64
65 // Initialize the global variable
66 $asenha_limit_login = array(
67 'ip_address' => $ip_address,
68 'request_uri' => sanitize_text_field( $_SERVER['REQUEST_URI'] ),
69 'ip_address_log' => $result,
70 'maybe_lockout' => false,
71 'extended_lockout' => false,
72 'within_lockout_period' => false,
73 'lockout_period' => 0,
74 'lockout_period_remaining' => 0,
75 'login_fails_allowed' => $login_fails_allowed,
76 'login_lockout_maxcount' => $login_lockout_maxcount,
77 'default_lockout_period' => 60 * 15,
78 'extended_lockout_period' => 24 * 60 * 60,
79 'change_login_url' => $change_login_url,
80 'custom_login_slug' => $custom_login_slug,
81 );
82
83 if ( $result_count > 0 ) {
84 // IP address has been recorded in the database.
85 // Failed attempts have been recorded and fulfills lockout condition
86
87 if ( !empty($fail_count) && $fail_count % $login_fails_allowed == 0 ) {
88 $asenha_limit_login['maybe_lockout'] = true;
89 // Has reached max / gone beyond number of lockouts allowed?
90
91 if ( $lockout_count >= $login_lockout_maxcount ) {
92 $asenha_limit_login['extended_lockout'] = true;
93 $lockout_period = $asenha_limit_login['extended_lockout_period'];
94 } else {
95 $asenha_limit_login['extended_lockout'] = false;
96 $lockout_period = $asenha_limit_login['default_lockout_period'];
97 }
98
99 $asenha_limit_login['lockout_period'] = $lockout_period;
100 // User/visitor is still within the lockout period
101
102 if ( time() - $last_fail_on <= $asenha_limit_login['lockout_period'] ) {
103 $asenha_limit_login['within_lockout_period'] = true;
104 $asenha_limit_login['lockout_period_remaining'] = $asenha_limit_login['lockout_period'] - (time() - $last_fail_on);
105
106 if ( $asenha_limit_login['lockout_period_remaining'] <= 60 ) {
107 // Get remaining lockout period in minutes and seconds
108 $lockout_period_remaining = $asenha_limit_login['lockout_period_remaining'] . ' seconds';
109 } elseif ( $asenha_limit_login['lockout_period_remaining'] <= 60 * 60 ) {
110 // Get remaining lockout period in minutes and seconds
111 $lockout_period_remaining = $common_methods->seconds_to_period( $asenha_limit_login['lockout_period_remaining'], 'to-minutes-seconds' );
112 } elseif ( $asenha_limit_login['lockout_period_remaining'] > 60 * 60 && $asenha_limit_login['lockout_period_remaining'] <= 24 * 60 * 60 ) {
113 // Get remaining lockout period in minutes and seconds
114 $lockout_period_remaining = $common_methods->seconds_to_period( $asenha_limit_login['lockout_period_remaining'], 'to-hours-minutes-seconds' );
115 } elseif ( $asenha_limit_login['lockout_period_remaining'] > 24 * 60 * 60 ) {
116 // Get remaining lockout period in minutes and seconds
117 $lockout_period_remaining = $common_methods->seconds_to_period( $asenha_limit_login['lockout_period_remaining'], 'to-days-hours-minutes-seconds' );
118 }
119
120 $error = new WP_Error( 'ip_address_blocked', '<b>WARNING:</b> You\'ve been locked out. You can login again in ' . $lockout_period_remaining . '.' );
121 return $error;
122 } else {
123 // User/visitor is no longer within the lockout period
124 $asenha_limit_login['within_lockout_period'] = false;
125
126 if ( $lockout_count == $login_lockout_maxcount ) {
127 // Remove the DB log entry for the current IP address. i.e. release from extended lockout
128 $where = array(
129 'ip_address' => $ip_address,
130 );
131 $where_format = array( '%s' );
132 // Delete existing data in the database
133 $wpdb->delete( $table_name, $where, $where_format );
134 }
135
136 return $user_or_error;
137 }
138
139 } else {
140 $asenha_limit_login['maybe_lockout'] = false;
141 return $user_or_error;
142 }
143
144 } else {
145 // IP address has not been recorded in the database.
146 return $user_or_error;
147 }
148
149 }
150
151 /**
152 * Disable login form inputs via CSS
153 *
154 * @since 2.5.0
155 */
156 public function maybe_hide_login_form()
157 {
158 global $asenha_limit_login ;
159
160 if ( isset( $asenha_limit_login ) && $asenha_limit_login['within_lockout_period'] ) {
161 // Hide logo, login form and the links below it
162 ?>
163 <style type="text/css">
164
165 body.login {
166 background:#f6d6d7;
167 }
168
169 #login h1,
170 #loginform,
171 #login #nav,
172 #backtoblog {
173 display: none;
174 }
175
176 @media screen and (max-height: 550px) {
177
178 #login {
179 padding: 80px 0 20px !important;
180 }
181
182 }
183
184 </style>
185 <?php
186 }
187
188 }
189
190 /**
191 * Log failed login attempts
192 *
193 * @since 2.5.0
194 */
195 public function log_failed_login( $username )
196 {
197 global $wpdb, $asenha_limit_login ;
198 $table_name = $wpdb->prefix . 'asenha_failed_logins';
199 // Check if the IP address has been used in a failed login attempt before, i.e. has it been recorded in the database?
200 $sql = $wpdb->prepare( "SELECT * FROM `" . $table_name . "` WHERE `ip_address` = %s", $asenha_limit_login['ip_address'] );
201 $result = $wpdb->get_results( $sql, ARRAY_A );
202 $result_count = count( $result );
203 // Update logged info for the IP address in the global variable
204 $asenha_limit_login['ip_address_log'] = $result;
205
206 if ( $result_count == 0 ) {
207 // IP address has not been recorded in the database.
208 $new_fail_count = 1;
209 $new_lockout_count = 0;
210 } else {
211 // IP address has been recorded in the database.
212 $new_fail_count = $result[0]['fail_count'] + 1;
213 $new_lockout_count = floor( ($result[0]['fail_count'] + 1) / $asenha_limit_login['login_fails_allowed'] );
214 }
215
216 // Get the URL where login failed, i.e. where brute force attack might be happening
217 // $login_url = ( ! empty( $_SERVER['HTTPS'] ) ? 'https://' : 'http://') . sanitize_text_field( $_SERVER['HTTP_HOST'] ) . sanitize_text_field( $_SERVER['REQUEST_URI'] );
218 // Time stamps
219 $unixtime = time();
220
221 if ( function_exists( 'wp_date' ) ) {
222 $datetime_wp = wp_date( 'Y-m-d H:i:s', $unixtime );
223 } else {
224 $datetime_wp = date_i18n( 'Y-m-d H:i:s', $unixtime );
225 }
226
227 $data = array(
228 'ip_address' => $asenha_limit_login['ip_address'],
229 'username' => $username,
230 'fail_count' => $new_fail_count,
231 'lockout_count' => $new_lockout_count,
232 'request_uri' => $asenha_limit_login['request_uri'],
233 'unixtime' => $unixtime,
234 'datetime_wp' => $datetime_wp,
235 'info' => '',
236 );
237 $data_format = array(
238 '%s',
239 // string
240 '%s',
241 // string
242 '%d',
243 // integer
244 '%d',
245 // integer
246 '%s',
247 // string
248 '%d',
249 // integer
250 '%s',
251 // string
252 '%s',
253 );
254
255 if ( $result_count == 0 ) {
256 // Insert into the database
257 $result = $wpdb->insert( $table_name, $data, $data_format );
258 } else {
259 // $options = get_option( ASENHA_SLUG_U );
260 // $login_fails_allowed = $options['login_fails_allowed'];
261 $fail_count = $result[0]['fail_count'];
262 $lockout_count = $result[0]['lockout_count'];
263 $last_fail_on = $result[0]['unixtime'];
264 $where = array(
265 'ip_address' => $asenha_limit_login['ip_address'],
266 );
267 $where_format = array( '%s' );
268 // Failed attempts have been recorded and fulfills lockout condition
269
270 if ( !empty($fail_count) && $fail_count % $asenha_limit_login['login_fails_allowed'] == 0 ) {
271 // Has reached max / gone beyond number of lockouts allowed?
272
273 if ( $lockout_count >= $asenha_limit_login['login_lockout_maxcount'] ) {
274 $asenha_limit_login['extended_lockout'] = true;
275 $lockout_period = $asenha_limit_login['extended_lockout_period'];
276 } else {
277 $asenha_limit_login['extended_lockout'] = false;
278 $lockout_period = $asenha_limit_login['default_lockout_period'];
279 }
280
281 $asenha_limit_login['lockout_period'] = $lockout_period;
282 // User/visitor is still within the lockout period
283
284 if ( time() - $last_fail_on <= $asenha_limit_login['lockout_period'] ) {
285 // Do nothing
286 } else {
287 if ( $lockout_count < $asenha_limit_login['login_lockout_maxcount'] ) {
288 // Update existing data in the database
289 $wpdb->update(
290 $table_name,
291 $data,
292 $where,
293 $data_format,
294 $where_format
295 );
296 }
297 }
298
299 } else {
300 // Update existing data in the database
301 $wpdb->update(
302 $table_name,
303 $data,
304 $where,
305 $data_format,
306 $where_format
307 );
308 }
309
310 }
311
312 }
313
314 /**
315 * Handle login errors
316 *
317 * @link https://developer.wordpress.org/reference/classes/wp_error/#methods
318 * @since 2.5.0
319 */
320 public function login_error_handler( $errors, $redirect_to )
321 {
322 global $asenha_limit_login ;
323
324 if ( is_wp_error( $errors ) ) {
325 $error_codes = $errors->get_error_codes();
326 foreach ( $error_codes as $error_code ) {
327
328 if ( $error_code == 'invalid_username' || $error_code == 'incorrect_password' ) {
329 // Remove default error messages that may give out valueable info to hackers
330 $errors->remove( 'invalid_username' );
331 // Outputs info that says username does not exist. May encourage login attempt with a different username instead.
332 $errors->remove( 'incorrect_password' );
333 // Outputs info that implies username exist. May encourage login attempt with a different password.
334 // Add a new error message that does not provide useful clues to hackers
335 $errors->add( 'invalid_username_or_incorrect_password', '<b>Error:</b> Invalid username or incorrect password.' );
336 // $errors->add( 'another_error_code', 'The error message.' );
337 }
338
339 }
340 }
341
342 return $errors;
343 }
344
345 /**
346 * Add login error message on top of the login form
347 *
348 * @since 2.5.0
349 */
350 public function add_failed_login_message( $message )
351 {
352 global $asenha_limit_login ;
353 if ( isset( $_REQUEST['failed_login'] ) && $_REQUEST['failed_login'] == 'true' ) {
354 if ( !$asenha_limit_login['within_lockout_period'] ) {
355 $message = '<div id="login_error"><b>Error:</b> Invalid username or incorrect password.</div>';
356 }
357 }
358 return $message;
359 }
360
361 /**
362 * Clear failed login attempts log after successful login
363 *
364 * @since 2.5.0
365 */
366 public function clear_failed_login_log()
367 {
368 global $wpdb, $asenha_limit_login ;
369 $table_name = $wpdb->prefix . 'asenha_failed_logins';
370 // Remove the DB log entry for the current IP address.
371 $where = array(
372 'ip_address' => $asenha_limit_login['ip_address'],
373 );
374 $where_format = array( '%s' );
375 $wpdb->delete( $table_name, $where, $where_format );
376 }
377
378 /**
379 * Obfuscate email address on the frontend using antispambot() native WP function
380 *
381 * @link: https://gist.github.com/eclarrrk/349360b52e8822b69cb6fc499722520f
382 * @since 5.5.0
383 */
384 public function obfuscate_string( $atts )
385 {
386 $atts = shortcode_atts( array(
387 'email' => '',
388 'display' => 'newline',
389 'link' => 'no',
390 'class' => '',
391 ), $atts );
392 $email = $atts['email'];
393 if ( !is_email( $email ) ) {
394 return;
395 }
396 $email_reversed = strrev( $email );
397 $email_rev_parts = explode( '@', $email_reversed );
398 $display = $atts['display'];
399
400 if ( 'newline' == $display ) {
401 $display_css = 'display:flex;justify-content:flex-end;';
402 } elseif ( 'inline' == $display ) {
403 $display_css = 'display:inline;';
404 }
405
406 $link = $atts['link'];
407 $class = $atts['class'];
408 return '<div style="display:inline;unicode-bidi:bidi-override;direction:rtl;" class="' . esc_attr( $class ) . '">' . esc_html( $email_rev_parts[0] ) . '<span style="display:none;">obfsctd</span>&#64;' . esc_html( $email_rev_parts[1] ) . '</div>';
409 }
410
411 /**
412 * If an author name is queried, decrypt it. Used by pre_get_posts action.
413 *
414 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/tags/4.0.2/inc/class-smart-user-slug-hider.php
415 * @since 2.1.0
416 */
417 function alter_author_query( $query )
418 {
419 // Check if it's a query for author data, and that 'author_name' is not empty
420 if ( $query->is_author() && $query->query_vars['author_name'] != '' ) {
421 // Check for character(s) representing a hexadecimal digit
422
423 if ( ctype_xdigit( $query->query_vars['author_name'] ) ) {
424 // Get user by the decrypted user ID
425 $user = get_user_by( 'id', $this->decrypt( $query->query_vars['author_name'] ) );
426
427 if ( $user ) {
428 $query->set( 'author_name', $user->user_nicename );
429 } else {
430 // No user found
431 $query->is_404 = true;
432 $query->is_author = false;
433 $query->is_archive = false;
434 }
435
436 } else {
437 // No hexadecimal digit detected in URL, i.e. someone is trying to access URL with original author slug
438 $query->is_404 = true;
439 $query->is_author = false;
440 $query->is_archive = false;
441 }
442
443 }
444 return;
445 }
446
447 /**
448 * Replace author slug in author link to encrypted value. Used by author_link filter.
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_link( $link, $user_id, $author_slug )
454 {
455 $encrypted_author_slug = $this->encrypt( $user_id );
456 return str_replace( '/' . $author_slug, '/' . $encrypted_author_slug, $link );
457 }
458
459 /**
460 * Replace author slug in REST API /users/ endpoint to encrypted value. Used by rest_prepare_user filter.
461 *
462 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/tags/4.0.2/inc/class-smart-user-slug-hider.php
463 * @since 2.1.0
464 */
465 function alter_json_users( $response, $user, $request )
466 {
467 $data = $response->get_data();
468 $data['slug'] = $this->encrypt( $data['id'] );
469 $response->set_data( $data );
470 return $response;
471 }
472
473 /**
474 * Helper function to return an encrypted user ID, which will then be used to replace the author slug.
475 *
476 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/trunk/inc/class-smart-user-slug-hider.php
477 * @since 2.1.0
478 */
479 private function encrypt( $user_id )
480 {
481 // Returns encrypted encrypted author slug from user ID, e.g. encrypt user ID 3 to author slug 4e3062d8c8626a14
482 return bin2hex( openssl_encrypt(
483 base_convert( $user_id, 10, 36 ),
484 'DES-EDE3',
485 md5( sanitize_text_field( $_SERVER['SERVER_ADDR'] ) . ASENHA_URL ),
486 OPENSSL_RAW_DATA
487 ) );
488 }
489
490 /**
491 * Helper function to decrypt an (encrypted) author slug and returns the user ID
492 *
493 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/trunk/inc/class-smart-user-slug-hider.php
494 * @since 2.1.0
495 */
496 private function decrypt( $encrypted_author_slug )
497 {
498 // Returns user ID, e.g. decrypts author slug 4e3062d8c8626a14 into user ID 3
499 return base_convert( openssl_decrypt(
500 pack( 'H*', $encrypted_author_slug ),
501 'DES-EDE3',
502 md5( sanitize_text_field( $_SERVER['SERVER_ADDR'] ) . ASENHA_URL ),
503 OPENSSL_RAW_DATA
504 ), 36, 10 );
505 }
506
507 /**
508 * Disable the XML-RPC component
509 *
510 * @since 2.2.0
511 */
512 public function maybe_disable_xmlrpc( $data )
513 {
514 http_response_code( 403 );
515 exit( 'You don\'t have permission to access this file.' );
516 }
517
518 }