PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
← All changes | src/component/security-tweaks/class-security-key.php +163 -79 5.8.0 → 6.3.0 View file →
@@ -7,17 +7,16 @@
7 7
8 8 namespace WP_Defender\Component\Security_Tweaks;
9 9
10 10 use WP_Error;
11 -use Exception;
12 11 use Throwable;
13 12 use Calotes\Component\Response;
13 +use WP_Defender\Traits\File_Operations;
14 14 use WP_Defender\Model\Setting\Mask_Login;
15 15 use WP_Defender\Model\Setting\Security_Tweaks;
16 16 use WP_Defender\Traits\Security_Tweaks_Option;
17 17 use WP_Defender\Component\Network_Cron_Manager;
18 18 use WP_Defender\Component\Security_Tweak;
19 -use WP_Filesystem_Base;
20 19
21 20 /**
22 21 * Class Security_Key
23 22 */
@@ -22,8 +21,9 @@
22 21 * Class Security_Key
23 22 */
24 23 class Security_Key extends Abstract_Security_Tweaks implements Security_Key_Const_Interface {
25 24
25 + use File_Operations;
26 26 use Security_Tweaks_Option;
27 27
28 28 public const REGENERATE_SALT_NEXT_RUN_OPTION = 'wpdef_regereate_salt_next_run';
29 29
@@ -37,33 +37,33 @@
37 37 * Default reminder duration for regenerating security keys.
38 38 *
39 39 * @var string
40 40 */
41 - public $default_days = '60 days';
41 + public string $default_days = '60 days';
42 42 /**
43 43 * Duration after which a reminder for regeneration is triggered.
44 44 *
45 - * @var null
45 + * @var string
46 46 */
47 - public $reminder_duration = null;
47 + public string $reminder_duration = '60 days';
48 48 /**
49 49 * Date of the last reminder.
50 50 *
51 - * @var null
51 + * @var int
52 52 */
53 - public $reminder_date = null;
53 + public int $reminder_date = 0;
54 54 /**
55 55 * Timestamp of the last modification of security keys.
56 56 *
57 - * @var null
57 + * @var int
58 58 */
59 - public $last_modified = null;
59 + public int $last_modified = 0;
60 60 /**
61 61 * Path to the wp-config.php file.
62 62 *
63 63 * @var string
64 64 */
65 - public $file;
65 + public string $file;
66 66
67 67 /**
68 68 * Flag to automate the security key/salt generation.
69 69 *
@@ -68,11 +68,18 @@
68 68 * Flag to automate the security key/salt generation.
69 69 *
70 70 * @var bool
71 71 */
72 - private $is_autogenerate_keys = true;
72 + private bool $is_autogenerate_keys = true;
73 73
74 74 /**
75 + * Flag set when the user explicitly reverts this tweak via the Edit button.
76 + *
77 + * @var bool
78 + */
79 + private bool $is_reverted = false;
80 +
81 + /**
75 82 * Constructor for Security_Key.
76 83 */
77 84 public function __construct() {
78 85 $this->file = defender_wp_config_path();
@@ -85,8 +92,12 @@
85 92 *
86 93 * @return bool|void
87 94 */
88 95 public function check() {
96 + if ( $this->is_reverted ) {
97 + return false;
98 + }
99 +
89 100 if ( ! $this->is_salts_exist() ) {
90 101 return false;
91 102 }
92 103
@@ -92,9 +103,9 @@
92 103
93 104 if ( $this->last_modified ) {
94 105 $reminder_date = strtotime( '+' . $this->reminder_duration, $this->last_modified );
95 106
96 - return $reminder_date > time();
107 + return $reminder_date > defender_get_current_time();
97 108 }
98 109 }
99 110
100 111 /**
@@ -103,10 +114,11 @@
103 114 * @return void
104 115 */
105 116 private function get_options(): void {
106 117 $options = get_site_option( 'defender_security_tweaks_' . $this->slug );
107 - $this->reminder_date = isset( $options['reminder_date'] ) ? $options['reminder_date'] : null;
118 + $this->reminder_date = $options['reminder_date'] ?? 0;
108 119 $this->reminder_duration = isset( $options['reminder_duration'] ) && '' !== $options['reminder_duration'] ? $options['reminder_duration'] : $this->default_days;
120 + $this->is_reverted = isset( $options['is_reverted'] ) && true === (bool) $options['is_reverted'];
109 121
110 122 $last_modified = $this->get_wp_config_last_modified_time();
111 123 if ( false === $last_modified ) {
112 124 $last_modified = $options['last_modified'] ?? null;
@@ -114,11 +126,9 @@
114 126 $last_modified = $options['last_modified'];
115 127 }
116 128 $this->last_modified = $last_modified;
117 129
118 - $this->is_autogenerate_keys = isset( $options['is_autogenerate_keys'] )
119 - ? (bool) $options['is_autogenerate_keys']
120 - : true;
130 + $this->is_autogenerate_keys = isset( $options['is_autogenerate_keys'] );
121 131 }
122 132
123 133 /**
124 134 * Here is the code for processing. If the return is true or Response, we add it to resolve list. WP_Error if any error.
@@ -167,8 +177,13 @@
167 177 *
168 178 * @return bool
169 179 */
170 180 public function revert(): bool {
181 + $values = get_site_option( 'defender_security_tweaks_' . $this->slug, array() );
182 + $values['is_reverted'] = true;
183 + update_site_option( 'defender_security_tweaks_' . $this->slug, $values );
184 + $this->is_reverted = true;
185 +
171 186 return true;
172 187 }
173 188
174 189 /**
@@ -180,27 +195,82 @@
180 195 return true;
181 196 }
182 197
183 198 /**
199 + * Parse and validate salt definitions string.
200 + *
201 + * @param string $body API response body.
202 + * @param array $constants Expected constant keys.
203 + *
204 + * @return array|false
205 + */
206 + private function parse_and_validate_salts( string $body, array $constants ): array|false {
207 + $raw_salts = explode( "\n", $body );
208 + $salts = array();
209 + $values = array();
210 + $index = 0;
211 +
212 + foreach ( $raw_salts as $salt ) {
213 + $salt = trim( $salt );
214 + if ( '' === $salt ) {
215 + continue;
216 + }
217 +
218 + $salt = stripslashes( $salt );
219 + $expected_const = $constants[ $index ] ?? null;
220 +
221 + if (
222 + null === $expected_const ||
223 + 0 !== strpos( $salt, 'define(' ) ||
224 + 1 !== preg_match( '/^define\(\s*[\'"]' . preg_quote( $expected_const, '/' ) . '[\'"]\s*,\s*[\'"](.*?)[\'"]\s*\)\s*;$/s', $salt, $matches )
225 + ) {
226 + return false;
227 + }
228 +
229 + $val = $matches[1];
230 + if ( 'put your unique phrase here' === $val || strlen( $val ) < 64 || isset( $values[ $val ] ) ) {
231 + return false;
232 + }
233 +
234 + $values[ $val ] = true;
235 + $salts[] = $salt;
236 + ++$index;
237 + }
238 +
239 + if ( count( $salts ) !== count( $constants ) ) {
240 + return false;
241 + }
242 +
243 + return $salts;
244 + }
245 +
246 + /**
184 247 * Get salts to be placed in wp-config.php.
185 248 *
186 249 * @return array|WP_Error
187 250 */
188 - private function get_salts() {
189 - $response = wp_safe_remote_get( 'https://api.wordpress.org/secret-key/1.1/salt/' );
251 + private function get_salts(): WP_Error|array {
252 + $constants = $this->get_constants();
253 + $max_attempts = 3;
254 + $attempt = 0;
190 255
191 - if ( is_wp_error( $response ) ) {
192 - return new WP_Error(
193 - 'defender_salts_not_found',
194 - esc_html__( 'Unable to generate salts. Please try again.', 'defender-security' )
195 - );
256 + while ( $attempt < $max_attempts ) {
257 + ++$attempt;
258 + $response = wp_safe_remote_get( 'https://api.wordpress.org/secret-key/1.1/salt/' );
259 +
260 + if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
261 + continue;
262 + }
263 +
264 + $salts = $this->parse_and_validate_salts( wp_remote_retrieve_body( $response ), $constants );
265 + if ( false !== $salts ) {
266 + return $salts;
267 + }
196 268 }
197 269
198 - return array_filter(
199 - explode( "\n", wp_remote_retrieve_body( $response ) ),
200 - function ( $value ) {
201 - return '' !== trim( $value );
202 - }
270 + return new WP_Error(
271 + 'defender_salts_not_found',
272 + esc_html__( 'Unable to generate salts. Please try again.', 'defender-security' )
203 273 );
204 274 }
205 275
206 276 /**
@@ -207,14 +277,12 @@
207 277 * Get how long the wp-config file is last updated.
208 278 *
209 279 * @return int|string
210 280 */
211 - private function get_last_modified_days() {
212 - $current_time = time();
281 + private function get_last_modified_days(): int|string {
282 + $current_time = defender_get_current_time();
213 283 $days_ago = ( $current_time - $this->last_modified ) / DAY_IN_SECONDS;
214 - $days_ago = $days_ago ? (int) round( $days_ago ) : 'unknown';
215 -
216 - return $days_ago ?? 1;
284 + return $days_ago ? (int) round( $days_ago ) : 'unknown';
217 285 }
218 286
219 287 /**
220 288 * Get all the constants.
@@ -239,9 +307,9 @@
239 307 *
240 308 * @return string
241 309 */
242 310 public function get_label(): string {
243 - return esc_html__( 'Update old security keys', 'defender-security' );
311 + return esc_html__( 'Update security keys', 'defender-security' );
244 312 }
245 313
246 314 /**
247 315 * Get the error reason.
@@ -248,30 +316,40 @@
248 316 *
249 317 * @return string
250 318 */
251 319 public function get_error_reason(): string {
252 - $get_last_modified_days = $this->get_last_modified_days();
253 -
254 - if ( 'unknown' === $get_last_modified_days ) {
255 - $error_message = esc_html__(
256 - 'We can\'t tell how old your security keys are, perhaps it\'s time to update them?',
257 - 'defender-security'
320 + if ( ! $this->is_salts_exist() ) {
321 + return wp_kses(
322 + __( '<strong>Your security keys are outdated</strong> <br /> and one or more security salts are missing in wp-config.php.', 'defender-security' ),
323 + array(
324 + 'strong' => array(),
325 + 'br' => array(),
326 + )
258 327 );
259 328 }
260 - if ( ! $this->is_salts_exist() ) {
261 - $error_message = esc_html__(
262 - 'One or more security salts aren\'t defined in wp-config.php. Time to regenerate them!',
263 - 'defender-security'
329 +
330 + $days = $this->get_last_modified_days();
331 + if ( 'unknown' === $days ) {
332 + return wp_kses(
333 + __( '<strong>Your security keys are outdated</strong> <br /> and we can\'t tell how old they are.', 'defender-security' ),
334 + array(
335 + 'strong' => array(),
336 + 'br' => array(),
337 + )
264 338 );
265 - } else {
266 - $error_message = sprintf(
267 - /* translators: %s: number of days */
268 - esc_html__( 'Your current security keys are %s days old. Time to update them!', 'defender-security' ),
269 - $get_last_modified_days
270 - );
271 339 }
272 340
273 - return $error_message;
341 + return wp_kses(
342 + sprintf(
343 + /* translators: %1$s: opening strong tag, %2$s: closing strong tag */
344 + __( '%1$sYour security keys are outdated.%2$s', 'defender-security' ),
345 + '<strong>',
346 + '</strong>'
347 + ),
348 + array(
349 + 'strong' => array(),
350 + )
351 + );
274 352 }
275 353
276 354 /**
277 355 * Return a summary data of this tweak.
@@ -278,25 +356,36 @@
278 356 *
279 357 * @return array
280 358 */
281 359 public function to_array(): array {
360 + $auto_regenerate = $this->get_is_autogenerate_keys();
361 + $auto_status = $auto_regenerate
362 + ? __( 'Auto-regenerate is turned on.', 'defender-security' )
363 + : __( 'Auto-regenerate is turned off.', 'defender-security' );
364 + $saved_duration = $this->get_option( 'reminder_duration' );
365 + $reminder_duration = ( is_string( $saved_duration ) && '' !== $saved_duration )
366 + ? $saved_duration
367 + : $this->default_days;
368 +
282 369 return array(
283 370 'slug' => $this->slug,
284 371 'title' => $this->get_label(),
285 372 'errorReason' => $this->get_error_reason(),
286 - 'successReason' => sprintf(
287 - /* translators: %s: number of days */
288 - esc_html__( 'Your security keys are less than %s days old, nice work.', 'defender-security' ),
289 - $this->get_last_modified_days()
373 + 'successReason' => wp_strip_all_tags(
374 + sprintf(
375 + /* translators: %1$s: reminder duration, %2$s: auto-regenerate status */
376 + __( 'Security keys regenerate every %1$s. %2$s', 'defender-security' ),
377 + $reminder_duration,
378 + $auto_status
379 + )
290 380 ),
291 381 'misc' => array(
292 - 'reminder' => $this->reminder_duration,
382 + 'show_revert_button' => true,
383 + 'reminder' => $reminder_duration,
384 + 'show_action_button' => true,
293 385 ),
294 - 'bulk_description' => esc_html__(
295 - 'Your current security keys are unknown days old. Time to update them! We will update the frequency to 60 days.',
296 - 'defender-security'
297 - ),
298 - 'bulk_title' => esc_html__( 'Security Keys', 'defender-security' ),
386 + 'bulk_description' => wp_strip_all_tags( __( 'WordPress security keys help protect user sessions and cookies. Update security keys with strong, unique values to improve encryption and reduce the risk of unauthorized access.', 'defender-security' ) ),
387 + 'bulk_title' => wp_strip_all_tags( __( 'Security Keys', 'defender-security' ) ),
299 388 );
300 389 }
301 390
302 391 /**
@@ -306,11 +395,9 @@
306 395 */
307 396 public function get_is_autogenerate_keys(): bool {
308 397 $is_autogenerate_keys = $this->get_option( 'is_autogenerate_keys' );
309 398
310 - $this->is_autogenerate_keys = is_null( $is_autogenerate_keys )
311 - ? true
312 - : (bool) $is_autogenerate_keys;
399 + $this->is_autogenerate_keys = is_null( $is_autogenerate_keys ) || (bool) $is_autogenerate_keys;
313 400
314 401 return $this->is_autogenerate_keys;
315 402 }
316 403
@@ -321,9 +408,9 @@
321 408 *
322 409 * @return bool Return true if value updated, otherwise false.
323 410 */
324 411 public function set_is_autogenrate_keys( bool $value ): bool {
325 - $this->is_autogenerate_keys = (bool) $value;
412 + $this->is_autogenerate_keys = $value;
326 413
327 414 return $this->update_option(
328 415 'is_autogenerate_keys',
329 416 $this->is_autogenerate_keys
@@ -352,9 +439,9 @@
352 439 */
353 440 $network_cron_manager = wd_di()->get( Network_Cron_Manager::class );
354 441
355 442 $interval_seconds = strtotime( $display_name, 0 );
356 - $start_time = time() + $interval_seconds;
443 + $start_time = defender_get_current_time() + $interval_seconds;
357 444
358 445 $network_cron_manager->register_callback(
359 446 'wpdef_sec_key_gen',
360 447 array( $this, 'cron_process' ),
@@ -415,14 +502,14 @@
415 502
416 503 /**
417 504 * Append salt to wp-config.php file content string.
418 505 *
419 - * @param string|false $contents Text content of wp-config.php.
420 - * @param string $new_salts Salts need to be appended.
506 + * @param bool|string $contents Text content of wp-config.php.
507 + * @param string $new_salts Salts need to be appended.
421 508 *
422 509 * @return string Text content after salts appended.
423 510 */
424 - private function append_salts( $contents, string $new_salts ): string {
511 + private function append_salts( bool|string $contents, string $new_salts ): string {
425 512 $pattern = '/(define\(\s*\'AUTH_KEY\'.*?\);)\s*' .
426 513 '(define\(\s*\'SECURE_AUTH_KEY\'.*?\);)\s*' .
427 514 '(define\(\s*\'LOGGED_IN_KEY\'.*?\);)\s*' .
428 515 '(define\(\s*\'NONCE_KEY\'.*?\);)\s*' .
@@ -458,9 +545,9 @@
458 545 *
459 546 * @return int|false
460 547 * @since 3.10.0
461 548 */
462 - private function get_wp_config_last_modified_time() {
549 + private function get_wp_config_last_modified_time(): bool|int {
463 550 $file = $this->file;
464 551 if ( ! file_exists( $file ) ) {
465 552 $file = ABSPATH . WPINC . '/general-template.php';
466 553 }
@@ -473,15 +560,10 @@
473 560 *
474 561 * @return WP_Error|bool|array True if success. WP_Error if any error.
475 562 */
476 563 private function update_keys(): WP_Error|bool|array {
477 - global $wp_filesystem;
564 + $wp_filesystem = $this->get_wp_filesystem();
478 565
479 - if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
480 - require_once ABSPATH . '/wp-admin/includes/file.php';
481 - WP_Filesystem();
482 - }
483 -
484 566 if ( ! $wp_filesystem->is_writable( $this->file ) ) {
485 567 return new WP_Error(
486 568 'defender_file_not_writable',
487 569 /* translators: %s: file path */
@@ -525,16 +607,18 @@
525 607
526 608 $contents = $this->append_salts( $contents, $new_salts );
527 609 }
528 610
529 - $is_done = (bool) file_put_contents( $this->file, $contents, LOCK_EX ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
611 + $is_done = $wp_filesystem->put_contents( $this->file, $contents, FS_CHMOD_FILE );
530 612
531 613 if ( ! $is_done ) {
532 614 return false;
533 615 }
534 616 $values = get_site_option( 'defender_security_tweaks_' . $this->slug, array() );
535 - $this->last_modified = time();
536 - $values['last_modified'] = time();
617 + $this->last_modified = defender_get_current_time();
618 + $values['last_modified'] = defender_get_current_time();
619 + unset( $values['is_reverted'] );
620 + $this->is_reverted = false;
537 621 update_site_option( 'defender_security_tweaks_' . $this->slug, $values );
538 622 $this->log( 'Security keys are updated.', Security_Tweak::LOG_FILE_NAME );
539 623 if ( is_multisite() && ! headers_sent() ) {
540 624 // Delete cookies forced.