PluginProbe
Patchstack – WordPress & Plugins Security / 2.3.3
Patchstack – WordPress & Plugins Security v2.3.3
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.3, 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_filter( 'woocommerce_process_registration_errors', [$this, 'general_captcha_check' ], 10, 1 );
44 add_action( 'woocommerce_before_lost_password_form', [ $this, 'add_captcha' ] );
45 }
46
47 // 2FA related actions.
48 if ( $this->get_option( 'patchstack_login_2fa', 0 ) ) {
49 add_action( 'login_form', [ $this, 'tfa_login_form' ] );
50 add_action( 'authenticate', [ $this, 'tfa_authenticate' ], 30, 3 );
51 add_action( 'profile_personal_options', [ $this, 'tfa_profile_personal_options' ] );
52 add_action( 'personal_options', [ $this, 'tfa_personal_options' ] );
53 add_action( 'edit_user_profile_update', [ $this, 'tfa_options_update' ] );
54 add_action( 'personal_options_update', [ $this, 'tfa_personal_options_update' ] );
55 add_action( 'admin_enqueue_scripts', [ $this, 'tfa_admin_enqueue_scripts' ] );
56
57 // WooCommerce related functionality.
58 if ( class_exists( 'WooCommerce' ) ) {
59 add_action( 'woocommerce_login_form', [ $this, 'tfa_woocommerce_login_form' ] );
60 add_action( 'woocommerce_edit_account_form', [ $this, 'tfa_woocommerce_profile_personal_options' ] );
61 add_action( 'woocommerce_save_account_details_errors', [ $this, 'tfa_woocommerce_validate_tfa'], 10, 2 );
62 }
63 }
64 }
65
66 /**
67 * Register the Google reCAPTCHA JavaScript for the login area.
68 *
69 * @return void
70 */
71 public function login_enqueue_scripts() {
72 if ( $this->get_option( 'patchstack_captcha_login_form', false ) && $this->get_option( 'patchstack_captcha_type' ) != 'v3' && $this->get_option( 'patchstack_captcha_type' ) != 'turnstile' ) {
73 wp_enqueue_script( 'patchstack_captcha', 'https://www.google.com/recaptcha/api.js' );
74 }
75 }
76
77 /**
78 * Add the 2FA code to the login form.
79 *
80 * @return void
81 */
82 public function tfa_login_form() {
83 require_once dirname( __FILE__ ) . '/views/2fa-login-form.php';
84 }
85
86 /**
87 * Add the 2FA code to the WooCommerce login form.
88 *
89 * @return void
90 */
91 public function tfa_woocommerce_login_form() {
92 require_once dirname( __FILE__ ) . '/views/2fa-login-form-woocommerce.php';
93 }
94
95 /**
96 * Check the 2FA code, if 2FA is enabled for the user.
97 *
98 * @param object $user
99 * @param string $username
100 * @param string $password
101 * @return object|WP_User|WP_Error
102 */
103 public function tfa_authenticate( $user, $username = '', $password = '' ) {
104 if ( ! isset( $user->ID ) ) {
105 return $user;
106 }
107
108 // If we have a valid user object, check to see if the user has 2FA enabled.
109 $enabled = get_user_option( 'webarx_2fa_enabled', $user->ID );
110 if ( empty( $enabled ) ) {
111 return $user;
112 }
113
114 // If enabled, check to see if the verification code is being sent.
115 if ( ! isset( $_POST['patchstack_2fa'] ) || ( isset( $_POST['patchstack_2fa'] ) && $_POST['patchstack_2fa'] == '' ) ) {
116 return new WP_Error( 'patchstack_2fa_empty_code', esc_attr__( 'Please enter the 2FA authentication code that is generated on your device.', 'patchstack' ) );
117 }
118
119 // Verify the code.
120 require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
121 $secret = $this->tfa_get_secret( $user );
122 if ( ! TokenAuth6238::verify( $secret, trim( $_POST['patchstack_2fa'] ) ) ) {
123 return new WP_Error( 'patchstack_2fa_invalid_code', esc_attr__( 'The 2FA authentication code you entered is invalid.', 'patchstack' ) );
124 }
125
126 return $user;
127 }
128
129 /**
130 * Show the 2FA disable field to the admin.
131 *
132 * @param object $user
133 * @return void
134 */
135 public function tfa_personal_options( $user ) {
136 require_once dirname( __FILE__ ) . '/views/2fa-profile-configuration-admin.php';
137 }
138
139 /**
140 * Show the 2FA fields.
141 *
142 * @param object $user
143 * @return void
144 */
145 public function tfa_profile_personal_options( $user ) {
146 $secret = $this->tfa_get_secret( $user );
147 require_once dirname( __FILE__ ) . '/views/2fa-profile-configuration.php';
148 }
149
150 /**
151 * Show the 2FA fields.
152 *
153 * @param object $user
154 * @return void
155 */
156 public function tfa_woocommerce_profile_personal_options( $user ) {
157 $secret = $this->tfa_get_secret( $user );
158 require_once dirname( __FILE__ ) . '/views/2fa-profile-configuration-woocommerce.php';
159 }
160
161 /**
162 * Validate the 2FA connection of a WooCommerce customer.
163 *
164 * @param mixed $errors
165 * @param mixed $user
166 * @return void
167 */
168 public function tfa_woocommerce_validate_tfa( &$errors, &$user ) {
169 // If we have a valid user object, check to see if the user has 2FA enabled.
170 $enabled = get_user_option( 'webarx_2fa_enabled', $user->ID );
171 if ( $enabled || ! isset ( $_POST['patchstack_2fa_enabled'] ) ) {
172 $this->tfa_personal_options_update( $user->ID );
173 return;
174 }
175
176 // Verify the code.
177 require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
178 $secret = $this->tfa_get_secret( $user );
179 if ( ! TokenAuth6238::verify( $secret, trim( $_POST['patchstack_2fa_secretkey_verification'] ) ) ) {
180 wc_add_notice( __( 'The 2FA authentication code you entered is invalid.', 'patchstack' ), 'error' );
181 return;
182 }
183
184 $this->tfa_personal_options_update( $user->ID );
185 }
186
187 /**
188 * Update the 2FA fields.
189 *
190 * @param integer $user_id
191 * @return void
192 */
193 public function tfa_personal_options_update( $user_id ) {
194 update_user_option( $user_id, 'webarx_2fa_enabled', ! empty( $_POST['patchstack_2fa_enabled'] ), true );
195 }
196
197 /**
198 * Update the 2FA fields on admin.
199 *
200 * @param integer $user_id
201 * @return void
202 */
203 public function tfa_options_update( $user_id ) {
204 update_user_option( $user_id, 'webarx_2fa_enabled', ! empty( $_POST['patchstack_2fa_enabled'] ), true );
205 }
206
207 /**
208 * Add the QRCode image generator JavaScript library.
209 *
210 * @return void
211 */
212 public function tfa_admin_enqueue_scripts() {
213 wp_register_script( 'patchstack_qrcode', $this->plugin->url . '/assets/js/qrcode.min.js', [], $this->plugin->version );
214 wp_enqueue_script( 'patchstack_qrcode' );
215 }
216
217 /**
218 * In case of legacy conditions, we encrypt the secret key and then store it.
219 *
220 * @return string
221 */
222 private function tfa_get_secret( $user ) {
223 $secret = get_user_option( 'webarx_2fa_secretkey', $user->ID );
224
225 // If user has no secret key set yet, generate one.
226 if ( empty( $secret ) || strlen( $secret ) === 16 ) {
227 if ( empty( $secret ) ) {
228 require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
229 $secret = TokenAuth6238::generateRandomClue();
230 }
231
232 $enc = $this->encrypt( $secret );
233 update_user_option( $user->ID, 'webarx_2fa_secretkey', $enc['cipher'], true );
234 update_user_option( $user->ID, 'webarx_2fa_secretkey_nonce', $enc['nonce'], true );
235 } else {
236 $nonce = get_user_option( 'webarx_2fa_secretkey_nonce', $user->ID );
237 $secret = $this->decrypt( $secret, $nonce );
238 }
239
240 return $secret;
241 }
242
243 /**
244 * Check if the IP address is banned from attempting to guess passwords.
245 *
246 * @return void
247 */
248 public function check_ipban() {
249 if ( is_user_logged_in() || ! $this->get_option( 'patchstack_block_bruteforce_ips', 0 ) ) {
250 return;
251 }
252
253 // Check if the users IP address is whitelisted.
254 $ip = $this->get_ip();
255 if ( $this->plugin->ban->is_ip_whitelisted( $ip ) ) {
256 return;
257 }
258
259 // Calculate block time.
260 $minutes = (int) $this->get_option( 'patchstack_anti_bruteforce_minutes', 30 );
261 $timeout = (int) $this->get_option( 'patchstack_anti_bruteforce_blocktime', 60 );
262 if ( empty( $minutes ) || empty( $timeout ) ) {
263 $time = 30 + 60;
264 } else {
265 $time = $minutes + $timeout;
266 }
267
268 // Check if X failed login attempts were made.
269 global $wpdb;
270 $results = $wpdb->get_results(
271 $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 ] ),
272 OBJECT
273 );
274
275 // Determine the number of attempts.
276 if ( ! isset( $results, $results[0], $results[0]->numIps ) ) {
277 $num = 0;
278 } else {
279 $num = $results[0]->numIps;
280 }
281
282 // Block the user?
283 if ( $num >= $this->get_option( 'patchstack_anti_bruteforce_attempts', 10 ) ) {
284 $this->plugin->firewall_base->display_error_page( 24 );
285 }
286 }
287
288 /**
289 * Determine if we should inject reCAPTCHA into certain pages.
290 *
291 * @return void
292 */
293 public function add_captcha() {
294 switch ( $this->get_option( 'patchstack_captcha_type' ) ) {
295 case 'v2':
296 $public = $this->get_option( 'patchstack_captcha_public_key', '' );
297 $private = $this->get_option( 'patchstack_captcha_private_key', '' );
298 break;
299 case 'invisible':
300 $public = $this->get_option( 'patchstack_captcha_public_key_v3', '' );
301 $private = $this->get_option( 'patchstack_captcha_private_key_v3', '' );
302 break;
303 case 'v3':
304 $public = $this->get_option( 'patchstack_captcha_public_key_v3_new', '' );
305 $private = $this->get_option( 'patchstack_captcha_private_key_v3_new', '' );
306 break;
307 case 'turnstile':
308 $public = $this->get_option( 'patchstack_captcha_public_key_turnstile', '' );
309 $private = $this->get_option( 'patchstack_captcha_private_key_turnstile', '' );
310 break;
311 default:
312 return;
313 break;
314 }
315
316 // Make sure that the keys are set.
317 if ( $public == '' || $private == '' ) {
318 return;
319 }
320
321 // reCAPTCHA on the login page.
322 if ( $this->get_option( 'patchstack_captcha_login_form' ) ) {
323 add_filter( 'login_form', [ $this->plugin->hardening, 'captcha_display' ] );
324 add_filter( 'woocommerce_login_form', [ $this->plugin->hardening, 'captcha_display' ] );
325 add_filter( 'wp_authenticate_user', [ $this, 'login_captcha_check' ], 10, 2 );
326 }
327
328 // reCAPTCHA on the registration form.
329 if ( $this->get_option( 'patchstack_captcha_registration_form' ) ) {
330 add_action( 'register_form', [ $this->plugin->hardening, 'captcha_display' ] );
331 add_action( 'woocommerce_register_form', [ $this->plugin->hardening, 'captcha_display' ] );
332 add_action( 'registration_errors', [ $this, 'general_captcha_check' ] );
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