PluginProbe
Patchstack – WordPress & Plugins Security / 2.3.6
Patchstack – WordPress & Plugins Security v2.3.6
2.3.7 trunk 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.17 2.1.18 2.1.19 2.1.2 2.1.20 2.1.21 2.1.22 2.1.23 2.1.24 2.1.25 2.1.3 2.1.4 2.1.5 2.1.6 All 49 releases
patchstack / includes / login.php

login.php in Patchstack – WordPress & Plugins Security 2.3.6, at includes/login.php

436 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // Do not allow the file to be called directly.
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 /**
9 * This class is used to alter anything related to the login page.
10 */
11 class P_Login extends P_Core {
12
13 /**
14 * Validated request or not.
15 *
16 * @param boolean
17 */
18 private $validated = false;
19
20 /**
21 * Add the actions required to interact with the login process.
22 *
23 * @param Patchstack $core
24 * @return void
25 */
26 public function __construct( $core ) {
27 parent::__construct( $core );
28
29 if ( $this->get_option( 'patchstack_license_free', 0 ) == 1 ) {
30 return;
31 }
32
33 add_action( 'login_init', [ $this, 'add_captcha' ] );
34 add_action( 'login_init', [ $this, 'check_ipban' ] );
35 add_action( 'login_head', [ $this, 'add_captcha' ] );
36 add_action( 'login_enqueue_scripts', [ $this, 'login_enqueue_scripts' ], 1 );
37
38 // WooCommerce related functionality.
39 if ( class_exists( 'WooCommerce' ) ) {
40 add_action( 'woocommerce_login_form_start', [ $this, 'add_captcha' ] );
41 add_action( 'woocommerce_register_form_start', [ $this, 'add_captcha' ] );
42 add_action( 'wp_authenticate', [ $this, 'add_captcha' ] );
43 add_action( 'woocommerce_before_lost_password_form', [ $this, 'add_captcha' ] );
44 }
45
46 // 2FA related actions.
47 if ( $this->get_option( 'patchstack_login_2fa', 0 ) ) {
48 add_action( 'login_form', [ $this, 'tfa_login_form' ] );
49 add_action( 'authenticate', [ $this, 'tfa_authenticate' ], 30, 3 );
50 add_action( 'profile_personal_options', [ $this, 'tfa_profile_personal_options' ] );
51 add_action( 'personal_options', [ $this, 'tfa_personal_options' ] );
52 add_action( 'edit_user_profile_update', [ $this, 'tfa_options_update' ] );
53 add_action( 'personal_options_update', [ $this, 'tfa_personal_options_update' ] );
54 add_action( 'admin_enqueue_scripts', [ $this, 'tfa_admin_enqueue_scripts' ] );
55
56 // WooCommerce related functionality.
57 if ( class_exists( 'WooCommerce' ) ) {
58 add_action( 'woocommerce_login_form', [ $this, 'tfa_woocommerce_login_form' ] );
59 add_action( 'woocommerce_edit_account_form', [ $this, 'tfa_woocommerce_profile_personal_options' ] );
60 add_action( 'woocommerce_save_account_details_errors', [ $this, 'tfa_woocommerce_validate_tfa'], 10, 2 );
61 }
62 }
63 }
64
65 /**
66 * Register the Google reCAPTCHA JavaScript for the login area.
67 *
68 * @return void
69 */
70 public function login_enqueue_scripts() {
71 if ( $this->get_option( 'patchstack_captcha_login_form', false ) && $this->get_option( 'patchstack_captcha_type' ) != 'v3' && $this->get_option( 'patchstack_captcha_type' ) != 'turnstile' ) {
72 wp_enqueue_script( 'patchstack_captcha', 'https://www.google.com/recaptcha/api.js' );
73 }
74 }
75
76 /**
77 * Add the 2FA code to the login form.
78 *
79 * @return void
80 */
81 public function tfa_login_form() {
82 require_once dirname( __FILE__ ) . '/views/2fa-login-form.php';
83 }
84
85 /**
86 * Add the 2FA code to the WooCommerce login form.
87 *
88 * @return void
89 */
90 public function tfa_woocommerce_login_form() {
91 require_once dirname( __FILE__ ) . '/views/2fa-login-form-woocommerce.php';
92 }
93
94 /**
95 * Check the 2FA code, if 2FA is enabled for the user.
96 *
97 * @param object $user
98 * @param string $username
99 * @param string $password
100 * @return object|WP_User|WP_Error
101 */
102 public function tfa_authenticate( $user, $username = '', $password = '' ) {
103 if ( ! isset( $user->ID ) ) {
104 return $user;
105 }
106
107 // If we have a valid user object, check to see if the user has 2FA enabled.
108 $enabled = get_user_option( 'webarx_2fa_enabled', $user->ID );
109 if ( empty( $enabled ) ) {
110 return $user;
111 }
112
113 // If enabled, check to see if the verification code is being sent.
114 if ( ! isset( $_POST['patchstack_2fa'] ) || ( isset( $_POST['patchstack_2fa'] ) && $_POST['patchstack_2fa'] == '' ) ) {
115 return new WP_Error( 'patchstack_2fa_empty_code', esc_attr__( 'Please enter the 2FA authentication code that is generated on your device.', 'patchstack' ) );
116 }
117
118 // Verify the code.
119 require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
120 $secret = $this->tfa_get_secret( $user );
121 if ( ! TokenAuth6238::verify( $secret, trim( $_POST['patchstack_2fa'] ) ) ) {
122 return new WP_Error( 'patchstack_2fa_invalid_code', esc_attr__( 'The 2FA authentication code you entered is invalid.', 'patchstack' ) );
123 }
124
125 return $user;
126 }
127
128 /**
129 * Show the 2FA disable field to the admin.
130 *
131 * @param object $user
132 * @return void
133 */
134 public function tfa_personal_options( $user ) {
135 require_once dirname( __FILE__ ) . '/views/2fa-profile-configuration-admin.php';
136 }
137
138 /**
139 * Show the 2FA fields.
140 *
141 * @param object $user
142 * @return void
143 */
144 public function tfa_profile_personal_options( $user ) {
145 $secret = $this->tfa_get_secret( $user );
146 require_once dirname( __FILE__ ) . '/views/2fa-profile-configuration.php';
147 }
148
149 /**
150 * Show the 2FA fields.
151 *
152 * @param object $user
153 * @return void
154 */
155 public function tfa_woocommerce_profile_personal_options( $user ) {
156 $secret = $this->tfa_get_secret( $user );
157 require_once dirname( __FILE__ ) . '/views/2fa-profile-configuration-woocommerce.php';
158 }
159
160 /**
161 * Validate the 2FA connection of a WooCommerce customer.
162 *
163 * @param mixed $errors
164 * @param mixed $user
165 * @return void
166 */
167 public function tfa_woocommerce_validate_tfa( &$errors, &$user ) {
168 // If we have a valid user object, check to see if the user has 2FA enabled.
169 $enabled = get_user_option( 'webarx_2fa_enabled', $user->ID );
170 if ( $enabled || ! isset ( $_POST['patchstack_2fa_enabled'] ) ) {
171 $this->tfa_personal_options_update( $user->ID );
172 return;
173 }
174
175 // Verify the code.
176 require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
177 $secret = $this->tfa_get_secret( $user );
178 if ( ! TokenAuth6238::verify( $secret, trim( $_POST['patchstack_2fa_secretkey_verification'] ) ) ) {
179 wc_add_notice( __( 'The 2FA authentication code you entered is invalid.', 'patchstack' ), 'error' );
180 return;
181 }
182
183 $this->tfa_personal_options_update( $user->ID );
184 }
185
186 /**
187 * Update the 2FA fields.
188 *
189 * @param integer $user_id
190 * @return void
191 */
192 public function tfa_personal_options_update( $user_id ) {
193 update_user_option( $user_id, 'webarx_2fa_enabled', ! empty( $_POST['patchstack_2fa_enabled'] ), true );
194 }
195
196 /**
197 * Update the 2FA fields on admin.
198 *
199 * @param integer $user_id
200 * @return void
201 */
202 public function tfa_options_update( $user_id ) {
203 update_user_option( $user_id, 'webarx_2fa_enabled', ! empty( $_POST['patchstack_2fa_enabled'] ), true );
204 }
205
206 /**
207 * Add the QRCode image generator JavaScript library.
208 *
209 * @return void
210 */
211 public function tfa_admin_enqueue_scripts() {
212 wp_register_script( 'patchstack_qrcode', $this->plugin->url . '/assets/js/qrcode.min.js', [], $this->plugin->version );
213 wp_enqueue_script( 'patchstack_qrcode' );
214 }
215
216 /**
217 * In case of legacy conditions, we encrypt the secret key and then store it.
218 *
219 * @return string
220 */
221 private function tfa_get_secret( $user ) {
222 $secret = get_user_option( 'webarx_2fa_secretkey', $user->ID );
223
224 // If user has no secret key set yet, generate one.
225 if ( empty( $secret ) || strlen( $secret ) === 16 ) {
226 if ( empty( $secret ) ) {
227 require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
228 $secret = TokenAuth6238::generateRandomClue();
229 }
230
231 $enc = $this->encrypt( $secret );
232 update_user_option( $user->ID, 'webarx_2fa_secretkey', $enc['cipher'], true );
233 update_user_option( $user->ID, 'webarx_2fa_secretkey_nonce', $enc['nonce'], true );
234 } else {
235 $nonce = get_user_option( 'webarx_2fa_secretkey_nonce', $user->ID );
236 $secret = $this->decrypt( $secret, $nonce );
237 }
238
239 return $secret;
240 }
241
242 /**
243 * Check if the IP address is banned from attempting to guess passwords.
244 *
245 * @return void
246 */
247 public function check_ipban() {
248 if ( is_user_logged_in() || ! $this->get_option( 'patchstack_block_bruteforce_ips', 0 ) ) {
249 return;
250 }
251
252 // Check if the users IP address is whitelisted.
253 $ip = $this->get_ip();
254 if ( $this->plugin->ban->is_ip_whitelisted( $ip ) ) {
255 return;
256 }
257
258 // Calculate block time.
259 $minutes = (int) $this->get_option( 'patchstack_anti_bruteforce_minutes', 30 );
260 $timeout = (int) $this->get_option( 'patchstack_anti_bruteforce_blocktime', 60 );
261 if ( empty( $minutes ) || empty( $timeout ) ) {
262 $time = 30 + 60;
263 } else {
264 $time = $minutes + $timeout;
265 }
266
267 // Check if X failed login attempts were made.
268 global $wpdb;
269 $results = $wpdb->get_results(
270 $wpdb->prepare( 'SELECT COUNT(*) AS numIps FROM ' . $wpdb->prefix . "patchstack_event_log WHERE ip = '%s' AND action = 'failed login' AND date >= ('" . current_time( 'mysql' ) . "' - INTERVAL %d MINUTE)", [ $ip, $time ] ),
271 OBJECT
272 );
273
274 // Determine the number of attempts.
275 if ( ! isset( $results, $results[0], $results[0]->numIps ) ) {
276 $num = 0;
277 } else {
278 $num = $results[0]->numIps;
279 }
280
281 // Block the user?
282 if ( $num >= $this->get_option( 'patchstack_anti_bruteforce_attempts', 10 ) ) {
283 $this->plugin->firewall_base->display_error_page( 24 );
284 }
285 }
286
287 /**
288 * Determine if we should inject reCAPTCHA into certain pages.
289 *
290 * @return void
291 */
292 public function add_captcha() {
293 switch ( $this->get_option( 'patchstack_captcha_type' ) ) {
294 case 'v2':
295 $public = $this->get_option( 'patchstack_captcha_public_key', '' );
296 $private = $this->get_option( 'patchstack_captcha_private_key', '' );
297 break;
298 case 'invisible':
299 $public = $this->get_option( 'patchstack_captcha_public_key_v3', '' );
300 $private = $this->get_option( 'patchstack_captcha_private_key_v3', '' );
301 break;
302 case 'v3':
303 $public = $this->get_option( 'patchstack_captcha_public_key_v3_new', '' );
304 $private = $this->get_option( 'patchstack_captcha_private_key_v3_new', '' );
305 break;
306 case 'turnstile':
307 $public = $this->get_option( 'patchstack_captcha_public_key_turnstile', '' );
308 $private = $this->get_option( 'patchstack_captcha_private_key_turnstile', '' );
309 break;
310 default:
311 return;
312 break;
313 }
314
315 // Make sure that the keys are set.
316 if ( $public == '' || $private == '' ) {
317 return;
318 }
319
320 // reCAPTCHA on the login page.
321 if ( $this->get_option( 'patchstack_captcha_login_form' ) ) {
322 add_filter( 'login_form', [ $this->plugin->hardening, 'captcha_display' ] );
323 add_filter( 'woocommerce_login_form', [ $this->plugin->hardening, 'captcha_display' ] );
324 add_filter( 'wp_authenticate_user', [ $this, 'login_captcha_check' ], 10, 2 );
325 }
326
327 // reCAPTCHA on the registration form.
328 if ( $this->get_option( 'patchstack_captcha_registration_form' ) ) {
329 add_action( 'register_form', [ $this->plugin->hardening, 'captcha_display' ] );
330 add_action( 'woocommerce_register_form', [ $this->plugin->hardening, 'captcha_display' ] );
331 add_action( 'registration_errors', [ $this, 'general_captcha_check' ] );
332 add_filter( 'woocommerce_process_registration_errors', [$this, 'general_captcha_check' ], 10, 1 );
333 }
334
335 // reCAPTCHA on the reset password form.
336 if ( $this->get_option( 'patchstack_captcha_reset_pwd_form' ) ) {
337 add_action( 'lostpassword_form', [ $this->plugin->hardening, 'captcha_display' ] );
338 add_action( 'woocommerce_lostpassword_form', [ $this->plugin->hardening, 'captcha_display' ] );
339 add_action( 'allow_password_reset', [ $this, 'general_captcha_check' ] );
340
341 // WooCommerce only.
342 if ( class_exists( 'WooCommerce' ) ) {
343 add_action( 'lostpassword_post', [ $this, 'general_captcha_check' ], 1, 1 );
344 }
345 }
346 }
347
348 /**
349 * Check reCAPTCHA upon login.
350 *
351 * @param string $user
352 * @param string $password
353 * @return WP_User|WP_Error
354 */
355 public function login_captcha_check( $user, $password ) {
356 if ( $this->validated ) {
357 return $user;
358 }
359
360 $result = $this->plugin->hardening->captcha_check();
361
362 if ( ! $result['response'] ) {
363 if ( $result['reason'] === 'ERROR_NO_KEYS' ) {
364 $this->validated = true;
365 return $user;
366 }
367 $error_message = sprintf( '<strong>%s</strong>: %s', 'Error', esc_attr__( 'You have entered an incorrect reCAPTCHA value.', 'patchstack' ) );
368
369 if ( $result['reason'] === 'VERIFICATION_FAILED' || $result['reason'] === 'RECAPTCHA_EMPTY_RESPONSE' ) {
370 wp_clear_auth_cookie();
371 return new WP_Error( 'patchstack_error', $error_message );
372 }
373
374 if ( isset( $_REQUEST['log'], $_REQUEST['pwd'] ) ) {
375 return new WP_Error( 'patchstack_error', $error_message );
376 }
377 } else {
378 $this->validated = true;
379 return $user;
380 }
381 }
382
383 /**
384 * Check reCAPTCHA upon login.
385 *
386 * @param string $user
387 * @param string $password
388 * @return WP_User|WP_Error
389 */
390 public function login_captcha_check_woocommerce( $error, $username, $password, $email ) {
391 if ( $this->validated ) {
392 return $error;
393 }
394
395 $result = $this->plugin->hardening->captcha_check();
396
397 if ( $result['response'] || $result['reason'] == 'ERROR_NO_KEYS' ) {
398 $this->validated = true;
399 return $error;
400 }
401
402 if ( ! is_wp_error( $error ) ) {
403 $error = new WP_Error();
404 }
405
406 $error->add( 'patchstack_error', 'ERROR' . ':&nbsp;' . esc_attr__( 'You have entered an incorrect reCAPTCHA value.', 'patchstack' ) );
407 return $error;
408 }
409
410 /**
411 * Captcha check for the register or lost password form.
412 *
413 * @param mixed|WP_Error $error
414 * @return WP_Error
415 */
416 public function general_captcha_check( $error ) {
417 if ( $this->validated ) {
418 return $error;
419 }
420
421 $result = $this->plugin->hardening->captcha_check();
422
423 if ( $result['response'] || $result['reason'] == 'ERROR_NO_KEYS' ) {
424 $this->validated = true;
425 return $error;
426 }
427
428 if ( ! is_wp_error( $error ) ) {
429 $error = new WP_Error();
430 }
431
432 $error->add( 'patchstack_error', 'ERROR' . ':&nbsp;' . esc_attr__( 'You have entered an incorrect reCAPTCHA value.', 'patchstack' ) );
433 return $error;
434 }
435 }
436