PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.1.0
Defender Security – Malware Scanner, Login Security & Firewall v3.1.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
defender-security / src / component / security-tweaks / security-key.php

security-key.php in Defender Security – Malware Scanner, Login Security & Firewall 3.1.0, at src/component/security-tweaks/security-key.php

508 lines 12.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WP_Defender\Component\Security_Tweaks;
4
5 use Calotes\Component\Response;
6 use WP_Error;
7 use Calotes\Base\Component;
8 use WP_Defender\Model\Setting\Mask_Login;
9 use WP_Defender\Model\Setting\Security_Tweaks;
10 use WP_Defender\Component\Security_Tweaks\Security_Key_Const_Interface;
11 use WP_Defender\Traits\Security_Tweaks_Option;
12
13 /**
14 * Class Security_Key
15 *
16 * @package WP_Defender\Component\Security_Tweaks
17 */
18 class Security_Key extends Component implements Security_Key_Const_Interface {
19
20 use Security_Tweaks_Option;
21
22 public $slug = 'security-key';
23 public $default_days = '60 days';
24 public $reminder_duration = null;
25 public $reminder_date = null;
26 public $last_modified = null;
27 public $file;
28
29 /**
30 * Flag to automate the security key/salt generation.
31 *
32 * @var bool
33 */
34 private $is_autogenerate_keys = true;
35
36 public function __construct() {
37 $this->file = defender_wp_config_path();
38 $this->add_hooks();
39 }
40
41 /**
42 * Check whether the issue has been resolved or not.
43 *
44 * @return bool
45 */
46 public function check() {
47 $this->get_options();
48
49 if ( ! $this->is_salts_exist() ) {
50 return false;
51 }
52
53 if ( ! $this->last_modified ) {
54 if ( file_exists( $this->file ) ) {
55 $this->last_modified = filemtime( $this->file );
56 } else {
57 $this->last_modified = filemtime( ABSPATH . WPINC . '/general-template.php' );
58 }
59 }
60
61 if ( $this->last_modified ) {
62 $reminder_date = strtotime( '+' . $this->reminder_duration, $this->last_modified );
63
64 return $reminder_date > time();
65 }
66 }
67
68 /**
69 * Get options.
70 *
71 * @return void
72 */
73 private function get_options() {
74 $options = get_site_option( 'defender_security_tweaks_' . $this->slug );
75 $this->reminder_date = ! empty( $options['reminder_date'] ) ? $options['reminder_date'] : null;
76 $this->reminder_duration = ! empty( $options['reminder_duration'] ) ? $options['reminder_duration'] : $this->default_days;
77 $this->last_modified = ! empty( $options['last_modified'] ) ? $options['last_modified'] : null;
78
79 $this->is_autogenerate_keys = ! empty( $options['is_autogenerate_keys'] )
80 ? (bool) $options['is_autogenerate_keys']
81 : true;
82 }
83
84 /**
85 * Here is the code for processing, if the return is true, we add it to resolve list, WP_Error if any error.
86 *
87 * @return bool|WP_Error
88 */
89 public function process() {
90 if ( ! is_writable( $this->file ) ) {
91 return new WP_Error(
92 'defender_file_not_writable',
93 /* translators: %s: file path */
94 sprintf( __( 'The file %s is not writable', 'wpdef' ), $this->file )
95 );
96 }
97
98 $constants = $this->get_constants();
99 $salts = $this->get_salts();
100
101 if ( is_wp_error( $salts ) ) {
102 return $salts;
103 }
104
105 $contents = file_get_contents( $this->file );
106 $new_salts = '';
107
108 foreach ( $constants as $key => $const ) {
109 if ( defined( $const ) ) {
110 $pattern = "/^define\(\s*['|\"]{$const}['|\"],(.*)\)\s*;/m";
111 $replacement = $salts[ $key ];
112 $contents = preg_replace_callback(
113 $pattern,
114 function () use ( $replacement ) {
115 return $replacement;
116 },
117 $contents
118 );
119 } else {
120 $new_salts .= $salts[ $key ] . PHP_EOL;
121 }
122 }
123
124 if ( ! empty( $new_salts ) ) {
125 $new_salts = PHP_EOL .
126 '/* DEFENDER GENERATED SALTS */' .
127 PHP_EOL .
128 $new_salts .
129 PHP_EOL;
130
131 $contents = $this->append_salts( $contents, $new_salts );
132 }
133
134 $is_done = (bool) file_put_contents( $this->file, $contents, LOCK_EX );
135
136 if ( $is_done ) {
137 $values = get_site_option( 'defender_security_tweaks_' . $this->slug );
138 $values['last_modified'] = time();
139 update_site_option( 'defender_security_tweaks_' . $this->slug, $values );
140
141 $url = wp_login_url( network_admin_url( 'admin.php?page=wdf-hardener' ) );
142 $mask_login = new Mask_Login();
143
144 if ( $mask_login->is_active() ) {
145 $url = $mask_login->get_new_login_url();
146 }
147
148 $interval = 3;
149 return new Response(
150 true,
151 array(
152 'message' => sprintf(
153 /* translators: %s: login url, %d: number of seconds */
154 __(
155 'All key salts have been regenerated. You will now need to <a href="%1$s"><strong>re-login</strong></a>.<br/>This will auto reload after <span class="hardener-timer">%2$s</span> seconds.',
156 'wpdef'
157 ),
158 $url,
159 $interval
160 ),
161 'redirect' => $url,
162 'interval' => $interval,
163 )
164 );
165 }
166
167 return $is_done;
168 }
169
170 /**
171 * This is for un-do stuff that has be done in @process.
172 *
173 * @return bool
174 */
175 public function revert() {
176 return true;
177 }
178
179 /**
180 * Shield up.
181 *
182 * @return bool
183 */
184 public function shield_up() {
185 return true;
186 }
187
188 /**
189 * Get salts to be placed in wp-config.php.
190 *
191 * @return string|WP_Error
192 */
193 private function get_salts() {
194 $response = wp_safe_remote_get( 'https://api.wordpress.org/secret-key/1.1/salt/' );
195
196 if ( is_wp_error( $response ) ) {
197 return new WP_Error(
198 'defender_salts_not_found',
199 __( 'Unable to generate salts. Please try again.', 'wpdf' )
200 );
201 }
202
203 return array_filter( explode( "\n", wp_remote_retrieve_body( $response ) ) );
204 }
205
206 /**
207 * Get how long the wp-config file is last updated.
208 *
209 * @return string
210 */
211 private function get_last_modified_days() {
212 $timestamp = filemtime( ABSPATH . '/' . WPINC . '/general-template.php' );
213 $current_time = time();
214 $days_ago = '';
215
216 if ( $this->last_modified ) {
217 $days_ago = ( $current_time - $this->last_modified ) / DAY_IN_SECONDS;
218 } elseif ( $timestamp ) {
219 $days_ago = ( $current_time - $timestamp ) / DAY_IN_SECONDS;
220 }
221
222 $days_ago = $days_ago ? round( $days_ago ) : __( 'unknown', 'wpdef' );
223
224 return $days_ago ?: 1;
225 }
226
227 /**
228 * Get all the constants.
229 *
230 * @return array
231 */
232 private function get_constants() {
233 return array(
234 'AUTH_KEY',
235 'SECURE_AUTH_KEY',
236 'LOGGED_IN_KEY',
237 'NONCE_KEY',
238 'AUTH_SALT',
239 'SECURE_AUTH_SALT',
240 'LOGGED_IN_SALT',
241 'NONCE_SALT',
242 );
243 }
244
245 /**
246 * Return a summary data of this tweak.
247 *
248 * @return array
249 */
250 public function to_array() {
251 $get_last_modified_days = $this->get_last_modified_days();
252
253 if ( 'unknown' === $get_last_modified_days ) {
254 $error_message = __(
255 'We can\'t tell how old your security keys are, perhaps it\'s time to update them?',
256 'wpdef'
257 );
258 }
259 if ( ! $this->is_salts_exist() ) {
260 $error_message = __(
261 'One or more security salts aren\'t defined in wp-config.php. Time to regenerate them!',
262 'wpdef'
263 );
264 } else {
265 $error_message = sprintf(
266 /* translators: %s: number of days */
267 __( 'Your current security keys are %s days old. Time to update them!', 'wpdef' ),
268 $get_last_modified_days
269 );
270 }
271
272 return array(
273 'slug' => $this->slug,
274 'title' => __( 'Update old security keys', 'wpdef' ),
275 'errorReason' => $error_message,
276 'successReason' => sprintf(
277 /* translators: %s: number of days */
278 __( 'Your security keys are less than %s days old, nice work.', 'wpdef' ),
279 $get_last_modified_days
280 ),
281 'misc' => array(
282 'reminder' => $this->reminder_duration,
283 ),
284 'bulk_description' => __(
285 'Your current security keys are unknown days old. Time to update them! We will update the frequency to 60 days.',
286 'wpdef'
287 ),
288 'bulk_title' => __( 'Security Keys', 'wpdef' ),
289 );
290 }
291
292 /**
293 * Getter method of is_autogenerate_keys.
294 *
295 * @return bool. Return true or false which is used to trigger auto generation of security salt/key.
296 */
297 public function get_is_autogenerate_keys() {
298 $is_autogenerate_keys = $this->get_option( 'is_autogenerate_keys' );
299
300 $this->is_autogenerate_keys = is_null( $is_autogenerate_keys )
301 ? true
302 : (bool) $is_autogenerate_keys;
303
304 return $this->is_autogenerate_keys;
305 }
306
307 /**
308 * Setter method of is_autogenerate_keys.
309 *
310 * @param bool $value Boolean flag of the is_autogenerate_keys.
311 *
312 * @return bool Return true if value updated, otherwise false.
313 */
314 public function set_is_autogenrate_keys( $value ) {
315 $this->is_autogenerate_keys = (bool) $value;
316
317 return $this->update_option(
318 'is_autogenerate_keys',
319 $this->is_autogenerate_keys
320 );
321 }
322
323 /**
324 * Method to initialize component hooks.
325 */
326 public function add_hooks() {
327 add_action( 'wpdef_sec_key_gen', array( &$this, 'cron_process' ) );
328 }
329
330 /**
331 * Cron schedule.
332 */
333 public function cron_schedule() {
334 if (
335 true === $this->get_is_autogenerate_keys() &&
336 ! wp_next_scheduled( 'wpdef_sec_key_gen' )
337 ) {
338 $display_name = $this->get_option( 'reminder_duration' );
339
340 if ( empty( $display_name ) ) {
341 $display_name = $this->default_days;
342 }
343
344 $schedule_detail = $this->get_schedule_detail( $display_name );
345
346 $schedule_key = key( $schedule_detail );
347 $schedule_interval = time() + $schedule_detail[ $schedule_key ]['interval'];
348
349 wp_schedule_event(
350 $schedule_interval,
351 $schedule_key,
352 'wpdef_sec_key_gen'
353 );
354 }
355 }
356
357 /**
358 * Cron unschedule.
359 */
360 public function cron_unschedule() {
361 $timestamp = wp_next_scheduled( 'wpdef_sec_key_gen' );
362
363 if ( $timestamp ) {
364 wp_unschedule_event( $timestamp, 'wpdef_sec_key_gen' );
365 }
366 }
367
368 /**
369 * Cron schedule security key/salt generation process.
370 */
371 public function cron_process() {
372 try {
373 $security_tweak_model = new Security_Tweaks();
374 if ( ! $security_tweak_model->is_tweak_ignore( $this->slug ) &&
375 true === $this->get_is_autogenerate_keys()
376 ) {
377 $this->process();
378 }
379 } catch ( \Throwable $th ) {
380 // PHP 7+ catch.
381 $this->log( get_class( $th ) . ': ' . $th->getMessage(), 'internal.log' );
382 } catch ( \Exception $e ) {
383 $this->log( get_class( $e ) . ': ' . $e->getMessage(), 'internal.log' );
384 }
385 }
386
387 /**
388 * Get scheduler detail.
389 *
390 * @param string $display_name The name to filter the schedules list.
391 *
392 * @return array Return filtered schedule detail.
393 */
394 private function get_schedule_detail( $display_name ) {
395 $all_schedules = wp_get_schedules();
396
397 $schedule_detail = array_filter(
398 $all_schedules,
399 function( $arr ) use ( $display_name ) {
400 return $display_name === $arr['display'];
401 }
402 );
403
404 return $schedule_detail;
405 }
406
407 /**
408 * Check all salts are exists.
409 *
410 * @return bool Return true if all constants have salt else false.
411 */
412 private function is_salts_exist() {
413 foreach ( $this->get_constants() as $constants ) {
414 if ( ! defined( $constants ) ) {
415 return false;
416 }
417 }
418
419 return true;
420 }
421
422 /**
423 * Append salt to wp-config.php file content string.
424 *
425 * @param string $contents Text content of wp-config.php.
426 * @param string $new_salts Salts need to be appended.
427 *
428 * @return string Text content after salts appended.
429 */
430 private function append_salts( $contents, $new_salts ) {
431 $config_array = preg_split( '/\R/', $contents );
432
433 $line_no = $this->search_line( // Important $line_no is array index therefore 0 means first line.
434 $config_array,
435 "That's all, stop editing!" // Warning text in wp-config file.
436 );
437
438 if ( false === $line_no ) { // If faster prediction failed.
439 // Regex prediction (slow one but last resort). Matches multiline of: "if ( ! defined( 'ABSPATH' ) ) {".
440 $regex = "/^\s*if\s*\(\s*\!\s*defined\s*\(\s*['|\"]ABSPATH['|\"]\s*\)\s*\)\s*\{?\s*/m";
441
442 $line_no = $this->grep_line( $config_array, $regex );
443 }
444
445 // Splice before WP initialize and append.
446 array_splice( $config_array, $line_no, 0, $new_salts );
447
448 return implode( PHP_EOL, $config_array );
449 }
450
451 /**
452 * Simple array search and return the array index of first match value.
453 *
454 * @param array $array Array of text for search.
455 * @param string $search_text Search term.
456 *
457 * @return int|bool On found return array index else false.
458 */
459 private function search_line( $array, $search_text ) {
460 $filtered_array = array_filter(
461 $array,
462 function( $el ) use ( $search_text ) {
463 return ( false !== strpos( $el, $search_text ) );
464 }
465 );
466
467 if ( empty( $filtered_array ) ) {
468 return false;
469 }
470
471 reset( $filtered_array );
472 return key( $filtered_array );
473 }
474
475 /**
476 * Grep array search and return the array index of first match value.
477 *
478 * @param array $array Array of text for search.
479 * @param string $regex Search term (accepts regex).
480 *
481 * @return int|bool On found return array index else false.
482 */
483 private function grep_line( $array, $regex ) {
484 $filtered_array = preg_grep( $regex, $array );
485
486 if ( empty( $filtered_array ) ) {
487 return false;
488 }
489
490 reset( $filtered_array );
491 return key( $filtered_array );
492 }
493
494 /**
495 * @return array
496 */
497 public function reminder_frequencies() {
498
499 return array(
500 '30 days',
501 '60 days',
502 '90 days',
503 '6 months',
504 '1 year',
505 );
506 }
507 }
508