PluginProbe ʕ •ᴥ•ʔ
NinjaFirewall (WP Edition) – Advanced Security Plugin and Firewall / 4.9
NinjaFirewall (WP Edition) – Advanced Security Plugin and Firewall v4.9
4.9 4.8.8 4.8.7 4.8.6 trunk 4.5 4.5.1 4.5.10 4.5.11 4.5.2 4.5.3 4.5.4 4.5.5 4.5.6 4.5.7 4.5.8 4.5.9 4.6 4.6.1 4.7 4.7.1 4.7.2 4.7.3 4.7.4 4.7.5 4.8 4.8.1 4.8.2 4.8.3 4.8.4 4.8.5
ninjafirewall / lib / helpers.php
ninjafirewall / lib Last commit date
share 9 years ago .htaccess 11 years ago anti_malware.php 5 years ago class-api.php 4 weeks ago class-centralised-logging.php 4 weeks ago class-coupon.php 7 months ago class-email-sodium.php 4 weeks ago class-firewall-log.php 4 weeks ago class-helpers.php 9 months ago class-import-export.php 5 months ago class-ip.php 5 months ago class-nfw-database.php 7 months ago class-plugin-upgrade.php 4 weeks ago class-security-updates.php 4 weeks ago class-session.php 4 weeks ago class_mail.php 4 weeks ago firewall.php 4 weeks ago fw_fileguard.php 5 months ago fw_livelog.php 1 year ago help.php 4 weeks ago helpers.php 4 weeks ago i18n-extra.php 4 weeks ago i18n.php 1 year ago index.html 13 years ago init_update.php 2 years ago install.php 1 year ago install_default.php 4 weeks ago loader.php 7 months ago mail_template_firewall.php 1 year ago mail_template_plugin.php 4 weeks ago scheduled_tasks.php 3 years ago settings_dashboard.php 4 weeks ago settings_dashboard_about.php 4 weeks ago settings_dashboard_statistics.php 2 months ago settings_event_notifications.php 4 weeks ago settings_events.php 2 months ago settings_firewall_options.php 2 months ago settings_firewall_policies.php 4 weeks ago settings_login_protection.php 2 months ago settings_logs.php 4 weeks ago settings_logs_firewall_log.php 4 weeks ago settings_logs_live_log.php 2 months ago settings_monitoring.php 4 weeks ago settings_monitoring_file_check.php 2 months ago settings_monitoring_file_guard.php 2 months ago settings_network.php 2 months ago settings_security_rules.php 2 months ago settings_security_rules_editor.php 4 weeks ago settings_security_rules_update.php 4 weeks ago sign.pub 7 years ago thickbox.php 4 years ago widget.php 3 years ago wpplus.php 5 months ago
helpers.php
1703 lines
1 <?php
2 /*
3 +=====================================================================+
4 | _ _ _ _ _____ _ _ _ |
5 | | \ | (_)_ __ (_) __ _| ___(_)_ __ _____ ____ _| | | |
6 | | \| | | '_ \ | |/ _` | |_ | | '__/ _ \ \ /\ / / _` | | | |
7 | | |\ | | | | || | (_| | _| | | | | __/\ V V / (_| | | | |
8 | |_| \_|_|_| |_|/ |\__,_|_| |_|_| \___| \_/\_/ \__,_|_|_| |
9 | |__/ |
10 | (c) NinTechNet Limited ~ https://nintechnet.com/ |
11 +=====================================================================+
12 */
13
14 if (! defined('NFW_ENGINE_VERSION') ) { die('Forbidden'); }
15
16 // --------------------------------------------------------------------- 2023-07-27
17 // The name of the MU plugin can be defined in wp-config.php.
18
19 if (! defined('NINJAFIREWALL_MU_PLUGIN') ) {
20 define('NINJAFIREWALL_MU_PLUGIN', '0-ninjafirewall.php');
21 } else {
22 // If defined and different, make sure to delete the old one
23 if ( NINJAFIREWALL_MU_PLUGIN != '0-ninjafirewall.php' &&
24 file_exists( WPMU_PLUGIN_DIR .'/0-ninjafirewall.php') ) {
25
26 unlink( WPMU_PLUGIN_DIR .'/0-ninjafirewall.php');
27 }
28 }
29
30 // ---------------------------------------------------------------------
31 // Contextual help reminder.
32
33 function nfw_contextual_help() {
34 echo '<div style="text-align:right;font-weight:normal;padding-top: 9px;">' .
35 '<span class="description" style="color:#808080;">' .
36 esc_html('Click on the above "Help" tab for help.', 'ninjafirewall') .
37 '</span></div>';
38 }
39
40 // --------------------------------------------------------------------- 2023-07-27
41 // Animated button/switch.
42
43 function nfw_toggle_switch( $type, $name, $text_on, $text_off,
44 $size, $value = 0, $disabled = false, $attr = false,
45 $id = false, $align = false ) {
46
47 if ( $size == 'large') {
48 $size = 'style="width:150px;"';
49
50 } elseif ( $size == 'small') {
51 $size = 'style="width:80px"';
52
53 } else {
54 $size = 'style="width:'. (int) $size .'px"';
55 }
56
57 if ( $type == 'danger') {
58 $type = 'tgl-danger';
59 } elseif ( $type == 'warning') {
60 $type = 'tgl-warning';
61 } elseif ( $type == 'green') {
62 $type = 'tgl-green';
63 } else {
64 $type = 'tgl-info';
65 }
66
67 $text_on = esc_attr( $text_on );
68 $text_off = esc_attr( $text_off );
69
70 if ( $id == false ) {
71 $id = uniqid();
72 }
73
74 $name = esc_attr( $name );
75 $id = esc_attr( $id );
76
77 if ( $disabled == false ) {
78 $disabled = '';
79 } else {
80 $disabled = ' disabled';
81 }
82 if ( $attr != false ) {
83 $attr = ' '. $attr;
84 }
85
86 if ( $align == false ) {
87 $align = '';
88 } elseif ( $align == 'left') {
89 $align = ' alignleft';
90 } else {
91 $align = ' alignright';
92 }
93 ?>
94 <div class="tg-list-item<?php echo $align ?>">
95 <input class="tgl tgl-switch" name="<?php echo $name ?>"<?php checked( $value, 1 ) ?> id="<?php echo $id ?>" type="checkbox"<?php echo $disabled; ?><?php echo $attr ?> />
96 <label class="tgl-btn <?php echo $type ?>"<?php nfw_aria_label( $value, 1, $text_on, $text_off ) ?> data-tg-on="<?php echo $text_on ?>" data-tg-off="<?php echo $text_off ?>" for="<?php echo $id ?>" <?php echo $size ?>></label>
97 </div>
98 <?php
99 }
100
101 // ---------------------------------------------------------------------
102 // Check for HTTPS. This function is also available in firewall.php
103 // and is used here only if the firewall is not loaded.
104
105 if (! function_exists('nfw_is_https') ) {
106
107 function nfw_is_https() {
108 // Can be defined in the .htninja:
109 if ( defined('NFW_IS_HTTPS') ) { return; }
110
111 if ( ( isset( $_SERVER['SERVER_PORT'] ) && $_SERVER['SERVER_PORT'] == 443 ) ||
112 ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ||
113 ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off') ) {
114 define('NFW_IS_HTTPS', true);
115 } else {
116 define('NFW_IS_HTTPS', false);
117 }
118 }
119 }
120 nfw_is_https();
121
122 // ---------------------------------------------------------------------
123 // Check whether the user is whitelisted (.htninja etc).
124
125 function nfw_is_whitelisted() {
126
127 if ( defined('NFW_UWL') && NFW_UWL == true ) {
128 return true;
129 }
130 }
131
132 // ---------------------------------------------------------------------
133
134 add_filter('wp_insert_post_empty_content', 'nf_wp_insert_post_empty_content', 10000, 2 );
135
136 function nf_wp_insert_post_empty_content( $maybe_empty, $postarr ) {
137
138 $nfw_options = nfw_get_option('nfw_options');
139
140 if (! empty( NinjaFirewall_session::read('nfw_goodguy') ) || nfw_is_whitelisted() ||
141 empty( $nfw_options['enabled'] ) || empty( $nfw_options['disallow_publish'] ) ) {
142
143 return false;
144 }
145
146 /**
147 * We only care about page and post post_type.
148 */
149 if (! empty( $postarr['post_type'] ) &&
150 ( $postarr['post_type'] == 'post' || $postarr['post_type'] == 'page') ) {
151
152 if (! isset( $postarr['ID'] ) ) {
153 $id = 0;
154 } else {
155 $id = $postarr['ID'];
156 }
157
158 /**
159 * Ignore post if it isn't either already published or set to be published immediately.
160 */
161 if ( get_post_status( $id ) != 'publish' &&
162 ( empty( $postarr['post_status'] ) || $postarr['post_status'] != 'publish') ) {
163
164 return false;
165 }
166
167 /**
168 * Ignore empty post whose ID is 0, including issue with the Quick Draft widget (#2140).
169 */
170 if ( empty( $id ) && empty( $postarr['post_content'] ) ) {
171 return false;
172 }
173
174 $old_post = get_post( $id );
175 if ( $old_post->post_title == $postarr['post_title'] &&
176 $old_post->post_content == $postarr['post_content'] ) {
177
178 return false;
179 }
180
181 /**
182 * We must use meta capability (edit_post/edit_page), not capability (edit_postS/edit_pageS).
183 */
184 $edit_post = "edit_{$postarr['post_type']}";
185 if ( current_user_can( $edit_post, $id ) ) {
186 return false;
187 }
188
189 if (! empty( $postarr['post_title'] ) ) {
190 $post_title = $postarr['post_title'];
191 } else {
192 $post_title = __('N/A', 'ninjafirewall');
193 }
194 if (! empty( $postarr['post_content'] ) ) {
195 if ( strlen( $postarr['post_content'] ) > 100 ) {
196 $postarr['post_content'] = mb_substr( $postarr['post_content'], 0, 100, 'utf-8') .'...';
197 }
198 $post_content = $postarr['post_content'];
199 } else {
200 $post_content = __('N/A', 'ninjafirewall');
201 }
202
203 /**
204 * Page or post creation.
205 */
206 if ( empty( $id ) ) {
207 /* Translators : "page" or "post" type */
208 $action = sprintf( __('Attempt to create a new %s', 'ninjafirewall'), $postarr['post_type'] );
209 /**
210 * Page or post edition.
211 */
212 } else {
213 /* Translators : "page" or "post" type and its numerical ID */
214 $action = sprintf(
215 __('Attempt to edit a published %s (ID: %s)', 'ninjafirewall'), $postarr['post_type'], $id
216 );
217 }
218
219 /**
220 * Check if the user is authenticated.
221 */
222 $current_user = wp_get_current_user();
223 if ( empty( $current_user->user_login ) ) {
224 $user = __('Unauthenticated user', 'ninjafirewall');
225 } else {
226 $user = $current_user->user_login;
227 }
228
229 $subject = __('Blocked post/page edition attempt', 'ninjafirewall');
230
231 NinjaFirewall_log::write(
232 "WordPress: $subject",
233 "post_content: $post_content",
234 NFWLOG_CRITICAL, 0, $nfw_options, NFW_LOG_DIR .'/nfwlog'
235 );
236
237 /**
238 * Backtrace.
239 */
240 $return = nfw_debug_backtrace( $nfw_options );
241 if (! empty( $return['nftmpfname'] ) ) {
242 $attachment = $return['nftmpfname'];
243 } else {
244 $attachment = [];
245 }
246
247 /**
248 * Email notification.
249 */
250 $subject = [];
251 $content = [ home_url('/'), $user, $action, $post_title, $post_content,
252 NFW_REMOTE_ADDR, $_SERVER['SCRIPT_FILENAME'], $_SERVER['REQUEST_URI'],
253 date_i18n('F j, Y @ H:i:s T'), $return['message']
254 ];
255 NinjaFirewall_mail::send('perm_edit', $subject, $content, '', $attachment, 1 );
256
257
258 /**
259 * Block the request.
260 */
261 NinjaFirewall_session::delete();
262 wp_die(
263 'NinjaFirewall: '. __('You are not allowed to perform this task.', 'ninjafirewall'),
264 'NinjaFirewall: '. __('You are not allowed to perform this task.', 'ninjafirewall'),
265 $nfw_options['ret_code']
266 );
267 }
268 return false;
269 }
270
271 // ---------------------------------------------------------------------
272
273 add_filter('pre_delete_post', 'nf_pre_delete_post', 10000, 3 );
274
275 function nf_pre_delete_post( $delete, $post, $force_delete ) {
276
277 $nfw_options = nfw_get_option('nfw_options');
278
279 if (! empty( NinjaFirewall_session::read('nfw_goodguy') ) || nfw_is_whitelisted() ||
280 empty( $nfw_options['enabled'] ) || empty( $nfw_options['disallow_publish'] ) ) {
281
282 return null;
283 }
284 if (! isset( $post->post_type ) || ! isset( $post->post_status ) || empty( $post->ID ) ) {
285 return null;
286 }
287 if ( ( $post->post_type == 'post' ||
288 $post->post_type == 'page') && $post->post_status == 'publish') {
289
290 if (! current_user_can( "delete_{$post->post_type}", $post->ID ) ) {
291
292 /**
293 * Check if user is authenticated.
294 */
295 $current_user = wp_get_current_user();
296 if ( empty( $current_user->user_login ) ) {
297 $user = __('Unauthenticated user', 'ninjafirewall');
298 } else {
299 $user = $current_user->user_login;
300 }
301
302 if (! empty( $post->post_title ) ) {
303 $post_title = $post->post_title;
304 } else {
305 $post_title = __('N/A', 'ninjafirewall');
306 }
307
308 $subject = __('Blocked post/page deletion attempt', 'ninjafirewall');
309
310 NinjaFirewall_log::write(
311 "WordPress: $subject",
312 "post ID: {$post->ID}",
313 NFWLOG_CRITICAL, 0, $nfw_options, NFW_LOG_DIR .'/nfwlog'
314 );
315
316 /**
317 * Backtrace.
318 */
319 $return = nfw_debug_backtrace( $nfw_options );
320 if (! empty( $return['nftmpfname'] ) ) {
321 $attachment = $return['nftmpfname'];
322 } else {
323 $attachment = [];
324 }
325
326 /**
327 * Email notification.
328 */
329 $subject = [];
330 $content = [ home_url('/'), $user, $post->ID, $post_title,
331 NFW_REMOTE_ADDR, $_SERVER['SCRIPT_FILENAME'], $_SERVER['REQUEST_URI'],
332 date_i18n('F j, Y @ H:i:s T'), $return['message']
333 ];
334 NinjaFirewall_mail::send('perm_delete', $subject, $content, '', $attachment, 1 );
335
336 /**
337 * Block the request.
338 */
339 NinjaFirewall_session::delete();
340 wp_die(
341 'NinjaFirewall: '. __('You are not allowed to perform this task.', 'ninjafirewall'),
342 'NinjaFirewall: '. __('You are not allowed to perform this task.', 'ninjafirewall'),
343 $nfw_options['ret_code']
344 );
345 }
346 }
347 return null;
348 }
349
350 // ---------------------------------------------------------------------
351 // Return backtrace verbosity.
352
353 function nfw_verbosity( $nfw_options ) {
354
355 if (! isset( $nfw_options['a_61'] ) || $nfw_options['a_61'] == 1 ) {
356 // Medium verbosity:
357 return 0;
358
359 } elseif ( $nfw_options['a_61'] == -1 ) {
360 // Disabled:
361 return false;
362
363 } elseif ( $nfw_options['a_61'] == 2 ) {
364 // High verbosity:
365 return 1;
366 }
367
368 // Low verbosity:
369 return 2;
370 }
371
372 // ---------------------------------------------------------------------
373 // Prevent account deletion.
374
375 function nfw_delete_user( $user_id ) {
376
377 $nfw_options = nfw_get_option('nfw_options');
378 $user_data = get_userdata( $user_id );
379
380 if ( current_user_can('delete_users') || empty( $nfw_options['disallow_deletion'] ) ||
381 empty( $nfw_options['enabled'] ) ) {
382
383 /**
384 * Log and allow the request.
385 */
386 NinjaFirewall_log::write(
387 "Deleting user",
388 "User: {$user_data->user_login}, ID: $user_id",
389 NFWLOG_INFO, 0, $nfw_options, NFW_LOG_DIR .'/nfwlog'
390 );
391 return;
392 }
393
394 $subject = __('Blocked user deletion attempt', 'ninjafirewall');
395 /**
396 * Write to log.
397 */
398 NinjaFirewall_log::write(
399 "WordPress: $subject",
400 "User: {$user_data->user_login}, ID: $user_id",
401 NFWLOG_CRITICAL, 0, $nfw_options, NFW_LOG_DIR .'/nfwlog'
402 );
403
404 /**
405 * Backtrace.
406 */
407 $return = nfw_debug_backtrace( $nfw_options );
408 if (! empty( $return['nftmpfname'] ) ) {
409 $attachment = $return['nftmpfname'];
410 } else {
411 $attachment = [];
412 }
413
414 /**
415 * Email notification.
416 */
417 $subject = [];
418 $content = [ home_url('/'), "{$user_data->user_login} (ID: $user_id)",
419 NFW_REMOTE_ADDR, $_SERVER['SCRIPT_FILENAME'], $_SERVER['REQUEST_URI'],
420 date_i18n('F j, Y @ H:i:s T') , $return['message']
421 ];
422 NinjaFirewall_mail::send('delete_user', $subject, $content, '', $attachment, 1 );
423
424 /**
425 * Block the request.
426 */
427 NinjaFirewall_session::delete();
428 wp_die(
429 'NinjaFirewall: '. __('You are not allowed to perform this task.', 'ninjafirewall'),
430 'NinjaFirewall: '. __('You are not allowed to perform this task.', 'ninjafirewall'),
431 $nfw_options['ret_code']
432 );
433 }
434
435 add_action('delete_user', 'nfw_delete_user');
436
437 // ---------------------------------------------------------------------
438 // Allow/disallow account creation.
439
440 function nfw_account_creation( $user_login ) {
441
442 $nfw_options = nfw_get_option('nfw_options');
443
444 /**
445 * We must allow the request if the username exists too, otherwise we'll
446 * block them from using the "Lost password" feature.
447 */
448 if ( current_user_can('create_users') || empty( $nfw_options['disallow_creation'] ) ||
449 empty( $nfw_options['enabled'] ) || username_exists( $user_login ) ) {
450 /**
451 * Do nothing.
452 */
453 return $user_login;
454 }
455
456 $subject = __('Blocked user account creation', 'ninjafirewall');
457 /**
458 * Write to log.
459 */
460 NinjaFirewall_log::write(
461 "WordPress: {$subject}",
462 "Username: {$user_login}",
463 NFWLOG_CRITICAL, 0, $nfw_options, NFW_LOG_DIR .'/nfwlog'
464 );
465
466 /**
467 * Backtrace.
468 */
469 $return = nfw_debug_backtrace( $nfw_options );
470 if (! empty( $return['nftmpfname'] ) ) {
471 $attachment = $return['nftmpfname'];
472 } else {
473 $attachment = [];
474 }
475
476 /**
477 * Email notification.
478 */
479 $subject = [];
480 $content = [ home_url('/'), $user_login, NFW_REMOTE_ADDR, $_SERVER['SCRIPT_FILENAME'],
481 $_SERVER['REQUEST_URI'], date_i18n('F j, Y @ H:i:s T') , $return['message']
482 ];
483 NinjaFirewall_mail::send('create_user', $subject, $content, '', $attachment, 1 );
484
485 /**
486 * Block the request.
487 */
488 NinjaFirewall_session::delete();
489 wp_die(
490 'NinjaFirewall: '. __('You are not allowed to perform this task.', 'ninjafirewall'),
491 'NinjaFirewall: '. __('You are not allowed to perform this task.', 'ninjafirewall'),
492 $nfw_options['ret_code']
493 );
494 }
495
496 add_filter('pre_user_login' , 'nfw_account_creation');
497
498 // ---------------------------------------------------------------------
499 // Clean/delete cache folder & temp files (hourly cron job).
500
501 function nfw_garbage_collector() {
502
503 $path = NFW_LOG_DIR .'/nfwlog/cache';
504 $now = time();
505 // Make sure the cache folder exists, i.e, we have been
506 // through the whole installation process
507 if (! is_dir( $path ) ) {
508 return;
509 }
510
511 // Don't do anything if the garbage collector was executed less than 45mn ago
512 $gc = $path .'/garbage_collector.php';
513 if ( file_exists( $gc ) ) {
514 $nfw_mtime = filemtime( $gc ) ;
515 if ( $now - $nfw_mtime < 45*60 ) {
516 return;
517 }
518 unlink( $gc );
519 }
520 touch( $gc );
521
522 // Fetch options
523 $nfw_options = nfw_get_option('nfw_options');
524
525 // ------------------------------------------------------------------
526 // If nfw_options is corrupted (e.g., failed update etc) we try to restore it
527 // from a backup file otherwise we restore it from the default settings.
528 if ( nfw_validate_option( $nfw_options ) === false ) {
529
530 $files = NinjaFirewall_helpers::nfw_glob( $path, 'backup_.+?\.php$', true, true );
531 $valid_option = 0;
532
533 // Make sure we have a backup file
534 while ( is_array( $files ) && ! empty( $files[0] ) ) {
535 $content = [];
536 $last_file = array_pop( $files );
537 $data = file_get_contents( $last_file );
538 $data = str_replace('<?php exit; ?>', '', $data );
539 // Is it base64-encoded (since 4.3.5)?
540 if ( $data[0] == 'B') {
541 // Decode it
542 $data = ltrim( $data, 'B');
543 $data = base64_decode( $data );
544 }
545 $content = @explode("\n:-:\n", $data . "\n:-:\n");
546 $content[0] = json_decode( $content[0], true );
547
548 if ( nfw_validate_option( $content[0] ) === true ) {
549 // We can use that backup to restore our options
550 $valid_option = 1;
551 break;
552
553 // Delete this corrupted backup file
554 } else {
555 nfw_log_error(
556 sprintf(__('Backup file is corrupted, deleting it (%s)','ninjafirewall'), $last_file )
557 );
558 unlink( $last_file );
559 }
560 }
561
562 // Restore the last good backup
563 if (! empty( $valid_option ) ) {
564 nfw_update_option('nfw_options', $content[0] );
565 nfw_log_error( sprintf( __('NinjaFirewall\'s options are corrupted, restoring them from '.
566 'last known good backup file (%s)', 'ninjafirewall'), $last_file ) );
567
568 // Restore the default settings if no backup file was found
569 // (this action will also restore the firewall rules)
570 } else {
571 require_once __DIR__ .'/install_default.php';
572 nfw_log_error( __('NinjaFirewall\'s options are corrupted, restoring their default values '.
573 '(no valid backup found)', 'ninjafirewall') );
574 nfw_load_default_conf();
575 }
576
577 $nfw_options = nfw_get_option('nfw_options');
578 }
579
580 // ------------------------------------------------------------------
581
582 // Check if we must delete old firewall logs
583 if (! empty( $nfw_options['auto_del_log'] ) ) {
584 $auto_del_log = (int) $nfw_options['auto_del_log'] * 86400;
585
586 // Retrieve the list of all logs
587 $list = NinjaFirewall_helpers::nfw_glob(
588 NFW_LOG_DIR .'/nfwlog', 'firewall_.+?\.php$', true, true
589 );
590
591 foreach( $list as $file ) {
592 $lines = [];
593 $lines = file( $file, FILE_SKIP_EMPTY_LINES );
594 foreach( $lines as $k => $line ) {
595 if ( preg_match('/^\[(\d{10})\]/', $line, $match ) ) {
596 if ( $now - $auto_del_log > $match[1] ) {
597 // This line is too old, remove it
598 unset( $lines[ $k ] );
599 }
600 } else {
601 // Not a proper firewall log line
602 unset( $lines[ $k ] );
603 }
604 }
605 if ( empty( $lines ) ) {
606 // No lines left, delete the file
607 unlink( $file );
608 } else {
609 // Save the last preserved lines to the log
610 $fh = fopen( $file, 'w');
611 fwrite( $fh, "<?php exit; ?>\n" );
612 foreach( $lines as $line ) {
613 fwrite( $fh, $line );
614 }
615 fclose( $fh );
616 }
617 }
618 }
619
620 // File Guard temp files
621 $list = NinjaFirewall_helpers::nfw_glob( $path, 'fg_.+?\.php$', true, true );
622 foreach( $list as $file ) {
623 $nfw_ctime = filectime( $file );
624 // Delete it, if it is too old
625 if ( $now - $nfw_options['fg_mtime'] * 3660 > $nfw_ctime ) {
626 unlink( $file );
627 }
628 }
629
630 /**
631 * Remove older session files if they were untouched for 24mn (1440 sec).
632 * Note: NFWSESS_MAXLIFETIME can be defined in the wp-config.php or .htninja.
633 */
634 if ( defined('NFWSESSION_DIR') ) {
635 if (! defined('NFWSESS_MAXLIFETIME') ) {
636 define('NFWSESS_MAXLIFETIME', 1440);
637 }
638 $list = NinjaFirewall_helpers::nfw_glob( NFWSESSION_DIR, '^sess_', true, true );
639 foreach( $list as $file ) {
640 $sess_time = filemtime( $file );
641 if ( $sess_time + NFWSESS_MAXLIFETIME < $now ) {
642 wp_delete_file( $file );
643 }
644 }
645 }
646
647 // Live Log
648 $nfw_livelogrun = $path . '/livelogrun.php';
649 if ( file_exists( $nfw_livelogrun ) ) {
650 $nfw_mtime = filemtime( $nfw_livelogrun );
651 // If the file was not accessed for more than 100s, we assume
652 // the admin has stopped using live log from WordPress
653 // dashboard (refresh rate is max 45 seconds)
654 if ( $now - $nfw_mtime > 100 ) {
655 unlink( $nfw_livelogrun );
656 }
657 }
658 // If the log was not modified for the past 10mn, we delete it as well
659 $nfw_livelog = $path . '/livelog.php';
660 if ( file_exists( $nfw_livelog ) ) {
661 $nfw_mtime = filemtime( $nfw_livelog ) ;
662 if ( $now - $nfw_mtime > 600 ) {
663 unlink( $nfw_livelog );
664 }
665 }
666
667 // ------------------------------------------------------------------
668
669 // NinjaFirewall's configuration backup. We create a new one daily
670 $list = NinjaFirewall_helpers::nfw_glob( $path, 'backup_.+?\.php$', true, true );
671 if (! empty( $list[0] ) ) {
672 rsort( $list );
673 // Check if last backup if older than one day
674 if ( preg_match('`/backup_(\d{10})_.+\.php$`', $list[0], $match ) ) {
675 if ( $now - $match[1] > 86400 ) {
676 // Backup the configuration
677 $nfw_rules = nfw_get_option('nfw_rules');
678 if ( file_exists( $path .'/bf_conf.php') ) {
679 $bd_data = json_encode( file_get_contents( $path .'/bf_conf.php') );
680 } else {
681 $bd_data = '';
682 }
683 $data = json_encode( $nfw_options ) ."\n:-:\n". json_encode($nfw_rules) ."\n:-:\n". $bd_data;
684 $file = uniqid('backup_'. time() .'_', true) . '.php';
685 // Since version 4.3.5, we base64-encode the data because
686 // some hosts flag it as malicious
687 @file_put_contents( "$path/$file", '<?php exit; ?>B' . base64_encode( $data ), LOCK_EX );
688 array_unshift( $list, "$path/$file" );
689 }
690 }
691 // Keep the last 5 backup only (value can be defined
692 // in the wp-config.php)
693 if ( defined('NFW_MAX_BACKUP') ) {
694 $num = (int) NFW_MAX_BACKUP;
695 } else {
696 $num = 5;
697 }
698 $old_backup = array_slice( $list, $num );
699 foreach( $old_backup as $file ) {
700 unlink( $file );
701 }
702 } else {
703 // Create first backup
704 $nfw_rules = nfw_get_option('nfw_rules');
705 if ( empty( $nfw_rules ) ) {
706 return;
707 }
708 if ( file_exists( $path .'/bf_conf.php') ) {
709 $bd_data = json_encode( file_get_contents( $path .'/bf_conf.php') );
710 } else {
711 $bd_data = '';
712 }
713 $data = json_encode( $nfw_options ) ."\n:-:\n". json_encode( $nfw_rules ) ."\n:-:\n". $bd_data;
714 $file = uniqid('backup_'. time() .'_', true ) .'.php';
715 // Since version 4.3.5, we base64-encode the data because
716 // some hosts flag it as malicious
717 @file_put_contents( "$path/$file", '<?php exit; ?>B' . base64_encode( $data ), LOCK_EX );
718 }
719
720 // ------------------------------------------------------------------
721 /**
722 * WordPress, plugins and themes security updates.
723 */
724 require_once __DIR__ .'/class-security-updates.php';
725 NinjaFirewall_security_updates::check();
726
727 /**
728 * Check if we have a discount coupon to offer to the user.
729 */
730 require_once __DIR__ .'/class-coupon.php';
731 $coupon = new NinjaFirewall_coupon();
732 $coupon->run();
733 }
734
735 // ---------------------------------------------------------------------
736 // Write potential errors to a specific log.
737
738 function nfw_log_error( $message ) {
739
740 $log = NFW_LOG_DIR . '/nfwlog/error_log.php';
741
742 if (! file_exists( $log ) ) {
743 @file_put_contents( $log, "<?php exit; ?>\n", LOCK_EX );
744 }
745 @file_put_contents( $log, date('[d/M/y:H:i:s O]') . " $message\n", FILE_APPEND | LOCK_EX );
746
747 }
748
749 // ---------------------------------------------------------------------
750
751 function nfw_admin_notice() {
752
753 // Warn about Site Health if needed
754 if ( strpos( $_SERVER['SCRIPT_NAME'], '/wp-admin/site-health.php') !== FALSE ) {
755 // This bug was fixed in WordPress 5.6.1
756 global $wp_version;
757 if ( version_compare( $wp_version, '5.6.1', '<') ) {
758 if ( file_exists( NFW_LOG_DIR . '/nfwlog/cache/bf_conf.php') ) {
759 include NFW_LOG_DIR . '/nfwlog/cache/bf_conf.php';
760 if (! empty( $bf_enable ) ) {
761 echo '<div class="notice-warning notice is-dismissible"><p>'. __('Warning: Because NinjaFirewall\'s Login Protection is enabled, Site Health may return an error message regarding the loopback test (e.g., 404 or 401 HTTP status code). You can safely ignore it.', 'ninjafirewall') .'</p></div>';
762 }
763 }
764 }
765 }
766
767 if (nf_not_allowed( 0, __LINE__ ) ) { return; }
768
769 if (! defined('NF_DISABLED') ) {
770 is_nfw_enabled();
771 }
772
773 if (! file_exists(NFW_LOG_DIR . '/nfwlog') ) {
774 @mkdir( NFW_LOG_DIR . '/nfwlog', 0755);
775 @touch( NFW_LOG_DIR . '/nfwlog/index.html');
776 @file_put_contents(NFW_LOG_DIR . '/nfwlog/.htaccess', "Order Deny,Allow\nDeny from all", LOCK_EX);
777 if (! file_exists(NFW_LOG_DIR . '/nfwlog/cache') ) {
778 @mkdir( NFW_LOG_DIR . '/nfwlog/cache', 0755);
779 @touch( NFW_LOG_DIR . '/nfwlog/cache/index.html');
780 @file_put_contents(NFW_LOG_DIR . '/nfwlog/cache/.htaccess', "Order Deny,Allow\nDeny from all", LOCK_EX);
781 }
782 }
783 if (! file_exists(NFW_LOG_DIR . '/nfwlog') ) {
784 echo '<div class="error notice is-dismissible"><p><strong>' . __('NinjaFirewall error', 'ninjafirewall') . ' :</strong> ' .
785 sprintf( __('%s directory cannot be created. Please review your installation and ensure that %s is writable.', 'ninjafirewall'), '<code>'. esc_html(NFW_LOG_DIR) .'/nfwlog/</code>', '<code>/wp-content/</code>') . '</p></div>';
786 }
787 if (! is_writable(NFW_LOG_DIR . '/nfwlog') ) {
788 echo '<div class="error notice is-dismissible"><p><strong>' . __('NinjaFirewall error', 'ninjafirewall') . ' :</strong> ' .
789 sprintf( __('%s directory is read-only. Please review your installation and ensure that %s is writable.', 'ninjafirewall'), '<code>'. esc_html(NFW_LOG_DIR) .'/nfwlog/</code>', '<code>/nfwlog/</code>') . '</p></div>';
790 }
791
792 if (! NF_DISABLED) {
793 return;
794 }
795
796 $nfw_options = nfw_get_option('nfw_options');
797 if ( empty($nfw_options['ret_code']) && NF_DISABLED != 11 ) {
798 return;
799 }
800
801 if (! empty($GLOBALS['err_fw'][NF_DISABLED]) ) {
802 $msg = $GLOBALS['err_fw'][NF_DISABLED];
803 } else {
804 $msg = __('unknown error', 'ninjafirewall') . ' #' . NF_DISABLED;
805 }
806 echo '<div class="error notice is-dismissible"><p><strong>' . __('NinjaFirewall fatal error:', 'ninjafirewall') . '</strong> ' . $msg .
807 '. ' . __('Review your installation, your site is not protected.', 'ninjafirewall') . '</p></div>';
808 }
809
810 add_action('admin_head', 'nfw_hide_admin_notices');
811
812 function nfw_hide_admin_notices() {
813 if ( isset( $_GET['page'] ) && ( $_GET['page'] == 'NinjaFirewall' || preg_match('/^nfsub/', $_GET['page'] ) ) ) {
814 remove_all_actions('admin_notices');
815 remove_all_actions('all_admin_notices');
816 }
817 add_action('all_admin_notices', 'nfw_admin_notice');
818 }
819
820 // ---------------------------------------------------------------------
821
822 function nfw_send_loginemail( $user_login, $whoami ) {
823
824 $nfw_options = nfw_get_option('nfw_options');
825
826 if (! empty( $whoami ) ) {
827 $whoami = " ($whoami)";
828 }
829
830 /**
831 * Email notification.
832 */
833 $subject = [];
834 $content = [ $user_login . $whoami, NFW_REMOTE_ADDR,
835 ucfirst( date_i18n('F j, Y @ H:i:s T') ), home_url('/') ];
836
837 NinjaFirewall_mail::send('user_login', $subject, $content, '', [], 1 );
838 }
839
840 // --------------------------------------------------------------------- s1:h0
841
842 function nfw_query( $query ) {
843
844 if (! empty( NinjaFirewall_session::read('nfw_goodguy') ) || nfw_is_whitelisted() ) {
845 return;
846 }
847
848 $nfw_options = nfw_get_option('nfw_options');
849 // Return if not enabled, or if we are accessing the dashboard (e.g., /wp-admin/edit.php):
850 if ( empty($nfw_options['enum_archives']) || empty($nfw_options['enabled']) || is_admin() ) {
851 return;
852 }
853 if ( $query->is_main_query() && $query->is_author() ) {
854 if ( $query->get('author_name') ) {
855 $tmp = 'author_name=' . $query->get('author_name');
856 } elseif ( $query->get('author') ) {
857 $tmp = 'author=' . $query->get('author');
858 } else {
859 $tmp = 'author';
860 }
861 NinjaFirewall_session::delete();
862 $query->set('author_name', '0');
863
864 NinjaFirewall_log::write(
865 "User enumeration scan (author archives)",
866 $tmp,
867 NFWLOG_HIGH, 0, $nfw_options, NFW_LOG_DIR .'/nfwlog'
868 );
869
870 wp_safe_redirect( home_url('/') );
871 exit;
872 }
873 }
874
875 add_action('pre_get_posts','nfw_query');
876
877 // ---------------------------------------------------------------------
878 add_filter('wp_sitemaps_add_provider', function ($provider, $name) {
879
880 if (! empty( NinjaFirewall_session::read('nfw_goodguy') ) || nfw_is_whitelisted() ) {
881 return $provider;
882 }
883 $nfw_options = nfw_get_option('nfw_options');
884 if ( empty( $nfw_options['enum_sitemap'] ) || empty( $nfw_options['enabled'] ) ) {
885 return $provider;
886 }
887
888 if ( $name == 'users') {
889 return false;
890 }
891 return $provider;
892
893 }, 999, 2);
894 // ---------------------------------------------------------------------
895
896 function nfw_the_author( $display_name ) {
897
898 if (! empty( NinjaFirewall_session::read('nfw_goodguy') ) || nfw_is_whitelisted() ) {
899 return $display_name;
900 }
901 $nfw_options = nfw_get_option('nfw_options');
902 if ( empty( $nfw_options['enum_feed'] ) || empty($nfw_options['enabled']) ) {
903 return $display_name;
904 }
905 if ( is_feed() ) {
906 return '';
907 }
908 return $display_name;
909 }
910
911 add_filter('the_author', 'nfw_the_author', 99999, 1 );
912
913 // ---------------------------------------------------------------------
914
915 function nfw_no_application_passwords() {
916
917 $nfw_options = nfw_get_option('nfw_options');
918 if (! empty( $nfw_options['no_appswd'] ) ) {
919 // We don't log API accesses, only accesses to the script (in firewall.php).
920 return false;
921 }
922 return true;
923 }
924
925 add_filter('wp_is_application_passwords_available', 'nfw_no_application_passwords');
926
927 // --------------------------------------------------------------------- +
928 // REST API access.
929
930 function nfwhook_rest_authentication_errors( $res ) {
931
932 // Whitelisted user?
933 if ( nfw_is_whitelisted() || ! empty( NinjaFirewall_session::read('nfw_goodguy') ) ) {
934 return $res;
935 }
936
937 if (! defined('NF_DISABLED') ) {
938 is_nfw_enabled();
939 }
940 if ( NF_DISABLED ) {
941 return $res;
942 }
943
944 $path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
945 if ( strpos( $path, '/wp-json/wp/v2/pages/') === 0 && current_user_can('edit_pages') ) {
946 return $res;
947 }
948 if ( strpos( $path, '/wp-json/wp/v2/posts/') === 0 && current_user_can('edit_posts') ) {
949 return $res;
950 }
951
952 $nfw_options = nfw_get_option('nfw_options');
953
954 // Allow logged-in users (since 4.5.7)
955 if (! empty( $nfw_options['restapi_loggedin'] ) && is_user_logged_in() ) {
956 return $res;
957 }
958
959 if (! empty( $nfw_options['no_restapi'] ) ) {
960
961 NinjaFirewall_log::write(
962 'WordPress: Blocked access to the WP REST API',
963 $_SERVER['REQUEST_URI'],
964 NFWLOG_HIGH, 0, $nfw_options, NFW_LOG_DIR .'/nfwlog'
965 );
966
967 return new WP_Error(
968 'nfw_rest_api_access_restricted',
969 esc_html__('Forbidden access', 'ninjafirewall'),
970 ['status' => $nfw_options['ret_code'] ]
971 );
972 }
973 return $res;
974 }
975
976 add_filter('rest_authentication_errors', 'nfwhook_rest_authentication_errors');
977
978 // --------------------------------------------------------------------- s1:h0
979
980 function nfwhook_rest_request_before_callbacks( $res, $hnd, $req ) {
981
982 // Whitelisted user?
983 if ( nfw_is_whitelisted() || ! empty( NinjaFirewall_session::read('nfw_goodguy') ) ) {
984 return $res;
985 }
986
987 if (! defined('NF_DISABLED') ) {
988 is_nfw_enabled();
989 }
990 if ( NF_DISABLED ) { return $res; }
991
992 $nfw_options = nfw_get_option('nfw_options');
993
994 if (! empty( $nfw_options['enum_restapi']) ) {
995
996 if ( strpos( $req->get_route(), '/wp/v2/users') !== false && ! current_user_can('list_users') ) {
997
998 NinjaFirewall_log::write(
999 'User enumeration scan (REST API)',
1000 $_SERVER['REQUEST_URI'],
1001 NFWLOG_HIGH, 0, $nfw_options, NFW_LOG_DIR .'/nfwlog'
1002 );
1003
1004 return new WP_Error('nfw_rest_api_access_restricted', __('Forbidden access', 'ninjafirewall'), array('status' => $nfw_options['ret_code']) );
1005 }
1006 }
1007 return $res;
1008 }
1009 add_filter('rest_request_before_callbacks', 'nfwhook_rest_request_before_callbacks', 999, 3);
1010
1011 // ---------------------------------------------------------------------
1012
1013 function nfw_authenticate( $user ) {
1014
1015 $nfw_options = nfw_get_option('nfw_options');
1016
1017 if ( empty( $nfw_options['enum_login']) || empty($nfw_options['enabled']) ) {
1018 return $user;
1019 }
1020
1021 if ( is_wp_error( $user ) ) {
1022 if ( preg_match('/^(?:in(?:correct_password|valid_(?:username|email))|authentication_failed)$/', $user->get_error_code() ) ) {
1023 $lostpass = esc_attr( wp_lostpassword_url() );
1024 $user = new WP_Error('denied',
1025 __('Invalid username, email address or password.', 'ninjafirewall') .
1026 "<br /><a href=\"$lostpass\">".
1027 __('Lost your password?', 'ninjafirewall').
1028 '</a>'
1029 );
1030 add_filter('shake_error_codes', 'nfw_err_shake');
1031 }
1032 }
1033 return $user;
1034 }
1035
1036 add_filter('authenticate', 'nfw_authenticate', 90, 3 );
1037
1038 function nfw_err_shake( $shake_codes ) {
1039 $shake_codes[] = 'denied';
1040 return $shake_codes;
1041 }
1042
1043 // ---------------------------------------------------------------------
1044
1045 function nf_check_dbdata() {
1046
1047 $nfw_options = nfw_get_option('nfw_options');
1048
1049 /**
1050 * Don't do anything if NinjaFirewall is disabled or DB monitoring option is off.
1051 */
1052 if ( empty( $nfw_options['enabled'] ) || empty( $nfw_options['a_51'] ) ) {
1053 return;
1054 }
1055
1056 /**
1057 * Don't run more than once every minute.
1058 */
1059 if ( get_transient('nfw_db_check') !== false ) {
1060 return;
1061 }
1062
1063 /**
1064 * This can be defined in the wp-config.php or .htninja script.
1065 */
1066 if ( defined('NFW_DBCHECK_INTERVAL') ) {
1067 $dbcheck_interval = (int) NFW_DBCHECK_INTERVAL;
1068 if ( $dbcheck_interval < 60 ) {
1069 $dbcheck_interval = 60;
1070 }
1071 } else {
1072 /**
1073 * Default is 60 seconds.
1074 */
1075 $dbcheck_interval = 60;
1076 }
1077
1078 if ( is_multisite() ) {
1079 global $current_blog;
1080 $db_hash = NFW_LOG_DIR .'/nfwlog/cache/db_hash.'. $current_blog->site_id .'-'.
1081 $current_blog->blog_id .'.php';
1082 } else {
1083 global $blog_id;
1084 $db_hash = NFW_LOG_DIR .'/nfwlog/cache/db_hash.'. $blog_id .'.php';
1085 }
1086
1087 $adm_users = nf_get_dbdata();
1088 /**
1089 * Some object caching plugins can return an array with empty keys.
1090 */
1091 if ( empty( $adm_users[0]->user_login ) ) {
1092 set_transient('nfw_db_check', 1, $dbcheck_interval );
1093 return;
1094 }
1095
1096 /**
1097 * Sort by ID to prevent false alerts.
1098 */
1099 usort( $adm_users, 'nfw_sort_by_id');
1100
1101 if (! is_file( $db_hash ) ) {
1102 /**
1103 * We don't have any hash yet, let's create one and quit
1104 * (md5 is faster than sha1 with long strings)
1105 */
1106 @file_put_contents( $db_hash, md5( serialize( $adm_users ) ), LOCK_EX );
1107 set_transient('nfw_db_check', 1, $dbcheck_interval );
1108 return;
1109 }
1110
1111 $old_hash = trim ( file_get_contents( $db_hash ) );
1112 if (! $old_hash ) {
1113 @file_put_contents( $db_hash, md5( serialize( $adm_users ) ), LOCK_EX );
1114 set_transient('nfw_db_check', 1, $dbcheck_interval );
1115 return;
1116 }
1117
1118 /**
1119 * Compare both hashes.
1120 */
1121 if ( $old_hash == md5( serialize( $adm_users ) ) ) {
1122 set_transient('nfw_db_check', 1, $dbcheck_interval );
1123 return;
1124
1125 } else {
1126 /**
1127 * Create or update 60-second transient.
1128 */
1129 set_transient('nfw_db_check', 1, $dbcheck_interval );
1130 /**
1131 * Save the new hash.
1132 */
1133 $tmp = @file_put_contents( $db_hash, md5( serialize( $adm_users ) ), LOCK_EX );
1134 if ( $tmp === FALSE ) {
1135 return;
1136 }
1137
1138 /**
1139 * Retrieve each admin data.
1140 */
1141 $data = '';
1142 foreach( $adm_users as $adm ) {
1143 $data.= "Admin ID: {$adm->ID}\n";
1144 $data.= "-user_login: {$adm->user_login}\n";
1145 $data.= "-user_nicename: {$adm->user_nicename}\n";
1146 $data.= "-user_email: {$adm->user_email}\n";
1147 $data.= "-user_registered: {$adm->user_registered}\n";
1148 $data.= "-display_name: {$adm->display_name}\n\n";
1149 }
1150
1151 /**
1152 * Email notification.
1153 */
1154 $subject = [];
1155 $content = [ home_url('/'), ucfirst( date_i18n('F j, Y @ H:i:s T') ),
1156 count($adm_users), $data ];
1157
1158 NinjaFirewall_mail::send('database_change', $subject, $content, '', [], 1 );
1159
1160 /**
1161 * Log event if required.
1162 */
1163 if (! empty( $nfw_options['a_41'] ) ) {
1164 NinjaFirewall_log::write(
1165 __('Database changes detected', 'ninjafirewall'),
1166 __('administrator account', 'ninjafirewall'),
1167 NFWLOG_POSTDETECT, 0, $nfw_options, NFW_LOG_DIR .'/nfwlog'
1168 );
1169 }
1170 }
1171 }
1172
1173 // ---------------------------------------------------------------------
1174 // Get admin users (we don't want to use get_users()).
1175
1176 function nf_get_dbdata() {
1177
1178 global $wpdb;
1179 return @$wpdb->get_results(
1180 "SELECT {$wpdb->base_prefix}users.ID,{$wpdb->base_prefix}users.user_login,{$wpdb->base_prefix}users.user_pass,{$wpdb->base_prefix}users.user_nicename,{$wpdb->base_prefix}users.user_email,{$wpdb->base_prefix}users.user_registered,{$wpdb->base_prefix}users.display_name
1181 FROM {$wpdb->base_prefix}users
1182 INNER JOIN {$wpdb->base_prefix}usermeta
1183 ON ( {$wpdb->base_prefix}users.ID = {$wpdb->base_prefix}usermeta.user_id )
1184 WHERE 1=1
1185 AND ( ( ( {$wpdb->base_prefix}usermeta.meta_key = '{$wpdb->prefix}capabilities'
1186 AND {$wpdb->base_prefix}usermeta.meta_value LIKE '%\"administrator\"%') ) )"
1187 );
1188 }
1189
1190 // ---------------------------------------------------------------------
1191
1192 function nfw_sort_by_id( $a, $b ) {
1193
1194 return strcmp( $a->ID, $b->ID );
1195 }
1196
1197 // ---------------------------------------------------------------------
1198
1199 function nfw_get_option( $option ) {
1200
1201 if ( is_multisite() ) {
1202 return get_site_option( $option );
1203 } else {
1204 return get_option( $option );
1205 }
1206 }
1207
1208 // ---------------------------------------------------------------------
1209
1210 function nfw_update_option( $option, $new_value, $autoload = null ) {
1211
1212 if ( is_multisite() ) {
1213 update_site_option( $option, $new_value );
1214 }
1215 return update_option( $option, $new_value, $autoload );
1216 }
1217
1218 // ---------------------------------------------------------------------
1219
1220 function nfw_delete_option( $option ) {
1221
1222 if ( is_multisite() ) {
1223 delete_site_option( $option );
1224 }
1225 return delete_option( $option );
1226 }
1227
1228 // ---------------------------------------------------------------------
1229 // Make sure nfw_options is valid.
1230
1231 function nfw_validate_option( $value ) {
1232
1233 if (! isset( $value['enabled'] ) || ! isset( $value['blocked_msg'] ) ||
1234 ! isset( $value['logo'] ) || ! isset( $value['ret_code'] ) ||
1235 ! isset( $value['scan_protocol'] ) || ! isset( $value['get_scan'] ) ) {
1236
1237 // Data is corrupted:
1238 return false;
1239 }
1240
1241 return true;
1242 }
1243
1244 // ---------------------------------------------------------------------
1245
1246 function nfwhook_update_user_meta( $user_id, $meta_key, $meta_value, $prev_value ) {
1247
1248 nfwhook_user_meta( $meta_key, $meta_value, $prev_value );
1249
1250 }
1251 add_filter('update_user_meta', 'nfwhook_update_user_meta', 1, 4);
1252
1253 // ---------------------------------------------------------------------
1254
1255 function nfwhook_add_user_meta( $user_id, $meta_key, $meta_value ) {
1256
1257 nfwhook_user_meta( $user_id, $meta_key, $meta_value );
1258
1259 }
1260 add_filter('add_user_meta', 'nfwhook_add_user_meta', 1, 3);
1261
1262 // ---------------------------------------------------------------------
1263
1264 function nfwhook_user_meta( $id, $key, $value ) {
1265
1266 if (! defined('NF_DISABLED') ) {
1267 is_nfw_enabled();
1268 }
1269
1270 $nfw_options = nfw_get_option('nfw_options');
1271
1272 /**
1273 * Note: "NFW_DISABLE_PRVESC2" is now deprecated. Use the corresponding
1274 * firewall policy to disable it instead.
1275 */
1276 if ( NF_DISABLED || defined('NFW_DISABLE_PRVESC2') ||
1277 empty( $nfw_options['disallow_privesc'] ) ) {
1278
1279 return;
1280 }
1281
1282 global $wpdb;
1283
1284 if ( is_array( $key ) ) {
1285 $key = serialize( $key );
1286 }
1287
1288 /**
1289 * "current_user_can" must remain here,
1290 * see https://wordpress.org/support/topic/rest-api-problem-2/page/2/#post-11789636
1291 */
1292 if ( preg_match( "/{$wpdb->base_prefix}([0-9]+_)?capabilities/", $key ) &&
1293 ! current_user_can('edit_users') ) {
1294
1295 if ( is_array( $value ) ) {
1296 $value = serialize( $value );
1297 }
1298
1299 if ( strpos( $value, 's:13:"administrator"') === FALSE &&
1300 strpos( $value, 's:6:"editor"') === FALSE &&
1301 strpos( $value, 's:12:"shop_manager"') === FALSE &&
1302 strpos( $value, 's:13:"bbp_keymaster"') === FALSE ) {
1303
1304 return;
1305 }
1306 /**
1307 * If it's a subsite in a network, check what we are supposed to do.
1308 */
1309 if ( is_main_site() !== true && empty( $nfw_options['disallow_privesc_mu'] ) ) {
1310 return;
1311 }
1312
1313 $user_info = get_userdata( $id );
1314 $whoisit = '';
1315 $check_user = [
1316 'subscriber', 'contributor', 'author', 'customer', 'bbp_participant', 'bbp_spectator'
1317 ];
1318 foreach( $user_info->roles as $k => $v ) {
1319 if ( in_array( $v, $check_user ) ) {
1320 $whoisit = $v;
1321 break;
1322 }
1323 }
1324 if ( empty( $whoisit ) && ! empty( $user_info->roles ) ) {
1325 return;
1326 }
1327
1328 if ( strlen( $value ) > 200 ) {
1329 $value = mb_substr( $value, 0, 200, 'utf-8') . '...';
1330 }
1331 $subject = __('Blocked privilege escalation attempt', 'ninjafirewall');
1332
1333 NinjaFirewall_log::write(
1334 "WordPress: $subject",
1335 "$key: $value",
1336 NFWLOG_CRITICAL, 0, $nfw_options, NFW_LOG_DIR .'/nfwlog'
1337 );
1338
1339 if (! empty( $user_info->user_login ) ) {
1340 $username = "{$user_info->user_login}, ID: $id";
1341 } else {
1342 $usename = '-';
1343 }
1344
1345 /**
1346 * Backtrace.
1347 */
1348 $return = nfw_debug_backtrace( $nfw_options );
1349 if (! empty( $return['nftmpfname'] ) ) {
1350 $attachment = $return['nftmpfname'];
1351 } else {
1352 $attachment = [];
1353 }
1354
1355 /**
1356 * Email notification.
1357 */
1358 $subject = [];
1359 $content = [ home_url('/'), $username, $key, $value, NFW_REMOTE_ADDR,
1360 $_SERVER['SCRIPT_FILENAME'], $_SERVER['REQUEST_URI'],
1361 date_i18n('F j, Y @ H:i:s T') , $return['message'] ];
1362 NinjaFirewall_mail::send('privilege_escalation', $subject, $content, '', $attachment, 1 );
1363
1364 /**
1365 * Block the request.
1366 */
1367 NinjaFirewall_session::delete();
1368 wp_die(
1369 'NinjaFirewall: '. __('You are not allowed to perform this task.', 'ninjafirewall'),
1370 'NinjaFirewall: '. __('You are not allowed to perform this task.', 'ninjafirewall'),
1371 $nfw_options['ret_code']
1372 );
1373 }
1374 }
1375
1376 // --------------------------------------------------------------------- s1:h0
1377
1378 function nfw_login_form_hook( $message ) {
1379
1380 if (! empty( NinjaFirewall_session::read('nfw_bfd') ) ) {
1381 return '<p class="message" id="nfw_login_msg">'.
1382 esc_html__('NinjaFirewall brute-force protection is enabled and you are temporarily whitelisted.',
1383 'ninjafirewall') .'</p><br />';
1384 }
1385 return $message;
1386 }
1387 add_filter('login_message', 'nfw_login_form_hook');
1388
1389 // ---------------------------------------------------------------------
1390
1391 function nfw_rate_notice( $nfw_options ) {
1392
1393 // Display a one-time notice after two weeks of use:
1394 $now = time();
1395 if (! empty( $nfw_options['rate_notice'] ) && $nfw_options['rate_notice'] < $now ) {
1396
1397 echo '<div class="notice-info notice is-dismissible"><p>'. sprintf(
1398 __('Hey, it seems that you\'ve been using NinjaFirewall for some time. If you like it, please take <a href="%s">the time to rate it</a>. It took thousand of hours to develop it, but it takes only a couple of minutes to rate it. Thank you!', 'ninjafirewall'),
1399 'https://wordpress.org/support/view/plugin-reviews/ninjafirewall?rate=5#postform'
1400 ) .'</p></div>';
1401
1402 // Clear the reminder flag:
1403 unset( $nfw_options['rate_notice'] );
1404 // Update options:
1405 nfw_update_option('nfw_options', $nfw_options );
1406 }
1407
1408 }
1409
1410 // --------------------------------------------------------------------- s1:h1
1411
1412 function nfw_session_debug() {
1413
1414 // Make sure NinjaFirewall is running :
1415 if (! defined('NF_DISABLED') ) {
1416 is_nfw_enabled();
1417 }
1418 if ( NF_DISABLED ) { return; }
1419
1420 $show_session_icon = 0;
1421 $current_user = wp_get_current_user();
1422 // Check users first:
1423 if ( defined('NFW_SESSION_DEBUG_USER') ) {
1424 $users = explode(',', NFW_SESSION_DEBUG_USER );
1425 foreach ( $users as $user ) {
1426 if ( trim( $user ) == $current_user->user_login ) {
1427 $show_session_icon = 1;
1428 break;
1429 }
1430 }
1431 // Check capabilities:
1432 } elseif ( defined('NFW_SESSION_DEBUG_CAPS') ) {
1433 $caps = explode(',', NFW_SESSION_DEBUG_CAPS );
1434 foreach ( $caps as $cap ) {
1435 if (! empty( $current_user->caps[ trim( $cap ) ] ) ) {
1436 $show_session_icon = 1;
1437 break;
1438 }
1439 }
1440 }
1441
1442 if ( empty( $show_session_icon ) ) { return; }
1443
1444 // Check if the user whitelisted?
1445 if ( empty( NinjaFirewall_session::read('nfw_goodguy') ) ) {
1446 // No
1447 $font = 'ff0000';
1448 } else {
1449 // Yes
1450 $font = '00ff00';
1451 }
1452
1453 global $wp_admin_bar;
1454 $wp_admin_bar->add_menu( array(
1455 'id' => 'nfw_session_dbg',
1456 'title' => "<font color='#{$font}'>NF</font>"
1457 ) );
1458
1459 }
1460
1461 // Check if the session debug option is enabled:
1462 if ( defined('NFW_SESSION_DEBUG_USER') || defined('NFW_SESSION_DEBUG_CAPS') ) {
1463 add_action('admin_bar_menu', 'nfw_session_debug', 500 );
1464 }
1465
1466 // ---------------------------------------------------------------------
1467
1468 function nf_monitor_options( $value, $option, $old_value ) {
1469
1470 // Admin check is done in nfw_load_optmon().
1471
1472 // We're not interested in any object
1473 if ( is_object( $value ) || is_object( $old_value ) ) {
1474 return $value;
1475 }
1476
1477 // Similarly to https://core.trac.wordpress.org/ticket/38903, an integer will
1478 // trigger a DB UPDATE query even if it matches the character stored in the DB
1479 // (e.g.: 0 vs '0'). We must not block that, hence will use '===' only on arrays
1480 // (and that will prevent "Nesting level too deep" error as well):
1481 if ( is_array( $value ) ) {
1482 if ( $value === $old_value ) {
1483 return $value;
1484 }
1485 } else {
1486 // Simple comparison operator for integers and strings:
1487 if ( $value == $old_value ) {
1488 return $value;
1489 }
1490 }
1491
1492 $nfw_options = nfw_get_option('nfw_options');
1493
1494 if ( empty( $nfw_options['enabled'] ) || empty( $nfw_options['disallow_settings'] ) ) {
1495 return $value;
1496 }
1497
1498 // User-defined exclusion list (undocumented), NF options/rules (which are protected
1499 // by the firewall):
1500 if ( ( defined('NFW_OPTMON_EXCLUDE') && strpos( NFW_OPTMON_EXCLUDE, $option ) !== false ) ||
1501 $option === 'nfw_options' || $option === 'nfw_rules') {
1502
1503 return $value;
1504 }
1505
1506 global $wpdb;
1507 $monitor = array(
1508 'admin_email',
1509 'blog_public',
1510 'blogdescription',
1511 'blogname',
1512 'comment_moderation',
1513 'comments_notify',
1514 'comment_registration',
1515 'default_role',
1516 'home',
1517 'mailserver_login',
1518 'siteurl',
1519 'template',
1520 'stylesheet',
1521 'users_can_register'
1522 );
1523
1524 // No changes detected or not what we are looking for:
1525 if (! in_array( $option, $monitor ) ) {
1526 return $value;
1527 }
1528
1529 if ( is_array( $value ) ) {
1530 $tmp = serialize( $value );
1531 $value = '';
1532 if ( strlen( $tmp ) > 200 ) { $tmp = mb_substr( $tmp, 0, 200, 'utf-8') . '...'; }
1533 $value = $tmp;
1534 }
1535 if ( is_array( $old_value ) ) {
1536 $tmp = serialize( $old_value );
1537 $old_value = '';
1538 if ( strlen( $tmp ) > 200 ) { $tmp = mb_substr( $tmp, 0, 200, 'utf-8') . '...'; }
1539 $old_value = $tmp;
1540 }
1541
1542 // Send a notification to the admin:
1543 nf_monitor_options_alert( $option, $value, $old_value, 'settings');
1544
1545 /**
1546 * Log the request.
1547 */
1548 NinjaFirewall_log::write(
1549 'Blocked attempt to modify WordPress settings',
1550 "option: $option, value: $value",
1551 NFWLOG_CRITICAL, 0, $nfw_options, NFW_LOG_DIR .'/nfwlog'
1552 );
1553
1554 // Since 4.0.3 we don't close the connection anymore but
1555 // we block the modification by returning the previous value
1556 return $old_value;
1557 }
1558
1559 // ---------------------------------------------------------------------
1560
1561 function nfw_load_optmon() {
1562
1563 if (! nfw_is_whitelisted() && ! current_user_can('manage_options') ) {
1564 add_filter('pre_update_option', 'nf_monitor_options', 10, 3 );
1565 }
1566 }
1567
1568 add_action('plugins_loaded', 'nfw_load_optmon');
1569
1570 // ---------------------------------------------------------------------
1571 // $type = settings or injection.
1572
1573 function nf_monitor_options_alert( $option, $value, $old_value, $type ) {
1574
1575 $nfw_options = nfw_get_option('nfw_options');
1576
1577 /**
1578 * Backtrace.
1579 */
1580 $return = nfw_debug_backtrace( $nfw_options );
1581 if (! empty( $return['nftmpfname'] ) ) {
1582 $attachment = $return['nftmpfname'];
1583 } else {
1584 $attachment = [];
1585 }
1586
1587 /**
1588 * Email notification.
1589 */
1590 $subject = [];
1591 $content = [ $option, $old_value, $value, home_url('/'), NFW_REMOTE_ADDR,
1592 $_SERVER['SCRIPT_FILENAME'], $_SERVER['REQUEST_URI'],
1593 date_i18n('F j, Y @ H:i:s T'), $return['message'] ];
1594 NinjaFirewall_mail::send('wp_settings', $subject, $content, '', $attachment, 1 );
1595 }
1596
1597 // ---------------------------------------------------------------------
1598 // Attach a backtrace if required.
1599
1600 function nfw_debug_backtrace( $nfw_options ) {
1601
1602 $return = array();
1603 $return['message'] = '';
1604 $verbosity = nfw_verbosity( $nfw_options );
1605 if ( $verbosity !== false ) {
1606 $return['nftmpfname'] = NFW_LOG_DIR .'/nfwlog/backtrace_'. bin2hex( random_bytes( 8 ) ) .'.txt';
1607 $dbg = debug_backtrace( $verbosity );
1608 array_shift( $dbg );
1609 file_put_contents( $return['nftmpfname'], print_r( $dbg, true ) );
1610 $return['message'] = __('A PHP backtrace has been attached to this message for your convenience.', 'ninjafirewall') . "\n\n";
1611 }
1612 return $return;
1613 }
1614
1615 // ---------------------------------------------------------------------
1616 // Activate WPWAF mode.
1617
1618 function nfw_enable_wpwaf() {
1619
1620 if ( file_exists( WPMU_PLUGIN_DIR .'/'. NINJAFIREWALL_MU_PLUGIN ) ) {
1621 // Quick files comparison. We used md5 as we're only looking for changes,
1622 // i.e., if there was an update.
1623 if ( md5_file( WPMU_PLUGIN_DIR .'/'. NINJAFIREWALL_MU_PLUGIN) === md5_file( __DIR__ .'/loader.php') ) {
1624 return;
1625 }
1626 }
1627
1628 if (! is_dir( WPMU_PLUGIN_DIR ) ) {
1629 if (! @mkdir( WPMU_PLUGIN_DIR, 0755, true ) ) {
1630 return sprintf(
1631 esc_html__('Error, cannot create the %s folder.', 'ninjafirewall') .' '.
1632 esc_html__('Check your server permissions and try again.', 'ninjafirewall'),
1633 esc_html( WPMU_PLUGIN_DIR )
1634 );
1635 }
1636 }
1637
1638 if (! is_writable( WPMU_PLUGIN_DIR ) ) {
1639 return sprintf(
1640 esc_html__('Error, the %s folder is not writable.', 'ninjafirewall') .' '.
1641 esc_html__('Check your server permissions and try again.', 'ninjafirewall'),
1642 esc_html( WPMU_PLUGIN_DIR )
1643 );
1644 }
1645
1646 @copy( __DIR__ .'/loader.php', WPMU_PLUGIN_DIR .'/'. NINJAFIREWALL_MU_PLUGIN);
1647 if (! file_exists( WPMU_PLUGIN_DIR .'/'. NINJAFIREWALL_MU_PLUGIN) ) {
1648 return sprintf(
1649 esc_html__('Error, cannot write %s.', 'ninjafirewall') .' '.
1650 esc_html__('Check your server permissions and try again.', 'ninjafirewall'),
1651 esc_html( WPMU_PLUGIN_DIR .'/'. NINJAFIREWALL_MU_PLUGIN)
1652 );
1653 }
1654
1655 return;
1656 }
1657
1658 // ---------------------------------------------------------------------
1659 // Deactivate WPFAF mode.
1660
1661 function nfw_disable_wpwaf() {
1662
1663 if ( file_exists( WPMU_PLUGIN_DIR .'/'. NINJAFIREWALL_MU_PLUGIN) ) {
1664 unlink( WPMU_PLUGIN_DIR .'/'. NINJAFIREWALL_MU_PLUGIN);
1665 }
1666 }
1667
1668 // ---------------------------------------------------------------------
1669 function nfw_dropins() {
1670
1671 $nfw_options = nfw_get_option('nfw_options');
1672 if ( empty( $nfw_options['enabled'] ) ) { return; }
1673
1674 if ( file_exists( NFW_LOG_DIR .'/nfwlog/dropins.php') ) {
1675 @include_once NFW_LOG_DIR .'/nfwlog/dropins.php';
1676 }
1677 }
1678
1679 add_action('plugins_loaded', 'nfw_dropins', -1);
1680
1681 // ---------------------------------------------------------------------
1682 // For WP <4.9.
1683
1684 if (! function_exists('wp_readonly') ) {
1685 function wp_readonly( $var, $val) {
1686 if ( $var == $val ) {
1687 echo " readonly='readonly'";
1688 }
1689 }
1690 }
1691
1692 // ---------------------------------------------------------------------
1693 // Used to display the toggle/switch's status to screenreaders.
1694 function nfw_aria_label( $var, $val, $text_on, $text_off ) {
1695 if ( $var == $val ) {
1696 echo " aria-label='$text_on'";
1697 } else {
1698 echo " aria-label='$text_off'";
1699 }
1700 }
1701 // ---------------------------------------------------------------------
1702 // EOF
1703