PluginProbe
Patchstack – WordPress & Plugins Security / 2.2.4
Patchstack – WordPress & Plugins Security v2.2.4
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
← All changes | includes/login.php +57 -30 2.1.32.2.4 View file →
@@ -18,25 +18,25 @@
18 18 */
19 19 public function __construct( $core ) {
20 20 parent::__construct( $core );
21 21
22 - if ( $this->get_option( 'patchstack_license_free', 0 ) == 1 ) {
22 + if ( $this->get_option( 'patchstack_license_free', 0 ) == 1 || $this->is_community() ) {
23 23 return;
24 24 }
25 25
26 - add_action( 'login_init', array( $this, 'add_captcha' ) );
27 - add_action( 'login_init', array( $this, 'check_ipban' ) );
28 - add_action( 'login_init', array( $this, 'check_logonhours' ) );
29 - add_action( 'login_head', array( $this, 'add_captcha' ) );
30 - add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue_scripts' ), 1 );
26 + add_action( 'login_init', [ $this, 'add_captcha' ] );
27 + add_action( 'login_init', [ $this, 'check_ipban' ] );
28 + add_action( 'login_init', [ $this, 'check_logonhours' ] );
29 + add_action( 'login_head', [ $this, 'add_captcha' ] );
30 + add_action( 'login_enqueue_scripts', [ $this, 'login_enqueue_scripts' ], 1 );
31 31
32 32 // 2FA related actions.
33 33 if ( $this->get_option( 'patchstack_login_2fa', 0 ) ) {
34 - add_action( 'login_form', array( $this, 'tfa_login_form' ) );
35 - add_action( 'authenticate', array( $this, 'tfa_authenticate' ), 30, 3 );
36 - add_action( 'profile_personal_options', array( $this, 'tfa_profile_personal_options' ) );
37 - add_action( 'personal_options_update', array( $this, 'tfa_personal_options_update' ) );
38 - add_action( 'admin_enqueue_scripts', array( $this, 'tfa_admin_enqueue_scripts' ) );
34 + add_action( 'login_form', [ $this, 'tfa_login_form' ] );
35 + add_action( 'authenticate', [ $this, 'tfa_authenticate' ], 30, 3 );
36 + add_action( 'profile_personal_options', [ $this, 'tfa_profile_personal_options' ] );
37 + add_action( 'personal_options_update', [ $this, 'tfa_personal_options_update' ] );
38 + add_action( 'admin_enqueue_scripts', [ $this, 'tfa_admin_enqueue_scripts' ] );
39 39 }
40 40 }
41 41
42 42 /**
@@ -73,9 +73,8 @@
73 73 }
74 74
75 75 // If we have a valid user object, check to see if the user has 2FA enabled.
76 76 $enabled = get_user_option( 'webarx_2fa_enabled', $user->ID );
77 - $secret = get_user_option( 'webarx_2fa_secretkey', $user->ID );
78 77 if ( empty( $enabled ) ) {
79 78 return $user;
80 79 }
81 80
@@ -85,8 +84,9 @@
85 84 }
86 85
87 86 // Verify the code.
88 87 require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
88 + $secret = $this->tfa_get_secret( $user );
89 89 if ( ! TokenAuth6238::verify( $secret, trim( $_POST['patchstack_2fa'] ) ) ) {
90 90 return new WP_Error( 'patchstack_2fa_invalid_code', __( 'The 2FA authentication code you entered is invalid.', 'patchstack' ) );
91 91 }
92 92
@@ -99,17 +99,9 @@
99 99 * @param object $user
100 100 * @return void
101 101 */
102 102 public function tfa_profile_personal_options( $user ) {
103 - $secret = get_user_option( 'webarx_2fa_secretkey', $user->ID );
104 -
105 - // If user has no secret key set yet, generate one.
106 - if ( empty( $secret ) ) {
107 - require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
108 - $secret = TokenAuth6238::generateRandomClue();
109 - update_user_option( $user->ID, 'webarx_2fa_secretkey', $secret, true );
110 - }
111 -
103 + $secret = $this->tfa_get_secret( $user );
112 104 require_once dirname( __FILE__ ) . '/views/2fa-profile-configuration.php';
113 105 }
114 106
115 107 /**
@@ -127,13 +119,39 @@
127 119 *
128 120 * @return void
129 121 */
130 122 public function tfa_admin_enqueue_scripts() {
131 - wp_register_script( 'patchstack_qrcode', $this->plugin->url . '/assets/js/qrcode.min.js', array(), $this->plugin->version );
123 + wp_register_script( 'patchstack_qrcode', $this->plugin->url . '/assets/js/qrcode.min.js', [], $this->plugin->version );
132 124 wp_enqueue_script( 'patchstack_qrcode' );
133 125 }
134 126
135 127 /**
128 + * In case of legacy conditions, we encrypt the secret key and then store it.
129 + *
130 + * @return string
131 + */
132 + private function tfa_get_secret( $user ) {
133 + $secret = get_user_option( 'webarx_2fa_secretkey', $user->ID );
134 +
135 + // If user has no secret key set yet, generate one.
136 + if ( empty( $secret ) || strlen( $secret ) === 16 ) {
137 + if ( empty( $secret ) ) {
138 + require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
139 + $secret = TokenAuth6238::generateRandomClue();
140 + }
141 +
142 + $enc = $this->encrypt( $secret );
143 + update_user_option( $user->ID, 'webarx_2fa_secretkey', $enc['cipher'], true );
144 + update_user_option( $user->ID, 'webarx_2fa_secretkey_nonce', $enc['nonce'], true );
145 + } else {
146 + $nonce = get_user_option( 'webarx_2fa_secretkey_nonce', $user->ID );
147 + $secret = $this->decrypt( $secret, $nonce );
148 + }
149 +
150 + return $secret;
151 + }
152 +
153 + /**
136 154 * Check if the IP address is banned from attempting to guess passwords.
137 155 *
138 156 * @return void
139 157 */
@@ -147,12 +165,21 @@
147 165 if ( $this->plugin->ban->is_ip_whitelisted( $ip ) ) {
148 166 return;
149 167 }
150 168
169 + // Calculate block time.
170 + $minutes = (int) $this->get_option( 'patchstack_anti_bruteforce_minutes', 30 );
171 + $timeout = (int) $this->get_option( 'patchstack_anti_bruteforce_blocktime', 60 );
172 + if ( empty( $minutes ) || empty( $timeout ) ) {
173 + $time = 30 + 60;
174 + } else {
175 + $time = $minutes + $timeout;
176 + }
177 +
151 178 // Check if X failed login attempts were made.
152 179 global $wpdb;
153 180 $results = $wpdb->get_results(
154 - $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)", array( $ip, ( $this->get_option( 'patchstack_anti_bruteforce_blocktime', 60 ) + $this->get_option( 'patchstack_anti_bruteforce_minutes', 5 ) ) ) ),
181 + $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 ] ),
155 182 OBJECT
156 183 );
157 184
158 185 // Determine the number of attempts.
@@ -163,9 +190,9 @@
163 190 }
164 191
165 192 // Block the user?
166 193 if ( $num >= $this->get_option( 'patchstack_anti_bruteforce_attempts', 10 ) ) {
167 - $this->plugin->firewall_base->display_error_page( 22 );
194 + $this->plugin->firewall_base->display_error_page( 24 );
168 195 }
169 196 }
170 197
171 198 /**
@@ -253,22 +280,22 @@
253 280 }
254 281
255 282 // reCAPTCHA on the login page.
256 283 if ( $this->get_option( 'patchstack_captcha_login_form' ) ) {
257 - add_filter( 'login_form', array( $this->plugin->hardening, 'captcha_display' ) );
258 - add_filter( 'wp_authenticate_user', array( $this, 'login_captcha_check' ), 10, 2 );
284 + add_filter( 'login_form', [ $this->plugin->hardening, 'captcha_display' ] );
285 + add_filter( 'wp_authenticate_user', [ $this, 'login_captcha_check' ], 10, 2 );
259 286 }
260 287
261 288 // reCAPTCHA on the registration form.
262 289 if ( $this->get_option( 'patchstack_captcha_registration_form' ) ) {
263 - add_action( 'register_form', array( $this->plugin->hardening, 'captcha_display' ) );
264 - add_action( 'registration_errors', array( $this, 'general_captcha_check' ) );
290 + add_action( 'register_form', [ $this->plugin->hardening, 'captcha_display' ] );
291 + add_action( 'registration_errors', [ $this, 'general_captcha_check' ] );
265 292 }
266 293
267 294 // reCAPTCHA on the reset password form.
268 295 if ( $this->get_option( 'patchstack_captcha_reset_pwd_form' ) ) {
269 - add_action( 'lostpassword_form', array( $this->plugin->hardening, 'captcha_display' ) );
270 - add_action( 'allow_password_reset', array( $this, 'general_captcha_check' ) );
296 + add_action( 'lostpassword_form', [ $this->plugin->hardening, 'captcha_display' ] );
297 + add_action( 'allow_password_reset', [ $this, 'general_captcha_check' ] );
271 298 }
272 299 }
273 300
274 301 /**