PluginProbe ʕ •ᴥ•ʔ
Admin and Site Enhancements (ASE) / 6.1.0
Admin and Site Enhancements (ASE) v6.1.0
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 2 years ago class-admin-interface.php 2 years ago class-common-methods.php 2 years ago class-content-management.php 2 years ago class-custom-code.php 2 years ago class-deactivation.php 2 years ago class-disable-components.php 2 years ago class-login-logout.php 2 years ago class-optimizations.php 2 years ago class-security.php 2 years ago class-settings-fields-render.php 2 years ago class-settings-sanitization.php 2 years ago class-settings-sections-fields.php 2 years ago class-utilities.php 2 years ago
class-security.php
531 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 // Reverse email address characters if not in Firefox, which has bug related to unicode-bidi CSS property
397 $http_user_agent = $_SERVER['HTTP_USER_AGENT'];
398
399 if ( false !== stripos( sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ), 'firefox' ) ) {
400 // Do nothing. Do not reverse characters.
401 $email_reversed = $email;
402 $email_rev_parts = explode( '@', $email_reversed );
403 $email_rev_parts = array( $email_rev_parts[0], $email_rev_parts[1] );
404 $css_bidi_styles = '';
405 } else {
406 $email_reversed = strrev( $email );
407 $email_rev_parts = explode( '@', $email_reversed );
408 $css_bidi_styles = 'unicode-bidi:bidi-override;';
409 }
410
411 $display = $atts['display'];
412
413 if ( 'newline' == $display ) {
414 $display_css = 'display:flex;justify-content:flex-end;';
415 } elseif ( 'inline' == $display ) {
416 $display_css = 'display:inline;';
417 }
418
419 $link = $atts['link'];
420 $class = $atts['class'];
421 return '<div style="display:inline;' . esc_attr( $css_bidi_styles ) . ';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>';
422 }
423
424 /**
425 * If an author name is queried, decrypt it. Used by pre_get_posts action.
426 *
427 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/tags/4.0.2/inc/class-smart-user-slug-hider.php
428 * @since 2.1.0
429 */
430 function alter_author_query( $query )
431 {
432 // Check if it's a query for author data, and that 'author_name' is not empty
433 if ( $query->is_author() && $query->query_vars['author_name'] != '' ) {
434 // Check for character(s) representing a hexadecimal digit
435
436 if ( ctype_xdigit( $query->query_vars['author_name'] ) ) {
437 // Get user by the decrypted user ID
438 $user = get_user_by( 'id', $this->decrypt( $query->query_vars['author_name'] ) );
439
440 if ( $user ) {
441 $query->set( 'author_name', $user->user_nicename );
442 } else {
443 // No user found
444 $query->is_404 = true;
445 $query->is_author = false;
446 $query->is_archive = false;
447 }
448
449 } else {
450 // No hexadecimal digit detected in URL, i.e. someone is trying to access URL with original author slug
451 $query->is_404 = true;
452 $query->is_author = false;
453 $query->is_archive = false;
454 }
455
456 }
457 return;
458 }
459
460 /**
461 * Replace author slug in author link to encrypted value. Used by author_link filter.
462 *
463 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/tags/4.0.2/inc/class-smart-user-slug-hider.php
464 * @since 2.1.0
465 */
466 function alter_author_link( $link, $user_id, $author_slug )
467 {
468 $encrypted_author_slug = $this->encrypt( $user_id );
469 return str_replace( '/' . $author_slug, '/' . $encrypted_author_slug, $link );
470 }
471
472 /**
473 * Replace author slug in REST API /users/ endpoint to encrypted value. Used by rest_prepare_user filter.
474 *
475 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/tags/4.0.2/inc/class-smart-user-slug-hider.php
476 * @since 2.1.0
477 */
478 function alter_json_users( $response, $user, $request )
479 {
480 $data = $response->get_data();
481 $data['slug'] = $this->encrypt( $data['id'] );
482 $response->set_data( $data );
483 return $response;
484 }
485
486 /**
487 * Helper function to return an encrypted user ID, which will then be used to replace the author slug.
488 *
489 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/trunk/inc/class-smart-user-slug-hider.php
490 * @since 2.1.0
491 */
492 private function encrypt( $user_id )
493 {
494 // Returns encrypted encrypted author slug from user ID, e.g. encrypt user ID 3 to author slug 4e3062d8c8626a14
495 return bin2hex( openssl_encrypt(
496 base_convert( $user_id, 10, 36 ),
497 'DES-EDE3',
498 md5( ASENHA_URL ),
499 OPENSSL_RAW_DATA
500 ) );
501 }
502
503 /**
504 * Helper function to decrypt an (encrypted) author slug and returns the user ID
505 *
506 * @link https://plugins.trac.wordpress.org/browser/smart-user-slug-hider/trunk/inc/class-smart-user-slug-hider.php
507 * @since 2.1.0
508 */
509 private function decrypt( $encrypted_author_slug )
510 {
511 // Returns user ID, e.g. decrypts author slug 4e3062d8c8626a14 into user ID 3
512 return base_convert( openssl_decrypt(
513 pack( 'H*', $encrypted_author_slug ),
514 'DES-EDE3',
515 md5( ASENHA_URL ),
516 OPENSSL_RAW_DATA
517 ), 36, 10 );
518 }
519
520 /**
521 * Disable the XML-RPC component
522 *
523 * @since 2.2.0
524 */
525 public function maybe_disable_xmlrpc( $data )
526 {
527 http_response_code( 403 );
528 exit( 'You don\'t have permission to access this file.' );
529 }
530
531 }