iThemes Security (formerly Better WP Security) - Version 5.3.7

Version Description

  • Bug Fix: Throw a real 403 instead of a faked 404 for hide backend - Fixes compatability with certain plugins including WordPress SEO. Hat tip to Joost de Valk (@jdevalk) and the @Yoast team for bringing this issue to our attention.
Download this release

Release Info

Developer aaroncampbell
Plugin Icon 128x128 iThemes Security (formerly Better WP Security)
Version 5.3.7
Comparing to
See all releases

Code changes from version 5.2.1 to 5.3.7

Files changed (33) hide show
  1. better-wp-security.php +2 -2
  2. core/class-ithemes-sync-verb-itsec-set-temp-whitelist.php +6 -1
  3. core/class-itsec-core.php +17 -8
  4. core/class-itsec-files.php +7 -2
  5. core/class-itsec-global-settings.php +18 -18
  6. core/class-itsec-lib.php +39 -220
  7. core/class-itsec-lockout.php +9 -129
  8. core/class-itsec-logger-all-logs.php +7 -1
  9. core/class-itsec-logger.php +127 -77
  10. core/class-itsec-setup.php +45 -1
  11. core/history.txt +42 -0
  12. core/js/admin-dashboard.js +1 -1
  13. core/lib/class-itsec-lib-config-file.php +10 -0
  14. core/lib/class-itsec-lib-ip-tools.php +649 -0
  15. core/lib/class-itsec-lib-utility.php +2 -0
  16. core/modules/away-mode/class-ithemes-sync-verb-itsec-get-away-mode.php +3 -0
  17. core/modules/backup/class-itsec-backup.php +9 -10
  18. core/modules/ban-users/class-itsec-ban-users-admin.php +36 -25
  19. core/modules/ban-users/class-itsec-ban-users.php +1 -97
  20. core/modules/brute-force/class-itsec-brute-force-log.php +7 -1
  21. core/modules/content-directory/class-itsec-content-directory-admin.php +6 -1
  22. core/modules/core/class-itsec-core-admin.php +2 -2
  23. core/modules/file-change/class-itsec-file-change-admin.php +22 -14
  24. core/modules/hide-backend/class-itsec-hide-backend-admin.php +5 -5
  25. core/modules/hide-backend/class-itsec-hide-backend.php +3 -15
  26. core/modules/ipcheck/class-itsec-ipcheck.php +4 -4
  27. core/modules/malware/class-itsec-malware-admin.php +2 -2
  28. core/modules/malware/class-itsec-malware-scan-results-template.php +2 -1
  29. core/modules/malware/class-itsec-malware-scanner.php +9 -11
  30. core/modules/malware/css/malware.css +2 -0
  31. core/modules/tweaks/class-itsec-tweaks.php +9 -20
  32. history.txt +38 -0
  33. readme.txt +84 -24
better-wp-security.php CHANGED
@@ -3,10 +3,10 @@
3
  /*
4
  * Plugin Name: iThemes Security
5
  * Plugin URI: https://ithemes.com/security
6
- * Description: Protect your WordPress site by hiding vital areas of your site, protecting access to important files, preventing brute-force login attempts, detecting attack attempts and more.
7
  * Author: iThemes
8
  * Author URI: https://ithemes.com
9
- * Version: 5.2.1
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
3
  /*
4
  * Plugin Name: iThemes Security
5
  * Plugin URI: https://ithemes.com/security
6
+ * Description: Take the guesswork out of WordPress security. iThemes Security offers 30+ ways to lock down WordPress in an easy-to-use WordPress security plugin.
7
  * Author: iThemes
8
  * Author URI: https://ithemes.com
9
+ * Version: 5.3.7
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
core/class-ithemes-sync-verb-itsec-set-temp-whitelist.php CHANGED
@@ -24,7 +24,12 @@ class Ithemes_Sync_Verb_ITSEC_Set_Temp_Whitelist extends Ithemes_Sync_Verb {
24
 
25
  $ip = sanitize_text_field( $arguments['ip'] );
26
 
27
- if ( ITSEC_Lib::validates_ip_address( $ip ) ) {
 
 
 
 
 
28
 
29
  $response = array(
30
  'ip' => $ip,
24
 
25
  $ip = sanitize_text_field( $arguments['ip'] );
26
 
27
+ if ( ! class_exists( 'ITSEC_Lib_IP_Tools' ) ) {
28
+ $itsec_core = ITSEC_Core::get_instance();
29
+ require_once( dirname( $itsec_core->get_plugin_file() ) . '/core/lib/class-itsec-lib-ip-tools.php' );
30
+ }
31
+
32
+ if ( ITSEC_Lib_IP_Tools::validate( $ip ) ) {
33
 
34
  $response = array(
35
  'ip' => $ip,
core/class-itsec-core.php CHANGED
@@ -29,11 +29,11 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
29
  $pages,
30
  $pro_toc_items,
31
  $tracking_vars,
32
- $toc_items,
33
- $_plugin_file;
34
 
35
  public
36
- $available_pages;
 
37
 
38
  /**
39
  * Private constructor to make this a singleton
@@ -58,7 +58,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
58
  *
59
  */
60
  public function init( $plugin_file, $plugin_name ) {
61
- $this->_plugin_file = $plugin_file;
62
 
63
  global $itsec_globals, $itsec_files, $itsec_logger, $itsec_lockout, $itsec_notify, $itsec_sync;
64
 
@@ -91,7 +91,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
91
 
92
  //Set plugin defaults
93
  $itsec_globals = array(
94
- 'plugin_build' => 4038, //plugin build number - used to trigger updates
95
  'plugin_access_lvl' => 'manage_options', //Access level required to access plugin options
96
  'plugin_name' => sanitize_text_field( $plugin_name ), //the name of the plugin
97
  'plugin_base' => str_replace( WP_PLUGIN_DIR . '/', '', $plugin_file ),
@@ -755,9 +755,10 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
755
  wp_enqueue_script( 'jquery-ui-dialog' );
756
  wp_enqueue_style( 'jquery-ui-tabs' );
757
  wp_enqueue_style( 'wp-jquery-ui-dialog' );
758
- wp_enqueue_script( 'itsec_dashboard_js', $itsec_globals['plugin_url'] . 'core/js/admin-dashboard.js', array( 'jquery' ) );
759
  wp_localize_script( 'itsec_dashboard_js', 'itsec_dashboard', array(
760
  'text' => __( 'Show Intro', 'better-wp-security' ),
 
761
  ) );
762
  wp_enqueue_script( 'itsec_footer', $itsec_globals['plugin_url'] . 'core/js/admin-dashboard-footer.js', array( 'jquery' ), $itsec_globals['plugin_build'], true );
763
 
@@ -1122,7 +1123,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
1122
 
1123
  foreach ( $active_plugins as $active_plugin ) {
1124
  $file = basename( $active_plugin );
1125
-
1126
  if ( in_array( $file, array( 'better-wp-security.php', 'ithemes-security-pro.php' ) ) ) {
1127
  return;
1128
  }
@@ -1562,7 +1563,15 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
1562
  }
1563
 
1564
  public function get_plugin_file() {
1565
- return $this->_plugin_file;
 
 
 
 
 
 
 
 
1566
  }
1567
 
1568
  }
29
  $pages,
30
  $pro_toc_items,
31
  $tracking_vars,
32
+ $toc_items;
 
33
 
34
  public
35
+ $available_pages,
36
+ $plugin_file;
37
 
38
  /**
39
  * Private constructor to make this a singleton
58
  *
59
  */
60
  public function init( $plugin_file, $plugin_name ) {
61
+ $this->plugin_file = $plugin_file;
62
 
63
  global $itsec_globals, $itsec_files, $itsec_logger, $itsec_lockout, $itsec_notify, $itsec_sync;
64
 
91
 
92
  //Set plugin defaults
93
  $itsec_globals = array(
94
+ 'plugin_build' => 4040, //plugin build number - used to trigger updates
95
  'plugin_access_lvl' => 'manage_options', //Access level required to access plugin options
96
  'plugin_name' => sanitize_text_field( $plugin_name ), //the name of the plugin
97
  'plugin_base' => str_replace( WP_PLUGIN_DIR . '/', '', $plugin_file ),
755
  wp_enqueue_script( 'jquery-ui-dialog' );
756
  wp_enqueue_style( 'jquery-ui-tabs' );
757
  wp_enqueue_style( 'wp-jquery-ui-dialog' );
758
+ wp_enqueue_script( 'itsec_dashboard_js', $itsec_globals['plugin_url'] . 'core/js/admin-dashboard.js', array( 'jquery' ), '20160322' );
759
  wp_localize_script( 'itsec_dashboard_js', 'itsec_dashboard', array(
760
  'text' => __( 'Show Intro', 'better-wp-security' ),
761
+ 'url' => esc_url( add_query_arg( array( 'show_admin_modal' => 'true' ) ) ),
762
  ) );
763
  wp_enqueue_script( 'itsec_footer', $itsec_globals['plugin_url'] . 'core/js/admin-dashboard-footer.js', array( 'jquery' ), $itsec_globals['plugin_build'], true );
764
 
1123
 
1124
  foreach ( $active_plugins as $active_plugin ) {
1125
  $file = basename( $active_plugin );
1126
+
1127
  if ( in_array( $file, array( 'better-wp-security.php', 'ithemes-security-pro.php' ) ) ) {
1128
  return;
1129
  }
1563
  }
1564
 
1565
  public function get_plugin_file() {
1566
+ return $this->plugin_file;
1567
+ }
1568
+
1569
+ public static function required_cap() {
1570
+ return apply_filters( 'itsec_cap_required', is_multisite() ? 'manage_network_options' : 'manage_options' );
1571
+ }
1572
+
1573
+ public static function current_user_can_manage() {
1574
+ return current_user_can( self::required_cap() );
1575
  }
1576
 
1577
  }
core/class-itsec-files.php CHANGED
@@ -400,8 +400,13 @@ final class ITSEC_Files {
400
  */
401
  public static function quick_ban( $host ) {
402
  $host = trim( $host );
403
-
404
- if ( ! ITSEC_Lib::validates_ip_address( $host ) ) {
 
 
 
 
 
405
  return false;
406
  }
407
 
400
  */
401
  public static function quick_ban( $host ) {
402
  $host = trim( $host );
403
+
404
+ if ( ! class_exists( 'ITSEC_Lib_IP_Tools' ) ) {
405
+ $itsec_core = ITSEC_Core::get_instance();
406
+ require_once( dirname( $itsec_core->get_plugin_file() ) . '/core/lib/class-itsec-lib-ip-tools.php' );
407
+ }
408
+
409
+ if ( ! ITSEC_Lib_IP_Tools::validate( $host ) ) {
410
  return false;
411
  }
412
 
core/class-itsec-global-settings.php CHANGED
@@ -626,13 +626,18 @@ class ITSEC_Global_Settings {
626
  $bad_white_listed_ips = array();
627
  $raw_white_listed_ips = array();
628
 
629
- foreach ( $white_listed_addresses as $index => $address ) {
 
 
 
630
 
631
- $address = trim( $address );
 
 
632
 
633
  if ( strlen( trim( $address ) ) > 0 ) {
634
 
635
- if ( ITSEC_Lib::validates_ip_address( $address ) === false ) {
636
 
637
  $bad_white_listed_ips[] = filter_var( $address, FILTER_SANITIZE_STRING );
638
 
@@ -653,20 +658,12 @@ class ITSEC_Global_Settings {
653
  if ( sizeof( $bad_white_listed_ips ) > 0 ) {
654
 
655
  $type = 'error';
656
- $message = '';
657
 
658
- $message .= sprintf(
659
- '%s<br /><br />',
660
- __( 'There is a problem with an IP address in the white list:', 'better-wp-security' )
661
- );
662
 
663
  foreach ( $bad_white_listed_ips as $bad_ip ) {
664
 
665
- $message .= sprintf(
666
- '%s %s<br />',
667
- $bad_ip,
668
- __( 'is not a valid address in the white list users box.', 'better-wp-security' )
669
- );
670
 
671
  }
672
 
@@ -1097,13 +1094,16 @@ class ITSEC_Global_Settings {
1097
  echo '<p class="submit"><a href="' . PHP_EOL . ITSEC_Lib::get_ip() . '" class="itsec_add_ip_to_whitelist button-primary">' . __( 'Add my current IP to Whitelist', 'better-wp-security' ) . '</a></p>';
1098
  echo '<p class="description">' . __( 'Use the guidelines below to enter hosts that will not be locked out from your site. This will keep you from locking yourself out of any features if you should trigger a lockout. Please note this does not override away mode and will only prevent a temporary ban. Should a permanent ban be triggered you will still be added to the "Ban Users" list unless the IP address is also white listed in that section.', 'better-wp-security' ) . '</p>';
1099
  echo '<ul>';
1100
- echo '<li>' . __( 'You may white list users by individual IP address or IP address range.', 'better-wp-security' ) . '</li>';
1101
- echo '<li>' . __( 'Individual IP addesses must be in IPV4 standard format (i.e. ###.###.###.### or ###.###.###.###/##). Wildcards (*) or a netmask is allowed to specify a range of ip addresses.', 'better-wp-security' ) . '</li>';
1102
- echo '<li>' . __( 'If using a wildcard (*) you must start with the right-most number in the ip field. For example ###.###.###.* and ###.###.*.* are permitted but ###.###.*.### is not.', 'better-wp-security' ) . '</li>';
1103
- echo '<li><a href="http://ip-lookup.net/domain-lookup.php" target="_blank">' . __( 'Lookup IP Address.', 'better-wp-security' ) . '</a></li>';
 
 
1104
  echo '<li>' . __( 'Enter only 1 IP address or 1 IP address range per line.', 'better-wp-security' ) . '</li>';
1105
  echo '</ul>';
1106
- echo '<p class="description"><strong>' . __( 'This white list will prevent any ip listed from triggering an automatic lockout. You can still block the IP address manually in the banned users settings.', 'better-wp-security' ) . '</strong></p>';
 
1107
 
1108
  }
1109
 
626
  $bad_white_listed_ips = array();
627
  $raw_white_listed_ips = array();
628
 
629
+ if ( ! class_exists( 'ITSEC_Lib_IP_Tools' ) ) {
630
+ $itsec_core = ITSEC_Core::get_instance();
631
+ require_once( dirname( $itsec_core->get_plugin_file() ) . '/core/lib/class-itsec-lib-ip-tools.php' );
632
+ }
633
 
634
+ foreach ( $white_listed_addresses as $index => $address ) {
635
+ // Convert wildcard IPs to CIDR notation
636
+ $address = ITSEC_Lib_IP_Tools::ip_wild_to_ip_cidr( trim( $address ) );
637
 
638
  if ( strlen( trim( $address ) ) > 0 ) {
639
 
640
+ if ( ITSEC_Lib_IP_Tools::validate( $address ) === false ) {
641
 
642
  $bad_white_listed_ips[] = filter_var( $address, FILTER_SANITIZE_STRING );
643
 
658
  if ( sizeof( $bad_white_listed_ips ) > 0 ) {
659
 
660
  $type = 'error';
 
661
 
662
+ $message = __( 'There is a problem with an IP address in the white list:', 'better-wp-security' ) . '<br /><br />';
 
 
 
663
 
664
  foreach ( $bad_white_listed_ips as $bad_ip ) {
665
 
666
+ $message .= sprintf( __( '%s is not a valid address in the white list users box.', 'better-wp-security' ), $bad_ip ) . '<br />';
 
 
 
 
667
 
668
  }
669
 
1094
  echo '<p class="submit"><a href="' . PHP_EOL . ITSEC_Lib::get_ip() . '" class="itsec_add_ip_to_whitelist button-primary">' . __( 'Add my current IP to Whitelist', 'better-wp-security' ) . '</a></p>';
1095
  echo '<p class="description">' . __( 'Use the guidelines below to enter hosts that will not be locked out from your site. This will keep you from locking yourself out of any features if you should trigger a lockout. Please note this does not override away mode and will only prevent a temporary ban. Should a permanent ban be triggered you will still be added to the "Ban Users" list unless the IP address is also white listed in that section.', 'better-wp-security' ) . '</p>';
1096
  echo '<ul>';
1097
+ echo '<li>' . __( 'You may white list users by individual IP address or IP address range using wildcards or CIDR notation.', 'better-wp-security' ) . '</li>';
1098
+ echo '<ul>';
1099
+ echo '<li>' . __( 'Individual IP addresses must be in IPv4 or IPv6 standard format (###.###.###.### or ####:####:####:####:####:####:####:####).', 'better-wp-security' ) . '</li>';
1100
+ echo '<li>' . __( 'CIDR notation is allowed to specify a range of IP addresses (###.###.###.###/## or ####:####:####:####:####:####:####:####/###).', 'better-wp-security' ) . '</li>';
1101
+ echo '<li>' . __( 'Wildcards are also supported with some limitations. If using wildcards (*), you must start with the right-most chunk in the IP address. For example ###.###.###.* and ###.###.*.* are permitted but ###.###.*.### is not. Wildcards are only for convenient entering of IP addresses, and will be automatically converted to their appropriate CIDR notation format on save.', 'better-wp-security' ) . '</li>';
1102
+ echo '</ul>';
1103
  echo '<li>' . __( 'Enter only 1 IP address or 1 IP address range per line.', 'better-wp-security' ) . '</li>';
1104
  echo '</ul>';
1105
+ echo '<p><a href="http://ip-lookup.net/domain-lookup.php" target="_blank">' . __( 'Lookup IP Address.', 'better-wp-security' ) . '</a></p>';
1106
+ echo '<p class="description"><strong>' . __( 'This white list will prevent any IP listed from triggering an automatic lockout. You can still block the IP address manually in the banned users settings.', 'better-wp-security' ) . '</strong></p>';
1107
 
1108
  }
1109
 
core/class-itsec-lib.php CHANGED
@@ -10,40 +10,6 @@
10
  * @since 4.0.0
11
  */
12
  final class ITSEC_Lib {
13
-
14
- /**
15
- * Converts CIDR to ip range.
16
- *
17
- * Modified from function at http://stackoverflow.com/questions/4931721/getting-list-ips-from-cidr-notation-in-php
18
- * as it was far more elegant than my own solution
19
- *
20
- * @since 4.0.0.0
21
- *
22
- * @param string $cidr cidr notation to convert
23
- *
24
- * @return array range of ips returned
25
- */
26
- public static function cidr_to_range( $cidr ) {
27
-
28
- $range = array();
29
-
30
- if ( strpos( $cidr, '/' ) ) {
31
-
32
- $cidr = explode( '/', $cidr );
33
-
34
- $range[] = long2ip( ( ip2long( $cidr[0] ) ) & ( ( - 1 << ( 32 - (int) $cidr[1] ) ) ) );
35
- $range[] = long2ip( ( ip2long( $cidr[0] ) ) + pow( 2, ( 32 - (int) $cidr[1] ) ) - 1 );
36
-
37
- } else { //if not a range just return the original ip
38
-
39
- $range[] = $cidr;
40
-
41
- }
42
-
43
- return $range;
44
-
45
- }
46
-
47
  /**
48
  * Clear caches.
49
  *
@@ -112,8 +78,8 @@ final class ITSEC_Lib {
112
  log_priority int(2) NOT NULL DEFAULT 1,
113
  log_date datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
114
  log_date_gmt datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
115
- log_host varchar(20),
116
- log_username varchar(20),
117
  log_user bigint(20) UNSIGNED,
118
  log_url varchar(255),
119
  log_referrer varchar(255),
@@ -131,9 +97,9 @@ final class ITSEC_Lib {
131
  lockout_start_gmt datetime NOT NULL,
132
  lockout_expire datetime NOT NULL,
133
  lockout_expire_gmt datetime NOT NULL,
134
- lockout_host varchar(20),
135
  lockout_user bigint(20) UNSIGNED,
136
- lockout_username varchar(20),
137
  lockout_active int(1) NOT NULL DEFAULT 1,
138
  PRIMARY KEY (lockout_id),
139
  KEY lockout_expire_gmt (lockout_expire_gmt),
@@ -149,9 +115,9 @@ final class ITSEC_Lib {
149
  temp_type varchar(20) NOT NULL,
150
  temp_date datetime NOT NULL,
151
  temp_date_gmt datetime NOT NULL,
152
- temp_host varchar(20),
153
  temp_user bigint(20) UNSIGNED,
154
- temp_username varchar(20),
155
  PRIMARY KEY (temp_id),
156
  KEY temp_date_gmt (temp_date_gmt),
157
  KEY temp_host (temp_host),
@@ -433,41 +399,6 @@ final class ITSEC_Lib {
433
 
434
  }
435
 
436
- /**
437
- * Returns a psuedo-random string of requested length.
438
- *
439
- * Builds a random string similar to the WordPress password functions.
440
- *
441
- * @since 4.0.0
442
- *
443
- * @param int $length how long the string should be (max 62)
444
- * @param bool $base32 true if use only base32 characters to generate
445
- * @param bool $special_chars whether to include special characters in generation
446
- *
447
- * @return string
448
- */
449
- public static function get_random( $length, $base32 = false, $special_chars = false ) {
450
-
451
- if ( true === $base32 ) {
452
-
453
- $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
454
-
455
- } else {
456
-
457
- $string = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
458
-
459
- if ( true === $special_chars ) {
460
-
461
- $string .= '_)(*&^%$#@!~`:;<>,.?/{}[]|';
462
-
463
- }
464
-
465
- }
466
-
467
- return substr( str_shuffle( $string ), mt_rand( 0, strlen( $string ) - $length ), $length );
468
-
469
- }
470
-
471
  /**
472
  * Returns the server type of the plugin user.
473
  *
@@ -478,35 +409,9 @@ final class ITSEC_Lib {
478
  * @return string|bool server type the user is using of false if undetectable.
479
  */
480
  public static function get_server() {
481
-
482
- // @codeCoverageIgnoreStart
483
- //Allows to override server authentication for testing or other reasons.
484
- if ( defined( 'ITSEC_SERVER_OVERRIDE' ) ) {
485
- return ITSEC_SERVER_OVERRIDE;
486
- }
487
- // @codeCoverageIgnoreEnd
488
-
489
- $server_raw = strtolower( filter_var( $_SERVER['SERVER_SOFTWARE'], FILTER_SANITIZE_STRING ) );
490
-
491
- //figure out what server they're using
492
- if ( false !== strpos( $server_raw, 'apache' ) ) {
493
-
494
- return 'apache';
495
-
496
- } elseif ( false !== strpos( $server_raw, 'nginx' ) ) {
497
-
498
- return 'nginx';
499
-
500
- } elseif ( false !== strpos( $server_raw, 'litespeed' ) ) {
501
-
502
- return 'litespeed';
503
-
504
- } else { //unsupported server
505
-
506
- return false;
507
-
508
- }
509
-
510
  }
511
 
512
  /**
@@ -567,88 +472,53 @@ final class ITSEC_Lib {
567
  }
568
 
569
  /**
570
- * Converts IP with a netmask wildcards to one with * instead
571
  *
572
- * Allows use of wildcards in IP address by converting them to standard notation.
 
 
573
  *
574
- * @since 4.0.0
575
- *
576
- * @param string $ip ip to convert
577
- *
578
- * @return string the converted ip
579
  */
580
- public static function ip_mask_to_range( $ip ) {
581
-
582
- if ( strpos( $ip, '/' ) ) {
583
-
584
- $parts = explode( '/', trim( $ip ) );
585
- $octets = array_reverse( explode( '.', trim( $parts[0] ) ) );
586
-
587
- if ( isset( $parts[1] ) && 0 < intval( $parts[1] ) ) {
588
 
589
- $wildcards = ( 32 - $parts[1] ) / 8;
590
 
591
- for ( $count = 0; $count < $wildcards; $count ++ ) {
592
 
593
- $octets[$count] = '[0-9]+';
594
 
595
- }
596
 
597
- return implode( '.', array_reverse( $octets ) );
 
 
598
 
 
 
 
 
 
 
599
  } else {
600
-
601
- return $ip;
602
-
603
  }
604
-
605
  }
606
 
607
- return $ip;
608
-
609
- }
610
-
611
- /**
612
- * Converts IP with * wildcards to one with a netmask instead
613
- *
614
- * Attempts to create a standardized CIDR block from an IP using wildcards.
615
- *
616
- * @since 4.0.0
617
- *
618
- * @param string $ip ip to convert
619
- *
620
- * @return string the converted ip
621
- */
622
- public static function ip_wild_to_mask( $ip ) {
623
-
624
- $host_parts = array_reverse( explode( '.', trim( $ip ) ) );
625
-
626
- if ( strpos( $ip, '*' ) ) {
627
-
628
- $mask = 32; //used to calculate netmask with wildcards
629
- $converted_host = str_replace( '*', '0', $ip );
630
-
631
- //convert hosts with wildcards to host with netmask and create rule lines
632
- foreach ( $host_parts as $part ) {
633
-
634
- if ( '*' === $part ) {
635
- $mask = $mask - 8;
636
- }
637
-
638
- }
639
-
640
- $converted_host = trim( $converted_host );
641
 
642
- //Apply a mask if we had to convert
643
- if ( 0 < $mask ) {
644
- $converted_host .= '/' . $mask;
645
  }
646
-
647
- return $converted_host;
648
-
649
  }
650
 
651
- return $ip;
652
 
653
  }
654
 
@@ -824,57 +694,6 @@ final class ITSEC_Lib {
824
 
825
  }
826
 
827
- /**
828
- * Validates a list of ip addresses.
829
- *
830
- * Makes sure that the provided IP addresses are in fact valid IPV4 addresses.
831
- *
832
- * @since 4.0.0
833
- *
834
- * @param string $ip string of hosts to check
835
- *
836
- * @return array array of good hosts or false
837
- */
838
- public static function validates_ip_address( $ip ) {
839
- $ip = trim( filter_var( $ip, FILTER_SANITIZE_STRING ) );
840
-
841
- if ( substr_count( $ip, '.' ) !== 3 ) {
842
- return false;
843
- }
844
-
845
- $has_cidr = ( false !== strpos( $ip, '/' ) );
846
- $has_wildcard = ( false !== strpos( $ip, '*' ) );
847
-
848
- if ( $has_cidr && $has_wildcard ) {
849
- return false;
850
- }
851
-
852
- $ip_digit_regex = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)';
853
- $cidr_digit_regex = '(?:3[0-2]|2[0-9]|1[1-9]|[148])';
854
-
855
- $ip_regex = "(?:$ip_digit_regex\.){3}$ip_digit_regex";
856
-
857
- if ( $has_cidr ) {
858
- return (boolean) preg_match( "{^$ip_regex/$cidr_digit_regex$}", $ip );
859
- }
860
-
861
- if ( $has_wildcard ) {
862
- $wildcard_count = substr_count( $ip, '*' );
863
-
864
- if ( 1 === $wildcard_count ) {
865
- return (boolean) preg_match( "{^(?:$ip_digit_regex\.){3}\*$}", $ip );
866
- } else if ( 2 === $wildcard_count ) {
867
- return (boolean) preg_match( "{^(?:$ip_digit_regex\.){2}\*\.\*$}", $ip );
868
- } else if ( 3 === $wildcard_count ) {
869
- return (boolean) preg_match( "{^(?:$ip_digit_regex\.)\*\.\*\.\*$}", $ip );
870
- }
871
-
872
- return false;
873
- }
874
-
875
- return (boolean) preg_match( "{^$ip_regex$}", $ip );
876
- }
877
-
878
  /**
879
  * Validates a file path
880
  *
10
  * @since 4.0.0
11
  */
12
  final class ITSEC_Lib {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  /**
14
  * Clear caches.
15
  *
78
  log_priority int(2) NOT NULL DEFAULT 1,
79
  log_date datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
80
  log_date_gmt datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
81
+ log_host varchar(40),
82
+ log_username varchar(60),
83
  log_user bigint(20) UNSIGNED,
84
  log_url varchar(255),
85
  log_referrer varchar(255),
97
  lockout_start_gmt datetime NOT NULL,
98
  lockout_expire datetime NOT NULL,
99
  lockout_expire_gmt datetime NOT NULL,
100
+ lockout_host varchar(40),
101
  lockout_user bigint(20) UNSIGNED,
102
+ lockout_username varchar(60),
103
  lockout_active int(1) NOT NULL DEFAULT 1,
104
  PRIMARY KEY (lockout_id),
105
  KEY lockout_expire_gmt (lockout_expire_gmt),
115
  temp_type varchar(20) NOT NULL,
116
  temp_date datetime NOT NULL,
117
  temp_date_gmt datetime NOT NULL,
118
+ temp_host varchar(40),
119
  temp_user bigint(20) UNSIGNED,
120
+ temp_username varchar(60),
121
  PRIMARY KEY (temp_id),
122
  KEY temp_date_gmt (temp_date_gmt),
123
  KEY temp_host (temp_host),
399
 
400
  }
401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  /**
403
  * Returns the server type of the plugin user.
404
  *
409
  * @return string|bool server type the user is using of false if undetectable.
410
  */
411
  public static function get_server() {
412
+ require_once( trailingslashit( $GLOBALS['itsec_globals']['plugin_dir'] ) . 'core/lib/class-itsec-lib-utility.php' );
413
+
414
+ return ITSEC_Lib_Utility::get_web_server();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  }
416
 
417
  /**
472
  }
473
 
474
  /**
475
+ * Determines whether a given IP address is whitelisted
476
  *
477
+ * @param string $ip_to_check ip to check (can be in CIDR notation)
478
+ * @param array $white_ips ip list to compare to if not yet saved to options
479
+ * @param boolean $current whether to whitelist the current ip or not (due to saving, etc)
480
  *
481
+ * @return boolean true if whitelisted or false
 
 
 
 
482
  */
483
+ public static function is_ip_whitelisted( $ip_to_check, $white_ips = null, $current = false ) {
484
+ if ( ! class_exists( 'ITSEC_Lib_IP_Tools' ) ) {
485
+ $itsec_core = ITSEC_Core::get_instance();
486
+ require_once( dirname( $itsec_core->get_plugin_file() ) . '/core/lib/class-itsec-lib-ip-tools.php' );
487
+ }
 
 
 
488
 
489
+ if ( $white_ips === null ) {
490
 
491
+ $global_settings = get_site_option( 'itsec_global' );
492
 
493
+ $white_ips = ( isset( $global_settings['lockout_white_list'] ) ? $global_settings['lockout_white_list'] : array() );
494
 
495
+ }
496
 
497
+ if ( $current === true ) {
498
+ $white_ips[] = ITSEC_Lib::get_ip(); //add current user ip to whitelist to check automatically
499
+ }
500
 
501
+ // Check to see if we have a temporarily white listed IP
502
+ $temp = get_site_option( 'itsec_temp_whitelist_ip' );
503
+ if ( false !== $temp ) {
504
+ // If the temporary white list is expired, delete the option we store it in
505
+ if ( $temp['exp'] < current_time( 'timestamp' ) ) {
506
+ delete_site_option( 'itsec_temp_whitelist_ip' );
507
  } else {
508
+ // If the temporary white list is still valid, add the IP to our list of white IPs
509
+ $white_ips[] = $temp['ip'];
 
510
  }
 
511
  }
512
 
513
+ $white_ips = apply_filters( 'itsec_white_ips', $white_ips );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514
 
515
+ foreach ( $white_ips as $white_ip ) {
516
+ if ( ITSEC_Lib_IP_Tools::intersect( $ip_to_check, ITSEC_Lib_IP_Tools::ip_wild_to_ip_cidr( $white_ip ) ) ) {
517
+ return true;
518
  }
 
 
 
519
  }
520
 
521
+ return false;
522
 
523
  }
524
 
694
 
695
  }
696
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
  /**
698
  * Validates a file path
699
  *
core/class-itsec-lockout.php CHANGED
@@ -308,7 +308,7 @@ class ITSEC_Lockout {
308
  ITSEC_Lib::create_database_tables();
309
  }
310
 
311
- if ( ! $this->is_ip_whitelisted( $host ) && ( $lock_host !== null || $lock_user !== null || $lock_username !== null ) ) {
312
 
313
  $this->lockout( $options['type'], $options['reason'], $lock_host, $lock_user, $lock_username );
314
 
@@ -316,7 +316,7 @@ class ITSEC_Lockout {
316
 
317
  global $itsec_logger;
318
 
319
- $itsec_logger->log_event( __( 'lockout', 'better-wp-security' ), 10, array( __( 'A whitelisted host has triggered a lockout condition but was not locked out.', 'better-wp-security' ) ), sanitize_text_field( $host ) );
320
 
321
  }
322
 
@@ -331,7 +331,7 @@ class ITSEC_Lockout {
331
  */
332
  protected function execute_lock( $user = false, $network = false ) {
333
 
334
- if ( $this->is_ip_whitelisted( ITSEC_Lib::get_ip() ) ) {
335
  return;
336
  }
337
 
@@ -418,7 +418,7 @@ class ITSEC_Lockout {
418
  __( 'User lockout message', 'better-wp-security' ),
419
  $settings['user_lockout_message'],
420
  __( 'Is this computer white-listed', 'better-wp-security' ),
421
- ( $this->is_ip_whitelisted( ITSEC_Lib::get_ip() ) === true ? __( 'yes', 'better-wp-security' ) : __( 'no', 'better-wp-security' ) )
422
  );
423
 
424
  return $description;
@@ -493,126 +493,6 @@ class ITSEC_Lockout {
493
 
494
  }
495
 
496
- /**
497
- * Determines whether a given IP address is whitelisted.
498
- *
499
- * @since 4.0
500
- *
501
- * @access private
502
- *
503
- * @param string $ip_to_check ip to check
504
- *
505
- * @return boolean true if whitelisted or false
506
- */
507
- protected function is_ip_whitelisted( $ip_to_check, $current = false ) {
508
-
509
- global $itsec_globals;
510
-
511
- $white_ips = isset( $itsec_globals['settings']['lockout_white_list'] ) ? $itsec_globals['settings']['lockout_white_list'] : array();
512
-
513
- if ( ! is_array( $white_ips ) ) {
514
- $white_ips = explode( PHP_EOL, $white_ips );
515
- }
516
-
517
- //Add the server IP address
518
- if ( isset( $_SERVER['LOCAL_ADDR'] ) ) {
519
-
520
- $white_ips[] = $_SERVER['LOCAL_ADDR'];
521
-
522
- } elseif ( isset( $_SERVER['SERVER_ADDR'] ) ) {
523
-
524
- $white_ips[] = $_SERVER['SERVER_ADDR'];
525
-
526
- }
527
-
528
- if ( $current === true ) {
529
- $white_ips[] = ITSEC_Lib::get_ip(); //add current user ip to whitelist to check automatically
530
- }
531
-
532
- $temp = get_site_option( 'itsec_temp_whitelist_ip' );
533
-
534
- if ( $temp !== false ) {
535
-
536
- if ( $temp['exp'] < $itsec_globals['current_time'] ) {
537
-
538
- delete_site_option( 'itsec_temp_whitelist_ip' );
539
-
540
- } else {
541
-
542
- $white_ips[] = filter_var( $temp['ip'],
543
- FILTER_VALIDATE_IP,
544
- FILTER_FLAG_IPV4 );
545
-
546
- }
547
-
548
- }
549
-
550
- if ( is_array( $white_ips ) && sizeof( $white_ips > 0 ) ) {
551
-
552
- foreach ( $white_ips as $white_ip ) {
553
-
554
- $converted_white_ip = ITSEC_Lib::ip_wild_to_mask( $white_ip );
555
-
556
- $check_range = ITSEC_Lib::cidr_to_range( $converted_white_ip );
557
- $ip_range = ITSEC_Lib::cidr_to_range( $ip_to_check );
558
-
559
- if ( sizeof( $check_range ) === 2 ) { //range to check
560
-
561
- $check_min = ip2long( $check_range[0] );
562
- $check_max = ip2long( $check_range[1] );
563
-
564
- if ( sizeof( $ip_range ) === 2 ) {
565
-
566
- $ip_min = ip2long( $ip_range[0] );
567
- $ip_max = ip2long( $ip_range[1] );
568
-
569
- if ( ( $check_min < $ip_min && $ip_min < $check_max ) || ( $check_min < $ip_max && $ip_max < $check_max ) ) {
570
- return true;
571
- }
572
-
573
- } else {
574
-
575
- $ip = ip2long( $ip_range[0] );
576
-
577
- if ( $check_min < $ip && $ip < $check_max ) {
578
- return true;
579
- }
580
-
581
- }
582
-
583
- } else { //single ip to check
584
-
585
- $check = ip2long( $check_range[0] );
586
-
587
- if ( sizeof( $ip_range ) === 2 ) {
588
-
589
- $ip_min = ip2long( $ip_range[0] );
590
- $ip_max = ip2long( $ip_range[1] );
591
-
592
- if ( $ip_min < $check && $check < $ip_max ) {
593
- return true;
594
- }
595
-
596
- } else {
597
-
598
- $ip = ip2long( $ip_range[0] );
599
-
600
- if ( $check == $ip ) {
601
- return true;
602
- }
603
-
604
- }
605
-
606
- }
607
-
608
- }
609
-
610
- }
611
-
612
- return false;
613
-
614
- }
615
-
616
  /**
617
  * Process ajax request to set temp whitelist
618
  *
@@ -711,7 +591,7 @@ class ITSEC_Lockout {
711
  if ( $itsec_files->get_file_lock( 'lockout_' . $host . $user . $username ) ) {
712
 
713
  //Do we have a good host to lock out or not
714
- if ( $host != null && $this->is_ip_whitelisted( sanitize_text_field( $host ) ) === false && ITSEC_Lib::validates_ip_address( $host ) === true ) {
715
  $good_host = sanitize_text_field( $host );
716
  } else {
717
  $good_host = false;
@@ -769,7 +649,7 @@ class ITSEC_Lockout {
769
  //We have temp bans to perform
770
  if ( $good_host !== false || $good_user !== false || $good_username || $good_username !== false ) {
771
 
772
- if ( $this->is_ip_whitelisted( sanitize_text_field( $host ) ) ) {
773
 
774
  $whitelisted = true;
775
  $expiration = date( 'Y-m-d H:i:s', 1 );
@@ -800,7 +680,7 @@ class ITSEC_Lockout {
800
  )
801
  );
802
 
803
- $itsec_logger->log_event( __( 'lockout', 'better-wp-security' ), 10, array(
804
  'expires' => $expiration, 'expires_gmt' => $expiration_gmt, 'type' => $type
805
  ), sanitize_text_field( $host ) );
806
 
@@ -926,7 +806,7 @@ class ITSEC_Lockout {
926
  id="lo_<?php echo $host['lockout_id']; ?>"
927
  value="<?php echo $host['lockout_id']; ?>"/>
928
  <label
929
- for="lo_<?php echo $host['lockout_id']; ?>"><strong><?php echo filter_var( $host['lockout_host'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 );; ?></strong>
930
  - <?php _e( 'Expires in', 'better-wp-security' ); ?>
931
  <em> <?php echo human_time_diff( $itsec_globals['current_time_gmt'], strtotime( $host['lockout_expire_gmt'] ) ); ?></em></label>
932
  </li>
@@ -1216,7 +1096,7 @@ class ITSEC_Lockout {
1216
  //Tell which host was locked out
1217
  if ( $host !== false ) {
1218
 
1219
- $host_text = sprintf( '%s, <a href="http://ip-adress.com/ip_tracer/%s"><strong>%s</strong></a>, ', __( 'host', 'better-wp-security' ), sanitize_text_field( $host ), sanitize_text_field( $host ) );
1220
 
1221
  $host_expiration_text = __( 'The host has been locked out ', 'better-wp-security' );
1222
 
308
  ITSEC_Lib::create_database_tables();
309
  }
310
 
311
+ if ( ! ITSEC_Lib::is_ip_whitelisted( $host ) && ( $lock_host !== null || $lock_user !== null || $lock_username !== null ) ) {
312
 
313
  $this->lockout( $options['type'], $options['reason'], $lock_host, $lock_user, $lock_username );
314
 
316
 
317
  global $itsec_logger;
318
 
319
+ $itsec_logger->log_event( 'lockout', 10, array( __( 'A whitelisted host has triggered a lockout condition but was not locked out.', 'better-wp-security' ) ), sanitize_text_field( $host ) );
320
 
321
  }
322
 
331
  */
332
  protected function execute_lock( $user = false, $network = false ) {
333
 
334
+ if ( ITSEC_Lib::is_ip_whitelisted( ITSEC_Lib::get_ip() ) ) {
335
  return;
336
  }
337
 
418
  __( 'User lockout message', 'better-wp-security' ),
419
  $settings['user_lockout_message'],
420
  __( 'Is this computer white-listed', 'better-wp-security' ),
421
+ ( ITSEC_Lib::is_ip_whitelisted( ITSEC_Lib::get_ip() ) === true ? __( 'yes', 'better-wp-security' ) : __( 'no', 'better-wp-security' ) )
422
  );
423
 
424
  return $description;
493
 
494
  }
495
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  /**
497
  * Process ajax request to set temp whitelist
498
  *
591
  if ( $itsec_files->get_file_lock( 'lockout_' . $host . $user . $username ) ) {
592
 
593
  //Do we have a good host to lock out or not
594
+ if ( ! is_null( $host ) && ITSEC_Lib::is_ip_whitelisted( sanitize_text_field( $host ) ) === false && ITSEC_Lib_IP_Tools::validate( $host ) ) {
595
  $good_host = sanitize_text_field( $host );
596
  } else {
597
  $good_host = false;
649
  //We have temp bans to perform
650
  if ( $good_host !== false || $good_user !== false || $good_username || $good_username !== false ) {
651
 
652
+ if ( ITSEC_Lib::is_ip_whitelisted( sanitize_text_field( $host ) ) ) {
653
 
654
  $whitelisted = true;
655
  $expiration = date( 'Y-m-d H:i:s', 1 );
680
  )
681
  );
682
 
683
+ $itsec_logger->log_event( 'lockout', 10, array(
684
  'expires' => $expiration, 'expires_gmt' => $expiration_gmt, 'type' => $type
685
  ), sanitize_text_field( $host ) );
686
 
806
  id="lo_<?php echo $host['lockout_id']; ?>"
807
  value="<?php echo $host['lockout_id']; ?>"/>
808
  <label
809
+ for="lo_<?php echo $host['lockout_id']; ?>"><strong><?php echo esc_html( $host['lockout_host'] ); ?></strong>
810
  - <?php _e( 'Expires in', 'better-wp-security' ); ?>
811
  <em> <?php echo human_time_diff( $itsec_globals['current_time_gmt'], strtotime( $host['lockout_expire_gmt'] ) ); ?></em></label>
812
  </li>
1096
  //Tell which host was locked out
1097
  if ( $host !== false ) {
1098
 
1099
+ $host_text = sprintf( '%s, <a href="http://www.traceip.net/?query=%s"><strong>%s</strong></a>, ', __( 'host', 'better-wp-security' ), urlencode( $host ), sanitize_text_field( $host ) );
1100
 
1101
  $host_expiration_text = __( 'The host has been locked out ', 'better-wp-security' );
1102
 
core/class-itsec-logger-all-logs.php CHANGED
@@ -72,13 +72,19 @@ final class ITSEC_Logger_All_Logs extends ITSEC_WP_List_Table {
72
  *
73
  **/
74
  function column_host( $item ) {
 
 
 
 
75
 
76
  $r = array();
77
  if ( ! is_array( $item['host'] ) ) {
78
  $item['host'] = array( $item['host'] );
79
  }
80
  foreach ( $item['host'] as $host ) {
81
- $r[] = '<a href="http://ip-adress.com/ip_tracer/' . filter_var( $host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) . '" target="_blank">' . filter_var( $host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) . '</a>';
 
 
82
  }
83
  $return = implode( '<br />', $r );
84
 
72
  *
73
  **/
74
  function column_host( $item ) {
75
+ if ( ! class_exists( 'ITSEC_Lib_IP_Tools' ) ) {
76
+ $itsec_core = ITSEC_Core::get_instance();
77
+ require_once( dirname( $itsec_core->get_plugin_file() ) . '/core/lib/class-itsec-lib-ip-tools.php' );
78
+ }
79
 
80
  $r = array();
81
  if ( ! is_array( $item['host'] ) ) {
82
  $item['host'] = array( $item['host'] );
83
  }
84
  foreach ( $item['host'] as $host ) {
85
+ if ( ITSEC_Lib_IP_Tools::validate( $host ) ) {
86
+ $r[] = '<a href="http://www.traceip.net/?query=' . urlencode( $host ) . '" target="_blank">' . esc_html( $host ) . '</a>';
87
+ }
88
  }
89
  $return = implode( '<br />', $r );
90
 
core/class-itsec-logger.php CHANGED
@@ -14,37 +14,23 @@ final class ITSEC_Logger {
14
  $logger_modules,
15
  $module_path;
16
 
 
 
 
 
 
 
 
17
  function __construct() {
18
 
19
  global $itsec_globals;
20
 
21
- //make sure the log file info is there or generate it. This should only affect beta users.
22
- if ( ! isset( $itsec_globals['settings']['log_info'] ) ) {
23
-
24
- $itsec_globals['settings']['log_info'] = substr( sanitize_title( get_bloginfo( 'name' ) ), 0, 20 ) . '-' . ITSEC_Lib::get_random( mt_rand( 0, 10 ) );
25
-
26
- update_site_option( 'itsec_global', $itsec_globals['settings'] );
27
-
28
- }
29
-
30
- //Make sure the logs directory was created
31
- if ( ! is_dir( $itsec_globals['ithemes_log_dir'] ) ) {
32
- @mkdir( trailingslashit( $itsec_globals['ithemes_dir'] ) . 'logs' );
33
- }
34
-
35
- //don't create a log file if we don't need it.
36
- if ( isset( $itsec_globals['settings']['log_type'] ) && $itsec_globals['settings']['log_type'] !== 0 ) {
37
-
38
- $this->log_file = $itsec_globals['ithemes_log_dir'] . '/event-log-' . $itsec_globals['settings']['log_info'] . '.log';
39
- $this->start_log(); //create a log file if we don't have one
40
-
41
- }
42
-
43
  $this->logger_modules = array(); //array to hold information on modules using this feature
44
  $this->logger_displays = array(); //array to hold metabox information
45
  $this->module_path = ITSEC_Lib::get_module_path( __FILE__ );
46
 
47
  add_action( 'plugins_loaded', array( $this, 'register_modules' ) );
 
48
 
49
  add_action( 'admin_enqueue_scripts', array( $this, 'admin_script' ) ); //enqueue scripts for admin page
50
 
@@ -301,68 +287,98 @@ final class ITSEC_Logger {
301
  */
302
  public function log_event( $module, $priority = 5, $data = array(), $host = '', $username = '', $user = '', $url = '', $referrer = '' ) {
303
  global $wpdb, $itsec_globals;
304
-
305
  if ( isset( $this->logger_modules[ $module ] ) ) {
306
- $options = $this->logger_modules[ $module ];
307
-
308
  if ( ! isset( $itsec_globals['settings']['log_type'] ) || $itsec_globals['settings']['log_type'] === 0 || $itsec_globals['settings']['log_type'] == 2 ) {
309
- $values = array(
310
- 'log_type' => $options['type'],
311
- 'log_priority' => intval( $priority ),
312
- 'log_function' => $options['function'],
313
- 'log_date' => date( 'Y-m-d H:i:s', $itsec_globals['current_time'] ),
314
- 'log_date_gmt' => date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] ),
315
- 'log_host' => sanitize_text_field( $host ),
316
- 'log_username' => sanitize_text_field( $username ),
317
- 'log_user' => intval( $user ),
318
- 'log_url' => $url,
319
- 'log_referrer' => $referrer,
320
- 'log_data' => serialize( $data ),
321
- );
322
-
323
- $columns = '`' . implode( '`, `', array_keys( $values ) ) . '`';
324
- $placeholders = '%s, %d, %s, %s, %s, %s, %s, %s, %s, %s, %s';
325
-
326
- $query_format = "INSERT INTO `{$wpdb->base_prefix}itsec_log` ($columns) VALUES ($placeholders)";
327
-
328
- $cached_show_errors_setting = $wpdb->hide_errors();
329
- $result = $wpdb->query( $wpdb->prepare( $query_format, $values ) );
330
-
331
- if ( ! $result ) {
332
- $wpdb->show_errors();
333
-
334
- ITSEC_Lib::create_database_tables();
335
-
336
- // Attempt the query again. Since errors will now be shown, a remaining issue will be display an error.
337
- $result = $wpdb->query( $wpdb->prepare( $query_format, $values ) );
338
- }
339
-
340
- // Set $wpdb->show_errors back to its original setting.
341
- $wpdb->show_errors( $cached_show_errors_setting );
342
  }
343
 
344
  if ( isset( $itsec_globals['settings']['log_type'] ) && ( $itsec_globals['settings']['log_type'] === 1 || $itsec_globals['settings']['log_type'] == 2 ) ) {
345
- $file_data = $this->sanitize_array( $data, true );
346
-
347
- $message =
348
- $options['type'] . ',' .
349
- intval( $priority ) . ',' .
350
- $options['function'] . ',' .
351
- date( 'Y-m-d H:i:s', $itsec_globals['current_time'] ) . ',' .
352
- date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] ) . ',' .
353
- sanitize_text_field( $host ) . ',' .
354
- sanitize_text_field( $username ) . ',' .
355
- ( intval( $user ) === 0 ? '' : intval( $user ) ) . ',' .
356
- esc_sql( $url ) . ',' .
357
- esc_sql( $referrer ) . ',' .
358
- maybe_serialize( $file_data );
359
-
360
- error_log( $message . PHP_EOL, 3, $this->log_file );
361
-
362
  }
363
 
364
  }
365
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
  }
367
 
368
  /**
@@ -532,6 +548,8 @@ final class ITSEC_Logger {
532
 
533
  }
534
 
 
 
535
  if ( ( @file_exists( $this->log_file ) && @filesize( $this->log_file ) >= 10485760 ) ) {
536
  $this->rotate_log();
537
  }
@@ -597,7 +615,7 @@ final class ITSEC_Logger {
597
 
598
  }
599
 
600
- $this->start_log();
601
 
602
  }
603
 
@@ -643,14 +661,46 @@ final class ITSEC_Logger {
643
 
644
  }
645
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
646
  /**
647
  * Creates a new log file and adds header information (if needed)
648
  *
649
  * @return void
650
  */
651
- private function start_log() {
 
 
 
 
652
 
653
  if ( file_exists( $this->log_file ) !== true ) { //only if current log file doesn't exist
 
 
 
 
 
 
 
 
 
654
 
655
  $header = 'log_type,log_priority,log_function,log_date,log_date_gmt,log_host,log_username,log_user,log_url,log_referrer,log_data' . PHP_EOL;
656
 
14
  $logger_modules,
15
  $module_path;
16
 
17
+ /**
18
+ * @access private
19
+ *
20
+ * @var array Events that need to be logged to a file but couldn't
21
+ */
22
+ private $_events_to_log_to_file = array();
23
+
24
  function __construct() {
25
 
26
  global $itsec_globals;
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  $this->logger_modules = array(); //array to hold information on modules using this feature
29
  $this->logger_displays = array(); //array to hold metabox information
30
  $this->module_path = ITSEC_Lib::get_module_path( __FILE__ );
31
 
32
  add_action( 'plugins_loaded', array( $this, 'register_modules' ) );
33
+ add_action( 'plugins_loaded', array( $this, 'write_pending_events_to_file' ) );
34
 
35
  add_action( 'admin_enqueue_scripts', array( $this, 'admin_script' ) ); //enqueue scripts for admin page
36
 
287
  */
288
  public function log_event( $module, $priority = 5, $data = array(), $host = '', $username = '', $user = '', $url = '', $referrer = '' ) {
289
  global $wpdb, $itsec_globals;
290
+
291
  if ( isset( $this->logger_modules[ $module ] ) ) {
 
 
292
  if ( ! isset( $itsec_globals['settings']['log_type'] ) || $itsec_globals['settings']['log_type'] === 0 || $itsec_globals['settings']['log_type'] == 2 ) {
293
+ $this->_log_event_to_db( $module, $priority, $data, $host, $username, $user, $url, $referrer );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
  }
295
 
296
  if ( isset( $itsec_globals['settings']['log_type'] ) && ( $itsec_globals['settings']['log_type'] === 1 || $itsec_globals['settings']['log_type'] == 2 ) ) {
297
+ $this->_log_event_to_file( $module, $priority, $data, $host, $username, $user, $url, $referrer );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  }
299
 
300
  }
301
 
302
+ do_action( 'itsec_log_event', $module, $priority, $data, $host, $username, $user, $url, $referrer );
303
+
304
+ }
305
+
306
+ private function _log_event_to_db( $module, $priority = 5, $data = array(), $host = '', $username = '', $user = '', $url = '', $referrer = '' ) {
307
+ global $wpdb, $itsec_globals;
308
+
309
+ $options = $this->logger_modules[ $module ];
310
+
311
+ $values = array(
312
+ 'log_type' => $options['type'],
313
+ 'log_priority' => intval( $priority ),
314
+ 'log_function' => $options['function'],
315
+ 'log_date' => date( 'Y-m-d H:i:s', $itsec_globals['current_time'] ),
316
+ 'log_date_gmt' => date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] ),
317
+ 'log_host' => sanitize_text_field( $host ),
318
+ 'log_username' => sanitize_text_field( $username ),
319
+ 'log_user' => intval( $user ),
320
+ 'log_url' => $url,
321
+ 'log_referrer' => $referrer,
322
+ 'log_data' => serialize( $data ),
323
+ );
324
+
325
+ $columns = '`' . implode( '`, `', array_keys( $values ) ) . '`';
326
+ $placeholders = '%s, %d, %s, %s, %s, %s, %s, %s, %s, %s, %s';
327
+
328
+ $query_format = "INSERT INTO `{$wpdb->base_prefix}itsec_log` ($columns) VALUES ($placeholders)";
329
+
330
+ $cached_show_errors_setting = $wpdb->hide_errors();
331
+ $result = $wpdb->query( $wpdb->prepare( $query_format, $values ) );
332
+
333
+ if ( ! $result ) {
334
+ $wpdb->show_errors();
335
+
336
+ ITSEC_Lib::create_database_tables();
337
+
338
+ // Attempt the query again. Since errors will now be shown, a remaining issue will be display an error.
339
+ $result = $wpdb->query( $wpdb->prepare( $query_format, $values ) );
340
+ }
341
+
342
+ // Set $wpdb->show_errors back to its original setting.
343
+ $wpdb->show_errors( $cached_show_errors_setting );
344
+ }
345
+
346
+ private function _log_event_to_file( $module, $priority = 5, $data = array(), $host = '', $username = '', $user = '', $url = '', $referrer = '' ) {
347
+ global $itsec_globals;
348
+
349
+ // If the file can't be prepared, store the events up to write later (at plugins_loaded)
350
+ if ( false === $this->_prepare_log_file() ) {
351
+ $this->_events_to_log_to_file[] = compact( 'module', 'priority', 'data', 'host', 'username', 'user', 'url', 'referrer' );
352
+ return;
353
+ }
354
+
355
+ $options = $this->logger_modules[ $module ];
356
+
357
+ $file_data = $this->sanitize_array( $data, true );
358
+
359
+ $message =
360
+ $options['type'] . ',' .
361
+ intval( $priority ) . ',' .
362
+ $options['function'] . ',' .
363
+ date( 'Y-m-d H:i:s', $itsec_globals['current_time'] ) . ',' .
364
+ date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] ) . ',' .
365
+ sanitize_text_field( $host ) . ',' .
366
+ sanitize_text_field( $username ) . ',' .
367
+ ( intval( $user ) === 0 ? '' : intval( $user ) ) . ',' .
368
+ esc_sql( $url ) . ',' .
369
+ esc_sql( $referrer ) . ',' .
370
+ maybe_serialize( $file_data );
371
+
372
+ error_log( $message . PHP_EOL, 3, $this->log_file );
373
+
374
+ }
375
+
376
+ public function write_pending_events_to_file() {
377
+ if ( ! empty( $this->_events_to_log_to_file ) ) {
378
+ foreach ( $this->_events_to_log_to_file as $event ) {
379
+ call_user_func_array( array( $this, '_log_event_to_file' ), $event );
380
+ }
381
+ }
382
  }
383
 
384
  /**
548
 
549
  }
550
 
551
+ $this->get_log_file();
552
+
553
  if ( ( @file_exists( $this->log_file ) && @filesize( $this->log_file ) >= 10485760 ) ) {
554
  $this->rotate_log();
555
  }
615
 
616
  }
617
 
618
+ $this->_prepare_log_file();
619
 
620
  }
621
 
661
 
662
  }
663
 
664
+ private function get_log_file() {
665
+ global $itsec_globals;
666
+
667
+ //make sure the log file info is there or generate it. This should only affect beta users.
668
+ if ( ! isset( $itsec_globals['settings']['log_info'] ) ) {
669
+
670
+ // We need wp_generate_password() to create a cryptographically secure file name
671
+ if ( ! function_exists( 'wp_generate_password' ) ) {
672
+ return false;
673
+ }
674
+ $itsec_globals['settings']['log_info'] = substr( sanitize_title( get_bloginfo( 'name' ) ), 0, 20 ) . '-' . wp_generate_password( 30, false );
675
+
676
+ update_site_option( 'itsec_global', $itsec_globals['settings'] );
677
+
678
+ }
679
+ $this->log_file = $itsec_globals['ithemes_log_dir'] . '/event-log-' . $itsec_globals['settings']['log_info'] . '.log';
680
+ return $this->log_file;
681
+ }
682
+
683
  /**
684
  * Creates a new log file and adds header information (if needed)
685
  *
686
  * @return void
687
  */
688
+ private function _prepare_log_file() {
689
+ // We can't prepare a file if we can't get the file name
690
+ if ( false === $this->get_log_file() ) {
691
+ return false;
692
+ }
693
 
694
  if ( file_exists( $this->log_file ) !== true ) { //only if current log file doesn't exist
695
+ global $itsec_globals;
696
+
697
+ //Make sure the logs directory was created
698
+ if ( ! is_dir( $itsec_globals['ithemes_log_dir'] ) ) {
699
+ if ( wp_mkdir_p( $itsec_globals['ithemes_log_dir'] ) ) {
700
+ // Make sure we have an index file to block directory listing
701
+ file_put_contents( path_join( $itsec_globals['ithemes_log_dir'], 'index.php' ), "<?php\n// Silence is golden." );
702
+ }
703
+ }
704
 
705
  $header = 'log_type,log_priority,log_function,log_date,log_date_gmt,log_host,log_username,log_user,log_url,log_referrer,log_data' . PHP_EOL;
706
 
core/class-itsec-setup.php CHANGED
@@ -169,6 +169,10 @@ class ITSEC_Setup {
169
  if ( ! is_dir( $itsec_globals['ithemes_log_dir'] ) ) {
170
 
171
  @mkdir( $itsec_globals['ithemes_log_dir'] );
 
 
 
 
172
  $handle = @fopen( $itsec_globals['ithemes_log_dir'] . '/.htaccess', 'w+' );
173
  @fwrite( $handle, 'Deny from all' );
174
  @fclose( $handle );
@@ -178,6 +182,10 @@ class ITSEC_Setup {
178
  if ( ! is_dir( $itsec_globals['ithemes_backup_dir'] ) ) {
179
 
180
  @mkdir( $itsec_globals['ithemes_backup_dir'] );
 
 
 
 
181
  $handle = @fopen( $itsec_globals['ithemes_backup_dir'] . '/.htaccess', 'w+' );
182
  @fwrite( $handle, 'Deny from all' );
183
  @fclose( $handle );
@@ -200,7 +208,7 @@ class ITSEC_Setup {
200
 
201
  if ( $options === false || ( isset( $options['log_info'] ) && sizeof( $options ) <= 2 ) ) {
202
 
203
- $this->defaults['log_info'] = substr( sanitize_title( get_bloginfo( 'name' ) ), 0, 20 ) . '-' . ITSEC_Lib::get_random( mt_rand( 0, 10 ) );
204
 
205
  $itsec_globals['settings'] = $this->defaults;
206
 
@@ -231,6 +239,7 @@ class ITSEC_Setup {
231
  private function upgrade_execute( $upgrade = false ) {
232
 
233
  global $itsec_old_version, $itsec_globals, $wpdb, $itsec_setup_action;
 
234
 
235
  $itsec_setup_action = 'upgrade';
236
  $itsec_old_version = $upgrade;
@@ -330,6 +339,7 @@ class ITSEC_Setup {
330
  if ( $itsec_old_version < 4030 ) {
331
 
332
  ITSEC_Lib::create_database_tables(); //adds username field to lockouts and temp
 
333
  add_site_option( 'itsec_rewrites_changed', true );
334
 
335
  }
@@ -370,6 +380,40 @@ class ITSEC_Setup {
370
 
371
  }
372
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  }
374
 
375
  /**
169
  if ( ! is_dir( $itsec_globals['ithemes_log_dir'] ) ) {
170
 
171
  @mkdir( $itsec_globals['ithemes_log_dir'] );
172
+ // Make sure we have an index file to block directory listing
173
+ if ( ! file_exists( path_join( $itsec_globals['ithemes_log_dir'], 'index.php' ) ) ) {
174
+ file_put_contents( path_join( $itsec_globals['ithemes_log_dir'], 'index.php' ), "<?php\n// Silence is golden." );
175
+ }
176
  $handle = @fopen( $itsec_globals['ithemes_log_dir'] . '/.htaccess', 'w+' );
177
  @fwrite( $handle, 'Deny from all' );
178
  @fclose( $handle );
182
  if ( ! is_dir( $itsec_globals['ithemes_backup_dir'] ) ) {
183
 
184
  @mkdir( $itsec_globals['ithemes_backup_dir'] );
185
+ // Make sure we have an index file to block directory listing
186
+ if ( ! file_exists( path_join( $itsec_globals['ithemes_backup_dir'], 'index.php' ) ) ) {
187
+ file_put_contents( path_join( $itsec_globals['ithemes_backup_dir'], 'index.php' ), "<?php\n// Silence is golden." );
188
+ }
189
  $handle = @fopen( $itsec_globals['ithemes_backup_dir'] . '/.htaccess', 'w+' );
190
  @fwrite( $handle, 'Deny from all' );
191
  @fclose( $handle );
208
 
209
  if ( $options === false || ( isset( $options['log_info'] ) && sizeof( $options ) <= 2 ) ) {
210
 
211
+ $this->defaults['log_info'] = substr( sanitize_title( get_bloginfo( 'name' ) ), 0, 20 ) . '-' . wp_generate_password( 30, false );
212
 
213
  $itsec_globals['settings'] = $this->defaults;
214
 
239
  private function upgrade_execute( $upgrade = false ) {
240
 
241
  global $itsec_old_version, $itsec_globals, $wpdb, $itsec_setup_action;
242
+ $tables_updated = false;
243
 
244
  $itsec_setup_action = 'upgrade';
245
  $itsec_old_version = $upgrade;
339
  if ( $itsec_old_version < 4030 ) {
340
 
341
  ITSEC_Lib::create_database_tables(); //adds username field to lockouts and temp
342
+ $tables_updated = true;
343
  add_site_option( 'itsec_rewrites_changed', true );
344
 
345
  }
380
 
381
  }
382
 
383
+ //IPv6 support was added in 4039
384
+ if ( $itsec_old_version < 4039 && ! $tables_updated ) {
385
+ ITSEC_Lib::create_database_tables();
386
+ $tables_updated = true;
387
+ }
388
+
389
+ if ( $itsec_old_version < 4040 ) {
390
+ $options = get_site_option( 'itsec_global' );
391
+
392
+ if ( $options['log_info'] ) {
393
+ $new_log_info = substr( sanitize_title( get_bloginfo( 'name' ) ), 0, 20 ) . '-' . wp_generate_password( 30, false );
394
+ $old_file = path_join( $options['log_location'], 'event-log-' . $options['log_info'] . '.log' );
395
+ $new_file = path_join( $options['log_location'], 'event-log-' . $new_log_info . '.log' );
396
+
397
+ // If the file exists already, don't update the location unless we successfully move it.
398
+ if ( file_exists( $old_file ) && rename( $old_file, $new_file ) ) {
399
+ $options['log_info'] = $new_log_info;
400
+ }
401
+ }
402
+
403
+ // Make sure we have an index files to block directory listing in logs directory
404
+ if ( is_dir( $options['log_location'] ) && ! file_exists( path_join( $options['log_location'], 'index.php' ) ) ) {
405
+ file_put_contents( path_join( $options['log_location'], 'index.php' ), "<?php\n// Silence is golden." );
406
+ }
407
+
408
+ $backup_options = get_site_option( 'itsec_backup' );
409
+ // Make sure we have an index files to block directory listing in backups directory
410
+ if ( is_dir( $backup_options['location'] ) && ! file_exists( path_join( $backup_options['location'], 'index.php' ) ) ) {
411
+ file_put_contents( path_join( $backup_options['location'], 'index.php' ), "<?php\n// Silence is golden." );
412
+ }
413
+
414
+ update_site_option( 'itsec_global', $options );
415
+ }
416
+
417
  }
418
 
419
  /**
core/history.txt CHANGED
@@ -326,3 +326,45 @@
326
  Bug Fix: All data added to the options table by iThemes Security is removed on uninstall.
327
  Bug Fix: Fixed the cause of the following warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'ITSEC_SSL_Setup' does not have a method 'execute_deactivate'
328
  Enhancement: Improved code that ensures that tables and options table entries created by iThemes Security are removed on uninstall only when no other iThemes Security plugin is active.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  Bug Fix: All data added to the options table by iThemes Security is removed on uninstall.
327
  Bug Fix: Fixed the cause of the following warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'ITSEC_SSL_Setup' does not have a method 'execute_deactivate'
328
  Enhancement: Improved code that ensures that tables and options table entries created by iThemes Security are removed on uninstall only when no other iThemes Security plugin is active.
329
+ 2.2.0 - 2016-02-11 - Chris Jean & Aaron D. Campbell
330
+ New Feature: Added support for IPv6 addresses. This includes support for IPv6 in lockouts, ban hosts, and white lists.
331
+ Bug Fix: Fixed issue that could cause username-based lockouts to fail for long usernames.
332
+ Enhancement: Updated descriptions of valid IP and IP range formats for the Lockout White List and the Ban Hosts settings.
333
+ 2.2.1 - 2016-02-15 - Chris Jean & Aaron D. Campbell
334
+ Bug Fix: Fixed issue that prevented wildcard IP ranges from being blacklisted or whitelisted.
335
+ Bug Fix: Removed warnings generated when the Away Mode module is disabled and iThemes Sync contacts the site.
336
+ Enhancement: Updated host entries in log details to link to traceip.net rather than ip-adress.com. This is because ip-adress.com does not support IPv6 addresses.
337
+ Enhancement: Updated some translatable strings relating to blacklisting and whitelisting to allow for better translations.
338
+ Enhancement: Added details about how wildcard IP ranges are converted to CIDR format (this improves performance).
339
+ 2.2.2 - 2016-02-18 - Chris Jean & Aaron D. Campbell
340
+ Bug Fix: Fixed formatting issue that could cause raw HTML output in the malware scan logs.
341
+ Enhancement: Improved error handling and reporting for malware scan issues.
342
+ 2.2.3 - 2016-02-29 - Chris Jean & Aaron D. Campbell
343
+ Security Fix: Hardened the created backups and logs directories. Thanks to Nicolas Chatelain (SYSDREAM IT Security Services) for notifying us of this issue.
344
+ Security Fix: More secure backup and log file names. Thanks to Nicolas Chatelain (SYSDREAM IT Security Services) for notifying us of this issue.
345
+ Bug Fix: The "NGINX Conf File" setting is now properly respected, causing the generated NGINX configuration file to be stored in that location.
346
+ Enhancement: Generated database backup file names now contain a human-readable timestamp in the format of YYYYMMDD-HHMMSS.
347
+ Enhancement: Zipped database backup files no longer contain a deeply nested directory structure. Instead, they only contain the sql file.
348
+ Enhancement: When the "Force Unique Nickname" feature is enabled, the generated display name now uses an improved randomization function.
349
+ Enhancement: Improved tabbing of rules in generated nginx.conf files.
350
+ Enhancement: Removed the "See what's new button" as it has fulfilled its purpose.
351
+ 2.2.4 - 2016-03-01 - Chris Jean & Aaron D. Campbell
352
+ Bug Fix: Updated code that generates the backups and logs directories to ensure that it attempts to create the parent directory if it does not exist yet.
353
+ Bug Fix: Removed warnings that could be generated if the logs directory could not be created.
354
+ Bug Fix: Database backup files sent via email no longer have a name without an extension if zipping up the file fails.
355
+ 2.2.5 - 2016-03-03 - Chris Jean & Aaron D. Campbell
356
+ Bug Fix: Fixed temporary whitelisting by preventing a temporarily whitelisted IP from being locked out.
357
+ 2.2.8 - 2016-03-17 - Chris Jean & Aaron D. Campbell
358
+ Bug Fix: Fixed issue that could cause a fatal error after changing the content directory.
359
+ Bug Fix: Updated the link to sign up for security guide download to point to a https address. This is better security and prevents warnings when submitting from a http site in some browsers.
360
+ Bug Fix: If a cryptographically secure log file name can't be generated, queue up log file writes until we can.
361
+ 2.2.9 - 2016-03-29 - Chris Jean & Aaron D. Campbell
362
+ Security Fix: No longer using document.location to build 'Show Intro' link in admin - Thanks to David Lodge (Pen Test Partners) for notifying us of this issue.
363
+ Bug Fix: Fixed some notices when certain multisite options are used on BuddyPress
364
+ Enhancement: New itsec_white_ips filter to allow plugins that work with external services to whitelist service IPs
365
+ 2.2.10 - 2016-04-19 - Chris Jean & Aaron D. Campbell
366
+ Security Fix: Better caps checks for dismissal of changed file dialog - Thanks to Julio Potier for notifying us of this issue.
367
+ Bug Fix: Make file change warning dialog text properly translatable
368
+ Enhancement: Adding 'itsec_log_event' action for logged events
369
+ 2.2.11 - 2016-05-02 - Chris Jean & Aaron D. Campbell
370
+ Bug Fix: Throw a real 403 instead of a faked 404 for hide backend - Fixes compatability with certain plugins including WordPress SEO. Hat tip to Joost de Valk (@jdevalk) and the @Yoast team for bringing this issue to our attention.
core/js/admin-dashboard.js CHANGED
@@ -2,7 +2,7 @@ jQuery( document ).ready( function () {
2
 
3
  jQuery( '#screen-meta-links' ).append(
4
  '<div id="itsec-meta-link-wrap" class="hide-if-no-js screen-meta-toggle">' +
5
- '<a href="' + document.location + '&show_admin_modal=true" class="show-settings">' + itsec_dashboard.text + '</a>' +
6
  '</div>'
7
  );
8
 
2
 
3
  jQuery( '#screen-meta-links' ).append(
4
  '<div id="itsec-meta-link-wrap" class="hide-if-no-js screen-meta-toggle">' +
5
+ '<a href="' + itsec_dashboard.url + '" class="show-settings">' + itsec_dashboard.text + '</a>' +
6
  '</div>'
7
  );
8
 
core/lib/class-itsec-lib-config-file.php CHANGED
@@ -678,6 +678,16 @@ class ITSEC_Lib_Config_File {
678
  * @return string Full path to the server config file or a blank string if modifications for the file are disabled.
679
  */
680
  public static function get_server_config_file_path() {
 
 
 
 
 
 
 
 
 
 
681
  $file = self::get_default_server_config_file_name();
682
 
683
  if ( empty( $file ) ) {
678
  * @return string Full path to the server config file or a blank string if modifications for the file are disabled.
679
  */
680
  public static function get_server_config_file_path() {
681
+ global $itsec_globals;
682
+
683
+
684
+ $server = ITSEC_Lib_Utility::get_web_server();
685
+
686
+ if ( 'nginx' === $server && ! empty( $itsec_globals['settings']['nginx_file'] ) ) {
687
+ return $itsec_globals['settings']['nginx_file'];
688
+ }
689
+
690
+
691
  $file = self::get_default_server_config_file_name();
692
 
693
  if ( empty( $file ) ) {
core/lib/class-itsec-lib-ip-tools.php ADDED
@@ -0,0 +1,649 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * iThemes Security IP tools library.
4
+ *
5
+ * Contains the ITSEC_Lib_IP_Tools class.
6
+ *
7
+ * @package iThemes_Security
8
+ */
9
+
10
+ /**
11
+ * iThemes Security IP Tools Library class.
12
+ *
13
+ * Utility class for validating and comparing IPs, as well as converting ranges. Supports IPv4 and IPv6.
14
+ *
15
+ * @package iThemes_Security
16
+ * @since 2.2.0
17
+ */
18
+ class ITSEC_Lib_IP_Tools {
19
+ /**
20
+ * Stores max cidr (number of bits) for each IP version.
21
+ *
22
+ * @static
23
+ * @access private
24
+ *
25
+ * @var array
26
+ */
27
+ private static $_max_cidr = array(
28
+ 4 => 32,
29
+ 6 => 128,
30
+ );
31
+
32
+ /**
33
+ * Validates an IP or an IP Range using CIDR notation
34
+ *
35
+ * @since 2.2.0
36
+ *
37
+ * @static
38
+ * @access public
39
+ *
40
+ * @param string $ip The IP address to validate, can be given in CIDR notation
41
+ *
42
+ * @return bool|int False for an invalid IP or range, and the IP version (4 or 6) on for a valid one
43
+ */
44
+ public static function validate( $ip ) {
45
+ $ip_parts = self::_ip_cidr( $ip );
46
+
47
+ if ( filter_var( $ip_parts->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
48
+ if ( ! isset( $ip_parts->cidr ) || self::_is_valid_cidr( $ip_parts->cidr, 4 ) ) {
49
+ return 4;
50
+ }
51
+
52
+ // Invalid CIDR
53
+ return false;
54
+ } elseif ( filter_var( $ip_parts->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
55
+ if ( ! isset( $ip_parts->cidr ) || self::_is_valid_cidr( $ip_parts->cidr, 6 ) ) {
56
+ return 6;
57
+ }
58
+
59
+ // Invalid CIDR
60
+ return false;
61
+ }
62
+
63
+ // IP is not valid v4 or v6 IP
64
+ return false;
65
+ }
66
+
67
+ /**
68
+ * Converts an IP or an IP Range using CIDR notation, to it's parts (IP and CIDR)
69
+ *
70
+ * @since 2.2.0
71
+ *
72
+ * @static
73
+ * @access private
74
+ *
75
+ * @param string $ip The IP address, can be given in CIDR notation
76
+ *
77
+ * @return object IP parts, ->ip and ->cidr
78
+ */
79
+ private static function _ip_cidr( $ip ) {
80
+ $ip_parts = new stdClass();
81
+ if ( strpos( $ip, '/' ) ) {
82
+ list( $ip_parts->ip, $ip_parts->cidr ) = explode( '/', $ip );
83
+ } else {
84
+ $ip_parts->ip = $ip;
85
+ }
86
+
87
+ return $ip_parts;
88
+ }
89
+
90
+ /**
91
+ * Validates a CIDR value for an IP version
92
+ *
93
+ * @since 2.2.0
94
+ *
95
+ * @static
96
+ * @access private
97
+ *
98
+ * @param string $cidr The CIDR value to validate
99
+ * @param int $version The IP version to validate the CIDR for (4 or 6)
100
+ *
101
+ * @return bool
102
+ */
103
+ private static function _is_valid_cidr( $cidr, $version ) {
104
+ // $version needs to be valid
105
+ if ( ! in_array( $version, array_keys( self::$_max_cidr ) ) ) {
106
+ return false;
107
+ }
108
+
109
+ // The cidr needs to be numeric and between 0 and the max
110
+ if ( isset( $cidr ) && ( ! ctype_digit( $cidr ) || $cidr > self::$_max_cidr[ $version ] ) ) {
111
+ return false;
112
+ }
113
+
114
+ return true;
115
+ }
116
+
117
+ /**
118
+ * Checks to see if a given IP/CIDR is a range
119
+ *
120
+ * @since 2.2.0
121
+ *
122
+ * @static
123
+ * @access public
124
+ *
125
+ * @param string $ip The IP address, can be given in CIDR notation
126
+ * @param int $version The IP version (4 or 6). This needs to be supplied if skipping validation (for efficiency)
127
+ * @param bool $validate True to validate the IP, and false to skip (version must be supplied to skip) (Default true)
128
+ *
129
+ * @return bool
130
+ */
131
+ public static function is_range( $ip, $version = null, $validate = true ) {
132
+ if ( $validate || ! isset( $version ) ) {
133
+ $version = self::validate( $ip );
134
+
135
+ // If the IP isn't valid, it's not a range.
136
+ if ( ! $version ) {
137
+ return false;
138
+ }
139
+ }
140
+
141
+ $ip_parts = self::_ip_cidr( $ip );
142
+
143
+ // If there is no cidr specified or if it's the max for this IP version, then this is not a range.
144
+ return !( ! isset( $ip_parts->cidr ) || $ip_parts->cidr == self::$_max_cidr[ $version ] );
145
+ }
146
+
147
+ /**
148
+ * Gets the start and end IPs for a given range
149
+ *
150
+ * @static
151
+ * @access public
152
+ *
153
+ * @param string $ip The IP address, can be given in CIDR notation
154
+ *
155
+ * @return bool|array False if the IP is invalid, and an array containing start and end IPs for the range specified otherwise
156
+ */
157
+ public static function get_ip_range( $ip ) {
158
+ $version = self::validate( $ip );
159
+ if ( ! $version ) {
160
+ return false;
161
+ }
162
+
163
+ $ip_parts = self::_ip_cidr( $ip );
164
+
165
+ // If this isn't a range, return a single address
166
+ if ( ! self::is_range( $ip, $version, false ) ) {
167
+ return array(
168
+ 'start' => $ip_parts->ip,
169
+ 'end' => $ip_parts->ip,
170
+ );
171
+ }
172
+
173
+ $mask = self::get_mask( $ip_parts->cidr, $version );
174
+
175
+ $range = array();
176
+ $range['start'] = inet_ntop( inet_pton( $ip_parts->ip ) & inet_pton( $mask ) );
177
+ $range['end'] = inet_ntop( inet_pton( $ip_parts->ip ) | ~ inet_pton( $mask ) );
178
+ return $range;
179
+ }
180
+
181
+ /**
182
+ * Gets the mask from CIDR and IP version
183
+ *
184
+ * @static
185
+ * @access public
186
+ *
187
+ * @param string $cidr The CIDR value to validate
188
+ * @param int $version The IP version to validate the CIDR for (4 or 6)
189
+ *
190
+ * @return string IP Mask
191
+ */
192
+ public static function get_mask( $cidr, $version ) {
193
+ if ( ! in_array( $version, array( 4, 6 ) ) ) {
194
+ return false;
195
+ }
196
+ $bin_mask = str_repeat( '1', $cidr ) . str_repeat( '0', self::$_max_cidr[ $version ] - $cidr );
197
+
198
+ $bin2mask_method = '_bin2mask_v' . $version;
199
+
200
+ return call_user_func( array( 'self', $bin2mask_method ), $bin_mask );
201
+ }
202
+
203
+ /**
204
+ * Gets the IPv4 mask from the binary representation
205
+ *
206
+ * @static
207
+ * @access private
208
+ *
209
+ * @param string $bin_mask The binary representation of the mask
210
+ *
211
+ * @return string IP Mask
212
+ */
213
+ private static function _bin2mask_v4( $bin_mask ) {
214
+ $mask = array();
215
+ // Eight binary bits per number
216
+ foreach ( str_split( $bin_mask, 8 ) as $num ) {
217
+ // Convert from bin to dec and append
218
+ $mask[] = base_convert( $num, 2, 10 );
219
+ }
220
+
221
+ // Explode our new hex mask into 4 character segments and implode with colons
222
+ return implode( '.', $mask );
223
+ }
224
+
225
+ /**
226
+ * Gets the IPv6 mask from the binary representation
227
+ *
228
+ * @static
229
+ * @access private
230
+ *
231
+ * @param string $bin_mask The binary representation of the mask
232
+ *
233
+ * @return string IP Mask
234
+ */
235
+ private static function _bin2mask_v6( $bin_mask ) {
236
+ $mask = '';
237
+ // Four binary bits per hex character
238
+ foreach ( str_split( $bin_mask, 4 ) as $char ) {
239
+ // Convert from bin to hex and append
240
+ $mask .= base_convert( $char, 2, 16 );
241
+ }
242
+
243
+ // Explode our new hex mask into 4 character segments and implode with colons
244
+ return implode( ':', str_split( $mask, 4 ) );
245
+ }
246
+
247
+ /**
248
+ * Checks to see if an IP or range is within another IP or range
249
+ *
250
+ * @static
251
+ * @access public
252
+ *
253
+ * @param string $ip The IP address to check to see if is contained, can be given in CIDR notation
254
+ * @param string $range The IP address to check to see if contains, can be given in CIDR notation
255
+ *
256
+ * @return bool False if the given IP or range is not completely contained in the supplied range. True if it is
257
+ */
258
+ public static function in_range( $ip, $range ) {
259
+ $ip_version = self::validate( $ip );
260
+ // If the IP isn't valid, it's not in the range
261
+ if ( ! $ip_version ) {
262
+ return false;
263
+ }
264
+
265
+ $range_version = self::validate( $range );
266
+ // If the range isn't valid or isn't the same IP version as the first IP, it's not in the range
267
+ if ( $ip_version != $range_version ) {
268
+ return false;
269
+ }
270
+
271
+ if ( ! self::is_range( $range, $range_version, false ) ) {
272
+ if ( ! self::is_range( $ip, $ip_version, false ) ) {
273
+ $ip_parts = self::_ip_cidr( $ip );
274
+ $range_parts = self::_ip_cidr( $ip );
275
+
276
+ // If neither is a range, just compare and return
277
+ return $ip_parts->ip == $range_parts->ip;
278
+ } else {
279
+ // If the IP is a range and the specified range isn't, then return false
280
+ return false;
281
+ }
282
+ }
283
+
284
+ $ip_range = array_map( 'inet_pton', self::get_ip_range( $ip ) );
285
+ $in_range = array_map( 'inet_pton', self::get_ip_range( $range ) );
286
+
287
+ return ( $in_range['start'] <= $ip_range['start'] && $ip_range['end'] <= $in_range['end'] );
288
+ }
289
+
290
+ /**
291
+ * Checks to see if an IP or range intersects with another IP or range
292
+ *
293
+ * @static
294
+ * @access public
295
+ *
296
+ * @param string $ip1 IP address, can be given in CIDR notation
297
+ * @param string $ip2 IP address, can be given in CIDR notation
298
+ *
299
+ * @return bool
300
+ */
301
+ public static function intersect( $ip1, $ip2 ) {
302
+ $ip1_version = self::validate( $ip1 );
303
+ // If the first IP isn't valid, there is no intersection
304
+ if ( ! $ip1_version ) {
305
+ return false;
306
+ }
307
+
308
+ $ip2_version = self::validate( $ip2 );
309
+ // If the second IP isn't valid or isn't the same IP version as the first IP, there is no intersection
310
+ if ( $ip1_version != $ip2_version ) {
311
+ return false;
312
+ }
313
+
314
+ // If neither is a range, just compare and return
315
+ if ( ! self::is_range( $ip1, $ip1_version, false ) && ! self::is_range( $ip2, $ip2_version, false ) ) {
316
+ $ip1_parts = self::_ip_cidr( $ip1 );
317
+ $ip2_parts = self::_ip_cidr( $ip2 );
318
+
319
+ return $ip1_parts->ip == $ip2_parts->ip;
320
+ }
321
+
322
+ $ip1_range = array_map( 'inet_pton', self::get_ip_range( $ip1 ) );
323
+ $ip2_range = array_map( 'inet_pton', self::get_ip_range( $ip2 ) );
324
+
325
+ return (
326
+ // $ip1_range start is in $ip2_range
327
+ ( $ip2_range['start'] <= $ip1_range['start'] && $ip1_range['start'] <= $ip2_range['end'] ) ||
328
+ // $ip1_range end is in $ip2_range
329
+ ( $ip2_range['start'] <= $ip1_range['end'] && $ip1_range['end'] <= $ip2_range['end'] ) ||
330
+ // $ip2_range start is in $ip1_range
331
+ ( $ip1_range['start'] <= $ip2_range['start'] && $ip2_range['start'] <= $ip1_range['end'] ) ||
332
+ // $ip2_range end is in $ip1_range
333
+ ( $ip1_range['start'] <= $ip2_range['end'] && $ip2_range['end'] <= $ip1_range['end'] )
334
+ );
335
+ }
336
+
337
+ /**
338
+ * Converts IP with * wildcards to CIDR format
339
+ *
340
+ * Limited to only contiguous wildcards at the end of an IP, and wildcards represent a whole segment not a single character or digit
341
+ *
342
+ * @since 2.2.0
343
+ *
344
+ * @static
345
+ * @access public
346
+ *
347
+ * @param string $ip The IP address, can be given in CIDR notation
348
+ * @param int $version The IP version (4 or 6). This needs to be supplied if skipping validation (for efficiency)
349
+ * @param bool $validate True to validate the IP, and false to skip (version must be supplied to skip) (Default true)
350
+ *
351
+ * @return string IP in CIDR format
352
+ */
353
+ public static function ip_wild_to_ip_cidr( $ip, $version = null, $validate = true ) {
354
+ if ( $validate || ! isset( $version ) ) {
355
+ // Replace the wildcards with zeroes and test to get version
356
+ $version = self::validate( self::_clean_wildcards( $ip ) );
357
+
358
+ // If the IP isn't valid, it's not a range.
359
+ if ( ! $version ) {
360
+ return false;
361
+ }
362
+ }
363
+
364
+ // Not meant for IPs already using CIDR notation and only works on wildcards
365
+ if ( strpos( $ip, '/' ) || false === strpos( $ip, '*' ) ) {
366
+ return $ip;
367
+ }
368
+
369
+ $wild_to_cidr_method = "_ipv{$version}_wild_to_ip_cidr";
370
+
371
+ return call_user_func( array( 'self', $wild_to_cidr_method ), $ip );
372
+ }
373
+
374
+ /**
375
+ * Converts IPv4 IP with * wildcards to CIDR format
376
+ *
377
+ * Limited to only contiguous wildcards at the end of an IP, and wildcards represent a whole segment not a single character or digit
378
+ *
379
+ * @since 2.2.0
380
+ *
381
+ * @static
382
+ * @access private
383
+ *
384
+ * @param string $ip The IP address, can be given in CIDR notation
385
+ *
386
+ * @return string IP in CIDR format
387
+ */
388
+ private static function _ipv4_wild_to_ip_cidr( $ip ) {
389
+ $host_parts = array_reverse( explode( '.', trim( $ip ) ) );
390
+
391
+ $mask = self::$_max_cidr[4];
392
+ $ip = self::_clean_wildcards( $ip );
393
+
394
+ //convert hosts with wildcards to host with netmask and create rule lines
395
+ foreach ( $host_parts as $part ) {
396
+ if ( '*' === $part ) {
397
+ $mask -= 8;
398
+ } else {
399
+ break; // We only want to deal with contiguous wildcards at the end of an IP
400
+ }
401
+ }
402
+
403
+ return "{$ip}/{$mask}";
404
+ }
405
+
406
+ /**
407
+ * Converts IPv6 IP with * wildcards to CIDR format
408
+ *
409
+ * Limited to only contiguous wildcards at the end of an IP, and wildcards represent a whole segment not a single character or digit
410
+ *
411
+ * @since 2.2.0
412
+ *
413
+ * @static
414
+ * @access private
415
+ *
416
+ * @param string $ip The IP address, can be given in CIDR notation
417
+ *
418
+ * @return string IP in CIDR format
419
+ */
420
+ private static function _ipv6_wild_to_ip_cidr( $ip ) {
421
+ $host_parts = array_reverse( explode( ':', trim( $ip ) ) );
422
+
423
+ $mask = self::$_max_cidr[6];
424
+ $ip = self::_clean_wildcards( $ip );
425
+
426
+ //convert hosts with wildcards to host with netmask and create rule lines
427
+ foreach ( $host_parts as $part ) {
428
+ if ( '*' === $part ) {
429
+ $mask -= 16;
430
+ } else {
431
+ break; // We only want to deal with contiguous wildcards at the end of an IP
432
+ }
433
+ }
434
+
435
+ return "{$ip}/{$mask}";
436
+ }
437
+
438
+ /**
439
+ * Remove wildcards, but only those that represent an entire chunk or octets
440
+ *
441
+ * @param string $ip The IP to clean
442
+ *
443
+ * @return string IP address with wildcards replaced with 0s
444
+ */
445
+ private static function _clean_wildcards( $ip ) {
446
+ $search = array(
447
+ '/([:\.])(\*(\1|$))+/', // Match all whole chunks in the middle with wildcards, or a wildcard as the whole chunk at the end
448
+ '/^\*([:\.])/', // Match a wildcard as the whole first chunk
449
+ );
450
+ return preg_replace_callback( $search, array( 'self', 'clean_wildcards_preg_replace_callback' ), $ip );
451
+ }
452
+
453
+ /**
454
+ * Used with preg_replace_callback() to replace wildcards with 0 ONLY in cases where the wildcard is the whole chunk
455
+ *
456
+ * @param array $matches The matches found by preg_replace_callback()
457
+ *
458
+ * @return string Replacement string
459
+ */
460
+ public static function clean_wildcards_preg_replace_callback( $matches ) {
461
+ return str_replace( '*', '0', $matches[0] );
462
+ }
463
+
464
+ /**
465
+ * Converts IP in CIDR notation to a regex
466
+ *
467
+ * @since 2.2.0
468
+ *
469
+ * @static
470
+ * @access public
471
+ *
472
+ * @param string $ip The IP address, can be given in CIDR notation
473
+ * @param int $version The IP version (4 or 6). This needs to be supplied if skipping validation (for efficiency)
474
+ * @param bool $validate True to validate the IP, and false to skip (version must be supplied to skip) (Default true)
475
+ *
476
+ * @return string The IP in regex format
477
+ */
478
+ public static function ip_cidr_to_ip_regex( $ip, $version = null, $validate = true ) {
479
+ // Not meant for IPs already using wildcards
480
+ if ( strpos( $ip, '*' ) ) {
481
+ return $ip;
482
+ }
483
+
484
+ if ( $validate || ! isset( $version ) ) {
485
+ $version = self::validate( $ip );
486
+
487
+ // If the IP isn't valid, it's not a range.
488
+ if ( ! $version ) {
489
+ return false;
490
+ }
491
+ }
492
+
493
+ $ip_parts = self::_ip_cidr( $ip );
494
+
495
+ $cidr_to_wild_method = "_ipv{$version}_cidr_to_ip_regex";
496
+
497
+ return call_user_func( array( 'self', $cidr_to_wild_method ), $ip_parts );
498
+ }
499
+
500
+ /**
501
+ * Converts IPv4 in CIDR notation to a regex
502
+ *
503
+ * @since 2.2.0
504
+ *
505
+ * @static
506
+ * @access private
507
+ *
508
+ * @param object $ip_parts The IP address parts (->ip and ->cidr), generated using self::_ip_cidr()
509
+ *
510
+ * @return string The IP in regex format
511
+ */
512
+ private static function _ipv4_cidr_to_ip_regex( $ip_parts ) {
513
+ // Explode IP into octets and reverse them to work backwards
514
+ $octets = array_reverse( explode( '.', $ip_parts->ip ) );
515
+
516
+ if ( ! isset( $ip_parts->cidr ) ) {
517
+ $ip_parts->cidr = self::$_max_cidr[4];
518
+ }
519
+
520
+ // How many bits are actually masked
521
+ $masked_bits = self::$_max_cidr[4] - $ip_parts->cidr;
522
+
523
+ $i = 0;
524
+ // For each set of 8 masked bits, we match a whole octet (3 digits is good enough here)
525
+ while ( $masked_bits >= 8 ) {
526
+ $octets[ $i ] = '[0-9]{1,3}';
527
+ $masked_bits -= 8;
528
+ ++$i;
529
+ }
530
+
531
+ // If there are still masked bits to deal with after handling all whole octets
532
+ if ( $masked_bits ) {
533
+ // The step is the gap between the low and high values for this octet
534
+ $step = base_convert( str_repeat( '1', $masked_bits ), 2, 10 ) + 1;
535
+ // $low is the low value for this octect, based on the step
536
+ $low = $octets[ $i ] - ( $octets[ $i ] % $step );
537
+ // The regex we use is simply a valid range of numbers in a group with alternation, ex: (0|1|2|3|4|5|6|7)
538
+ $octets[ $i ] = '(' . implode( '|', range( $low, $low + $step - 1 ) ) . ')';
539
+ }
540
+
541
+ // Re-reverse the octets array to set things straight, and put the pieces back together
542
+ // Escape the . for a literal .
543
+ return implode( '\.', array_reverse( $octets ) );
544
+ }
545
+
546
+ /**
547
+ * Converts IPv6 in CIDR notation to a regex
548
+ *
549
+ * @since 2.2.0
550
+ *
551
+ * @static
552
+ * @access private
553
+ *
554
+ * @param object $ip_parts The IP address parts (->ip and ->cidr), generated using self::_ip_cidr()
555
+ *
556
+ * @return string The IP in regex format
557
+ */
558
+ private static function _ipv6_cidr_to_ip_regex( $ip_parts ) {
559
+ // If the IP address has a :: in it, we need to expand that out so we have all eight chuks to work with
560
+ $colons = substr_count( $ip_parts->ip, ':' );
561
+ if ( $colons < 7 ) {
562
+ // Fill out all the chunks so we can properly mask them all
563
+ $ip_parts->ip = str_replace( '::', str_repeat( ':0', 7 - $colons + 1 ) . ':', $ip_parts->ip );
564
+ }
565
+
566
+ // Explode IP into chunks and reverse them to work backwards
567
+ $chunks = array_reverse( explode( ':', $ip_parts->ip ) );
568
+
569
+ if ( ! isset( $ip_parts->cidr ) ) {
570
+ $ip_parts->cidr = self::$_max_cidr[6];
571
+ }
572
+ $masked_bits = self::$_max_cidr[6] - $ip_parts->cidr;
573
+
574
+ $i = 0;
575
+ // For each set of 16 masked bits, we match a whole chunk (1-4 hex characters)
576
+ while ( $masked_bits >= 16 ) {
577
+ $chunks[ $i ] = '[0-f]{1,4}';
578
+ $masked_bits -= 16;
579
+ ++$i;
580
+ }
581
+
582
+
583
+ // If there are still masked bits to deal with after handling all whole chunks, we start working in single hex characters
584
+ if ( $masked_bits ) {
585
+ // Explode the chunk into characters and reverse them to work backwards
586
+ $characters = array_reverse( str_split( str_pad( $chunks[ $i ], 4, '0', STR_PAD_LEFT ) ) );
587
+
588
+ $j = 0;
589
+ // For each set of 4 masked bits, we match a single hex character
590
+ while ( $masked_bits >= 4 ) {
591
+ $characters[ $j ] = '[0-f]';
592
+ $masked_bits -= 4;
593
+ ++$j;
594
+ }
595
+
596
+ // If there are still masked bits to deal with after handling all whole characters
597
+ if ( $masked_bits ) {
598
+ // $step is the gap between the low and high values for this hex character (we want this in base 10 for use in operations)
599
+ $step = base_convert( str_repeat( '1', $masked_bits ), 2, 10 ) + 1;
600
+ // $value is the current value of the character in base 10
601
+ $value = base_convert( $characters[ $j ], 16, 10 );
602
+ // $low is the base 10 representation of the low value for this character based on the step
603
+ $low = $value - ( $value % $step );
604
+ // $high is the hex value (redy for our regex) of the high value for this character
605
+ $high = base_convert( $low + $step - 1, 10, 16 );
606
+ // Convert $low to hex for our
607
+ $low = base_convert( $low, 10, 16 );
608
+ // For our regex we use a character set from low to high, ex: [4-7] or [8-b]
609
+ $characters[ $j ] = "[{$low}-{$high}]";
610
+ }
611
+
612
+ // Re-reverse the characters array to set things straight, and put the pieces back together
613
+ $chunks[ $i ] = implode( array_reverse( $characters ) );
614
+ $zeroes = strlen( $chunks[ $i ] ) - strlen( ltrim( $chunks[ $i ], '0' ) );
615
+ if ( $zeroes ) {
616
+ $chunks[ $i ] = str_repeat( '0?', $zeroes ) . ltrim( $chunks[ $i ], '0' );
617
+ }
618
+ }
619
+
620
+ for ( $i; $i < count( $chunks ); $i++ ) {
621
+ $chunks[ $i ] = ltrim( $chunks[ $i ], '0' );
622
+ $num_chars = strlen( $chunks[ $i ] );
623
+ if ( $num_chars < 4 ) {
624
+ $chunks[ $i ] = str_repeat( '0?', 4 - $num_chars ) . $chunks[ $i ];
625
+ }
626
+ }
627
+
628
+ // Re-reverse the chunks array to set things straight, and put the pieces back together
629
+ $regex = implode( ':', array_reverse( $chunks ) );
630
+
631
+ // Replace multiple chunks of all zeros with a regular expression that makes them optional but still enforces accurate matching
632
+ $regex = preg_replace_callback( '/0\?0\?0\?0\?(\:0\?0\?0\?0\?)+/', array( 'self', 'ipv6_regex_preg_replace_callback' ), $regex );
633
+
634
+ return $regex;
635
+ }
636
+
637
+ /**
638
+ * Used with preg_replace_callback() to make chunks of all zeroes optional while still enforcing accurate matching
639
+ *
640
+ * @param array $matches The matches found by preg_replace_callback()
641
+ *
642
+ * @return string Replacement string
643
+ */
644
+ public static function ipv6_regex_preg_replace_callback( $matches ) {
645
+ // Get the number of colons (chunks - 1) that we are replacing so we make sure to match no more than the original number of chunks
646
+ $colons = substr_count( $matches[0], ':' );
647
+ return sprintf( '(0{0,4}:){0,%d}(0{0,4})?', $colons );
648
+ }
649
+ }
core/lib/class-itsec-lib-utility.php CHANGED
@@ -63,9 +63,11 @@ class ITSEC_Lib_Utility {
63
  * @return string Returns apache, nginx, litespeed, or iis. Defaults to apache when the server cannot be identified.
64
  */
65
  public static function get_web_server() {
 
66
  if ( defined( 'ITSEC_SERVER_OVERRIDE' ) ) {
67
  return ITSEC_SERVER_OVERRIDE;
68
  }
 
69
 
70
 
71
  if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
63
  * @return string Returns apache, nginx, litespeed, or iis. Defaults to apache when the server cannot be identified.
64
  */
65
  public static function get_web_server() {
66
+ // @codeCoverageIgnoreStart
67
  if ( defined( 'ITSEC_SERVER_OVERRIDE' ) ) {
68
  return ITSEC_SERVER_OVERRIDE;
69
  }
70
+ // @codeCoverageIgnoreEnd
71
 
72
 
73
  if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
core/modules/away-mode/class-ithemes-sync-verb-itsec-get-away-mode.php CHANGED
@@ -21,6 +21,9 @@ class Ithemes_Sync_Verb_ITSEC_Get_Away_Mode extends Ithemes_Sync_Verb {
21
  $away_enabled = false;
22
  $next = absint( $away );
23
 
 
 
 
24
  }
25
 
26
  return array(
21
  $away_enabled = false;
22
  $next = absint( $away );
23
 
24
+ } else {
25
+ $away_enabled = null;
26
+ $next = null;
27
  }
28
 
29
  return array(
core/modules/backup/class-itsec-backup.php CHANGED
@@ -238,15 +238,18 @@ class ITSEC_Backup {
238
 
239
  $return .= PHP_EOL . PHP_EOL;
240
 
241
- $current_time = current_time( 'timestamp' );
242
-
243
  //save file
244
- $file = 'backup-' . substr( sanitize_title( get_bloginfo( 'name' ) ), 0, 20 ) . '-' . $current_time . '-' . ITSEC_Lib::get_random( mt_rand( 5, 10 ) );
 
 
245
 
246
- if ( ! is_dir( $itsec_globals['ithemes_backup_dir'] ) ) {
247
- @mkdir( trailingslashit( $itsec_globals['ithemes_dir'] ) . 'backups' );
 
248
  }
249
 
 
 
250
  $handle = @fopen( $itsec_globals['ithemes_backup_dir'] . '/' . $file . '.sql', 'w+' );
251
 
252
  @fwrite( $handle, $return );
@@ -261,7 +264,7 @@ class ITSEC_Backup {
261
 
262
  $zip = new PclZip( $itsec_globals['ithemes_backup_dir'] . '/' . $file . '.zip' );
263
 
264
- if ( 0 != $zip->create( $itsec_globals['ithemes_backup_dir'] . '/' . $file . '.sql' ) ) {
265
 
266
  //delete .sql and keep zip
267
  @unlink( $itsec_globals['ithemes_backup_dir'] . '/' . $file . '.sql' );
@@ -270,10 +273,6 @@ class ITSEC_Backup {
270
 
271
  }
272
 
273
- } else {
274
-
275
- $fileext = '.sql';
276
-
277
  }
278
 
279
  if ( 2 !== $this->settings['method'] || true === $one_time ) {
238
 
239
  $return .= PHP_EOL . PHP_EOL;
240
 
 
 
241
  //save file
242
+ $file = 'backup-' . substr( sanitize_title( get_bloginfo( 'name' ) ), 0, 20 ) . '-' . current_time( 'Ymd-His' ) . '-' . wp_generate_password( 30, false );
243
+
244
+ wp_mkdir_p( $itsec_globals['ithemes_backup_dir'] );
245
 
246
+ // Make sure we have an index file to block directory listing
247
+ if ( ! file_exists( path_join( $itsec_globals['ithemes_backup_dir'], 'index.php' ) ) ) {
248
+ file_put_contents( path_join( $itsec_globals['ithemes_backup_dir'], 'index.php' ), "<?php\n// Silence is golden." );
249
  }
250
 
251
+ $fileext = '.sql';
252
+
253
  $handle = @fopen( $itsec_globals['ithemes_backup_dir'] . '/' . $file . '.sql', 'w+' );
254
 
255
  @fwrite( $handle, $return );
264
 
265
  $zip = new PclZip( $itsec_globals['ithemes_backup_dir'] . '/' . $file . '.zip' );
266
 
267
+ if ( 0 != $zip->create( $itsec_globals['ithemes_backup_dir'] . '/' . $file . '.sql', PCLZIP_OPT_REMOVE_PATH, $itsec_globals['ithemes_backup_dir'] ) ) {
268
 
269
  //delete .sql and keep zip
270
  @unlink( $itsec_globals['ithemes_backup_dir'] . '/' . $file . '.sql' );
273
 
274
  }
275
 
 
 
 
 
276
  }
277
 
278
  if ( 2 !== $this->settings['method'] || true === $one_time ) {
core/modules/ban-users/class-itsec-ban-users-admin.php CHANGED
@@ -177,14 +177,18 @@ class ITSEC_Ban_Users_Admin {
177
  }
178
 
179
  echo '<textarea id="itsec_ban_users_host_list" name="itsec_ban_users[host_list]" rows="10" cols="50">' . $host_list . PHP_EOL . '</textarea>';
180
- echo '<p>' . __( 'Use the guidelines below to enter hosts that will not be allowed access to your site. Note you cannot ban yourself.', 'better-wp-security' ) . '</p>';
181
  echo '<ul>';
182
- echo '<li>' . __( 'You may ban users by individual IP address or IP address range.', 'better-wp-security' ) . '</li>';
183
- echo '<li>' . __( 'Individual IP addesses must be in IPV4 standard format (i.e. ###.###.###.### or ###.###.###.###/##). Wildcards (*) or a netmask is allowed to specify a range of ip addresses.', 'better-wp-security' ) . '</li>';
184
- echo '<li>' . __( 'If using a wildcard (*) you must start with the right-most number in the ip field. For example ###.###.###.* and ###.###.*.* are permitted but ###.###.*.### is not.', 'better-wp-security' ) . '</li>';
185
- echo '<li><a href="http://ip-lookup.net/domain-lookup.php" target="_blank">' . __( 'Lookup IP Address.', 'better-wp-security' ) . '</a></li>';
 
 
186
  echo '<li>' . __( 'Enter only 1 IP address or 1 IP address range per line.', 'better-wp-security' ) . '</li>';
 
187
  echo '</ul>';
 
188
 
189
  }
190
 
@@ -240,6 +244,10 @@ class ITSEC_Ban_Users_Admin {
240
  }
241
 
242
  protected function get_server_config_ban_hosts_rules( $server_type ) {
 
 
 
 
243
  if ( true !== $this->settings['enabled'] ) {
244
  return '';
245
  }
@@ -261,14 +269,13 @@ class ITSEC_Ban_Users_Admin {
261
 
262
  // process hosts list
263
  foreach ( $this->settings['host_list'] as $host ) {
264
- $host = ITSEC_Lib::ip_wild_to_mask( $host );
265
- $host = trim( $host );
266
 
267
  if ( empty( $host ) ) {
268
  continue;
269
  }
270
 
271
- if ( ITSEC_Ban_Users::is_ip_whitelisted( $host ) ) {
272
  /**
273
  * @todo warn the user the ip to be banned is whitelisted
274
  */
@@ -277,26 +284,19 @@ class ITSEC_Ban_Users_Admin {
277
 
278
 
279
  if ( in_array( $server_type, array( 'apache', 'litespeed' ) ) ) {
280
- $converted_host = ITSEC_Lib::ip_mask_to_range( $host );
281
- $converted_host = trim( $converted_host );
282
 
283
  if ( empty( $converted_host ) ) {
284
  continue;
285
  }
286
 
287
-
288
- $set_env_host = str_replace( '.', '\\.', $converted_host );
289
-
290
- $set_env_rules .= "\tSetEnvIF REMOTE_ADDR \"^$set_env_host$\" DenyAccess\n"; // Ban IP
291
- $set_env_rules .= "\tSetEnvIF X-FORWARDED-FOR \"^$set_env_host$\" DenyAccess\n"; // Ban IP from a proxy
292
- $set_env_rules .= "\tSetEnvIF X-CLUSTER-CLIENT-IP \"^$set_env_host$\" DenyAccess\n"; // Ban IP from a load balancer
293
  $set_env_rules .= "\n";
294
 
295
-
296
- $require_host = str_replace( '.[0-9]+', '', $converted_host );
297
-
298
- $require_rules .= "\t\t\tRequire not ip $require_host\n";
299
- $deny_rules .= "\t\tDeny from $require_host\n";
300
  } else if ( 'nginx' === $server_type ) {
301
  $host_rules .= "\tdeny $host;\n";
302
  }
@@ -339,7 +339,7 @@ class ITSEC_Ban_Users_Admin {
339
  } else if ( 'nginx' === $server_type ) {
340
  if ( ! empty( $host_rules ) ) {
341
  $rules .= "\n";
342
- $rules .= "# " . __( 'Ban Hosts - Security > Settings > Banned Users', 'better-wp-security' ) . "\n";
343
  $rules .= $host_rules;
344
  }
345
  }
@@ -374,7 +374,7 @@ class ITSEC_Ban_Users_Admin {
374
  $rewrite_rules .= "\t\tRewriteCond %{HTTP_USER_AGENT} ^$agent [NC,OR]\n";
375
  } else if ( 'nginx' === $server_type ) {
376
  $agent = str_replace( '"', '\\"', $agent );
377
- $agent_rules .= "if (\$http_user_agent ~* \"^$agent\") { return 403; }\n";
378
  }
379
  }
380
 
@@ -526,6 +526,10 @@ class ITSEC_Ban_Users_Admin {
526
  * @return Array Sanitized array
527
  */
528
  public function sanitize_module_input( $input ) {
 
 
 
 
529
 
530
  global $itsec_globals;
531
 
@@ -579,11 +583,18 @@ class ITSEC_Ban_Users_Admin {
579
  continue;
580
  }
581
 
582
- if ( ! ITSEC_Lib::validates_ip_address( $address ) ) {
 
 
 
 
 
 
 
583
  $bad_ips[] = trim( filter_var( $address, FILTER_SANITIZE_STRING ) );
584
  }
585
 
586
- if ( ITSEC_Ban_Users::is_ip_whitelisted( $address, null, true ) ) {
587
  $white_ips[] = trim( filter_var( $address, FILTER_SANITIZE_STRING ) );
588
  }
589
 
177
  }
178
 
179
  echo '<textarea id="itsec_ban_users_host_list" name="itsec_ban_users[host_list]" rows="10" cols="50">' . $host_list . PHP_EOL . '</textarea>';
180
+ echo '<p>' . __( 'Use the guidelines below to enter hosts that will not be allowed access to your site.', 'better-wp-security' ) . '</p>';
181
  echo '<ul>';
182
+ echo '<li>' . __( 'You may ban users by individual IP address or IP address range using wildcards or CIDR notation.', 'better-wp-security' ) . '</li>';
183
+ echo '<ul>';
184
+ echo '<li>' . __( 'Individual IP addresses must be in IPv4 or IPv6 standard format (###.###.###.### or ####:####:####:####:####:####:####:####).', 'better-wp-security' ) . '</li>';
185
+ echo '<li>' . __( 'CIDR notation is allowed to specify a range of IP addresses (###.###.###.###/## or ####:####:####:####:####:####:####:####/###).', 'better-wp-security' ) . '</li>';
186
+ echo '<li>' . __( 'Wildcards are also supported with some limitations. If using wildcards (*), you must start with the right-most chunk in the IP address. For example ###.###.###.* and ###.###.*.* are permitted but ###.###.*.### is not. Wildcards are only for convenient entering of IP addresses, and will be automatically converted to their appropriate CIDR notation format on save.', 'better-wp-security' ) . '</li>';
187
+ echo '</ul>';
188
  echo '<li>' . __( 'Enter only 1 IP address or 1 IP address range per line.', 'better-wp-security' ) . '</li>';
189
+ echo '<li>' . __( 'Note: You cannot ban yourself.', 'better-wp-security' ) . '</li>';
190
  echo '</ul>';
191
+ echo '<p><a href="http://ip-lookup.net/domain-lookup.php" target="_blank">' . __( 'Lookup IP Address.', 'better-wp-security' ) . '</a></p>';
192
 
193
  }
194
 
244
  }
245
 
246
  protected function get_server_config_ban_hosts_rules( $server_type ) {
247
+ if ( ! class_exists( 'ITSEC_Lib_IP_Tools' ) ) {
248
+ $itsec_core = ITSEC_Core::get_instance();
249
+ require_once( dirname( $itsec_core->get_plugin_file() ) . '/core/lib/class-itsec-lib-ip-tools.php' );
250
+ }
251
  if ( true !== $this->settings['enabled'] ) {
252
  return '';
253
  }
269
 
270
  // process hosts list
271
  foreach ( $this->settings['host_list'] as $host ) {
272
+ $host = ITSEC_Lib_IP_Tools::ip_wild_to_ip_cidr( trim( $host ) );
 
273
 
274
  if ( empty( $host ) ) {
275
  continue;
276
  }
277
 
278
+ if ( ITSEC_Lib::is_ip_whitelisted( $host ) ) {
279
  /**
280
  * @todo warn the user the ip to be banned is whitelisted
281
  */
284
 
285
 
286
  if ( in_array( $server_type, array( 'apache', 'litespeed' ) ) ) {
287
+ $converted_host = ITSEC_Lib_IP_Tools::ip_cidr_to_ip_regex( $host );
 
288
 
289
  if ( empty( $converted_host ) ) {
290
  continue;
291
  }
292
 
293
+ $set_env_rules .= "\tSetEnvIF REMOTE_ADDR \"^$converted_host$\" DenyAccess\n"; // Ban IP
294
+ $set_env_rules .= "\tSetEnvIF X-FORWARDED-FOR \"^$converted_host$\" DenyAccess\n"; // Ban IP from a proxy
295
+ $set_env_rules .= "\tSetEnvIF X-CLUSTER-CLIENT-IP \"^$converted_host$\" DenyAccess\n"; // Ban IP from a load balancer
 
 
 
296
  $set_env_rules .= "\n";
297
 
298
+ $require_rules .= "\t\t\tRequire not ip $host\n";
299
+ $deny_rules .= "\t\tDeny from $host\n";
 
 
 
300
  } else if ( 'nginx' === $server_type ) {
301
  $host_rules .= "\tdeny $host;\n";
302
  }
339
  } else if ( 'nginx' === $server_type ) {
340
  if ( ! empty( $host_rules ) ) {
341
  $rules .= "\n";
342
+ $rules .= "\t# " . __( 'Ban Hosts - Security > Settings > Banned Users', 'better-wp-security' ) . "\n";
343
  $rules .= $host_rules;
344
  }
345
  }
374
  $rewrite_rules .= "\t\tRewriteCond %{HTTP_USER_AGENT} ^$agent [NC,OR]\n";
375
  } else if ( 'nginx' === $server_type ) {
376
  $agent = str_replace( '"', '\\"', $agent );
377
+ $agent_rules .= "\tif (\$http_user_agent ~* \"^$agent\") { return 403; }\n";
378
  }
379
  }
380
 
526
  * @return Array Sanitized array
527
  */
528
  public function sanitize_module_input( $input ) {
529
+ if ( ! class_exists( 'ITSEC_Lib_IP_Tools' ) ) {
530
+ $itsec_core = ITSEC_Core::get_instance();
531
+ require_once( dirname( $itsec_core->get_plugin_file() ) . '/core/lib/class-itsec-lib-ip-tools.php' );
532
+ }
533
 
534
  global $itsec_globals;
535
 
583
  continue;
584
  }
585
 
586
+ //Store the original user supplied IP for use in error messages or to fill back into the list if invalid
587
+ $original_address = $address;
588
+
589
+ // This checks validity and converts wildcard notation to standard CIDR notation
590
+ $address = ITSEC_Lib_IP_Tools::ip_wild_to_ip_cidr( $address );
591
+ if ( ! $address ) {
592
+ // Put the address back to the original so it's not removed from the list
593
+ $address = $original_address;
594
  $bad_ips[] = trim( filter_var( $address, FILTER_SANITIZE_STRING ) );
595
  }
596
 
597
+ if ( ITSEC_Lib::is_ip_whitelisted( $address, null, true ) ) {
598
  $white_ips[] = trim( filter_var( $address, FILTER_SANITIZE_STRING ) );
599
  }
600
 
core/modules/ban-users/class-itsec-ban-users.php CHANGED
@@ -38,7 +38,7 @@ class ITSEC_Ban_Users {
38
 
39
  }
40
 
41
- if ( ! in_array( $host, $ban_list ) && ! ITSEC_Ban_Users::is_ip_whitelisted( $host, $white_list ) ) {
42
 
43
  $ban_list[] = $host;
44
  $settings['host_list'] = $ban_list;
@@ -49,100 +49,4 @@ class ITSEC_Ban_Users {
49
  }
50
 
51
  }
52
-
53
- /**
54
- * Determines whether a given IP address is whitelisted
55
- *
56
- * @param string $ip_to_check ip to check
57
- * @param array $white_ips ip list to compare to if not yet saved to options
58
- * @param boolean $current whether to whitelist the current ip or not (due to saving, etc)
59
- *
60
- * @return boolean true if whitelisted or false
61
- */
62
- public static function is_ip_whitelisted( $ip_to_check, $white_ips = null, $current = false ) {
63
-
64
- $ip_to_check = trim( $ip_to_check );
65
-
66
- if ( $white_ips === null ) {
67
-
68
- $global_settings = get_site_option( 'itsec_global' );
69
-
70
- $white_ips = ( isset( $global_settings['lockout_white_list'] ) ? $global_settings['lockout_white_list'] : array() );
71
-
72
- }
73
-
74
- if ( $current === true ) {
75
- $white_ips[] = ITSEC_Lib::get_ip(); //add current user ip to whitelist to check automatically
76
- }
77
-
78
- foreach ( $white_ips as $white_ip ) {
79
-
80
- $converted_white_ip = ITSEC_Lib::ip_wild_to_mask( $white_ip );
81
-
82
- $check_range = ITSEC_Lib::cidr_to_range( $converted_white_ip );
83
- $ip_range = ITSEC_Lib::cidr_to_range( $ip_to_check );
84
-
85
- if ( sizeof( $check_range ) === 2 ) { //range to check
86
-
87
- $check_min = ip2long( $check_range[0] );
88
- $check_max = ip2long( $check_range[1] );
89
-
90
- if ( sizeof( $ip_range ) === 2 ) {
91
-
92
- $ip_min = ip2long( $ip_range[0] );
93
- $ip_max = ip2long( $ip_range[1] );
94
-
95
- /**
96
- * Checks cover the following scenarios:
97
- * - min-a, min-b, max-a, max-b : min-b is in a range and min-a is in b range
98
- * - min-b, min-a, max-b, max-a : max-b is in a range and max-a is in b range
99
- * - min-a, min-b, max-b, max-a : range b is encapsulated by range a
100
- * - min-b, min-a, max-a, max-b : range a is encapsulated by range b
101
- */
102
- if ( ( $check_min <= $ip_min && $ip_min <= $check_max ) || ( $check_min <= $ip_max && $ip_max <= $check_max ) ||
103
- ( $ip_min <= $check_min && $check_min <= $ip_max ) || ( $ip_min <= $check_max && $check_max <= $ip_max ) ) {
104
- return true;
105
- }
106
-
107
- } else {
108
-
109
- $ip = ip2long( $ip_range[0] );
110
-
111
- if ( $check_min <= $ip && $ip <= $check_max ) {
112
- return true;
113
- }
114
-
115
- }
116
-
117
- } else { //single ip to check
118
-
119
- $check = ip2long( $check_range[0] );
120
-
121
- if ( sizeof( $ip_range ) === 2 ) {
122
-
123
- $ip_min = ip2long( $ip_range[0] );
124
- $ip_max = ip2long( $ip_range[1] );
125
-
126
- if ( $ip_min <= $check && $check <= $ip_max ) {
127
- return true;
128
- }
129
-
130
- } else {
131
-
132
- $ip = ip2long( $ip_range[0] );
133
-
134
- if ( $check == $ip ) {
135
- return true;
136
- }
137
-
138
- }
139
-
140
- }
141
-
142
- }
143
-
144
- return false;
145
-
146
- }
147
-
148
  }
38
 
39
  }
40
 
41
+ if ( ! in_array( $host, $ban_list ) && ! ITSEC_Lib::is_ip_whitelisted( $host, $white_list ) ) {
42
 
43
  $ban_list[] = $host;
44
  $settings['host_list'] = $ban_list;
49
  }
50
 
51
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
core/modules/brute-force/class-itsec-brute-force-log.php CHANGED
@@ -44,13 +44,19 @@ final class ITSEC_Brute_Force_Log extends ITSEC_WP_List_Table {
44
  *
45
  **/
46
  function column_host( $item ) {
 
 
 
 
47
 
48
  $r = array();
49
  if ( ! is_array( $item['host'] ) ) {
50
  $item['host'] = array( $item['host'] );
51
  }
52
  foreach ( $item['host'] as $host ) {
53
- $r[] = '<a href="http://ip-adress.com/ip_tracer/' . filter_var( $host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) . '" target="_blank">' . filter_var( $host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) . '</a>';
 
 
54
  }
55
  $return = implode( '<br />', $r );
56
 
44
  *
45
  **/
46
  function column_host( $item ) {
47
+ if ( ! class_exists( 'ITSEC_Lib_IP_Tools' ) ) {
48
+ $itsec_core = ITSEC_Core::get_instance();
49
+ require_once( dirname( $itsec_core->get_plugin_file() ) . '/core/lib/class-itsec-lib-ip-tools.php' );
50
+ }
51
 
52
  $r = array();
53
  if ( ! is_array( $item['host'] ) ) {
54
  $item['host'] = array( $item['host'] );
55
  }
56
  foreach ( $item['host'] as $host ) {
57
+ if ( ITSEC_Lib_IP_Tools::validate( $host ) ) {
58
+ $r[] = '<a href="http://www.traceip.net/?query=' . urlencode( $host ) . '" target="_blank">' . esc_html( $host ) . '</a>';
59
+ }
60
  }
61
  $return = implode( '<br />', $r );
62
 
core/modules/content-directory/class-itsec-content-directory-admin.php CHANGED
@@ -291,7 +291,12 @@ class ITSEC_Content_Directory_Admin {
291
 
292
  return;
293
  }
294
-
 
 
 
 
 
295
  $new_permissions = ITSEC_Lib_Directory::get_permissions( $new_dir );
296
 
297
  if ( is_int( $old_permissions) && is_int( $new_permissions ) && ( $old_permissions != $new_permissions ) ) {
291
 
292
  return;
293
  }
294
+
295
+ // Make sure ITSEC_Core knows it's in a different place
296
+ $itsec_core = ITSEC_Core::get_instance();
297
+ $itsec_core->plugin_file = str_replace( $old_name, $new_name, $itsec_core->get_plugin_file() );
298
+
299
+
300
  $new_permissions = ITSEC_Lib_Directory::get_permissions( $new_dir );
301
 
302
  if ( is_int( $old_permissions) && is_int( $new_permissions ) && ( $old_permissions != $new_permissions ) ) {
core/modules/core/class-itsec-core-admin.php CHANGED
@@ -171,7 +171,7 @@ class ITSEC_Core_Admin {
171
 
172
  <div id="mc_embed_signup">
173
  <form
174
- action="http://ithemes.us2.list-manage.com/subscribe/post?u=7acf83c7a47b32c740ad94a4e&amp;id=5176bfed9e"
175
  method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate"
176
  target="_blank" novalidate>
177
  <div style="text-align: center;">
@@ -251,7 +251,7 @@ class ITSEC_Core_Admin {
251
  global $itsec_globals;
252
 
253
  echo '<div class="updated" id="itsec_setup_notice"><span class="it-icon-itsec"></span>'
254
- . $itsec_globals['plugin_name'] . ' ' . __( 'is almost ready.', 'better-wp-security' ) . '<a href="#" class="itsec-notice-button" onclick="document.location.href=\'?itsec_setup=yes&_wpnonce=' . wp_create_nonce( 'itsec-nag' ) . '\';">' . __( 'Secure Your Site Now', 'better-wp-security' ) . '</a><a target="_blank" href="http://ithemes.com/ithemes-security-4-is-here" class="itsec-notice-button">' . __( "See what's new in 4.0", 'better-wp-security' ) . '</a><a href="#" class="itsec-notice-hide" onclick="document.location.href=\'?itsec_setup=no&_wpnonce=' . wp_create_nonce( 'itsec-nag' ) . '\';">&times;</a>
255
  </div>';
256
 
257
  }
171
 
172
  <div id="mc_embed_signup">
173
  <form
174
+ action="https://ithemes.us2.list-manage.com/subscribe/post?u=7acf83c7a47b32c740ad94a4e&amp;id=5176bfed9e"
175
  method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate"
176
  target="_blank" novalidate>
177
  <div style="text-align: center;">
251
  global $itsec_globals;
252
 
253
  echo '<div class="updated" id="itsec_setup_notice"><span class="it-icon-itsec"></span>'
254
+ . $itsec_globals['plugin_name'] . ' ' . __( 'is almost ready.', 'better-wp-security' ) . '<a href="#" class="itsec-notice-button" onclick="document.location.href=\'?itsec_setup=yes&_wpnonce=' . wp_create_nonce( 'itsec-nag' ) . '\';">' . __( 'Secure Your Site Now', 'better-wp-security' ) . '</a><a href="#" class="itsec-notice-hide" onclick="document.location.href=\'?itsec_setup=no&_wpnonce=' . wp_create_nonce( 'itsec-nag' ) . '\';">&times;</a>
255
  </div>';
256
 
257
  }
core/modules/file-change/class-itsec-file-change-admin.php CHANGED
@@ -86,16 +86,18 @@ class ITSEC_File_Change_Admin {
86
 
87
  global $itsec_globals;
88
 
89
- wp_register_script( 'itsec_file_change_warning_js', $this->module_path . 'js/admin-file-change-warning.js', array( 'jquery' ), $itsec_globals['plugin_build'] );
90
- wp_enqueue_script( 'itsec_file_change_warning_js' );
91
- wp_localize_script(
92
- 'itsec_file_change_warning_js',
93
- 'itsec_file_change_warning',
94
- array(
95
- 'nonce' => wp_create_nonce( 'itsec_file_change_warning' ),
96
- 'url' => admin_url() . 'admin.php?page=toplevel_page_itsec_logs&itsec_log_filter=file_change',
97
- )
98
- );
 
 
99
 
100
  if ( isset( get_current_screen()->id ) && ( false !== strpos( get_current_screen()->id, 'security_page_toplevel_page_itsec_settings' ) || false !== strpos( get_current_screen()->id, 'security_page_toplevel_page_itsec_logs' ) || false !== strpos( get_current_screen()->id, 'dashboard' ) ) ) {
101
 
@@ -149,7 +151,7 @@ class ITSEC_File_Change_Admin {
149
 
150
  global $blog_id; //get the current blog id
151
 
152
- if ( ( is_multisite() && ( 1 != $blog_id || ! current_user_can( 'manage_network_options' ) ) ) || ! current_user_can( 'activate_plugins' ) ) { //only display to network admin if in multisite
153
  return;
154
  }
155
 
@@ -172,9 +174,12 @@ class ITSEC_File_Change_Admin {
172
  global $itsec_globals;
173
 
174
  printf(
175
- '<div id="itsec_file_change_warning_dialog" class="error"><p>%s %s</p> <p><input type="button" id="itsec_go_to_logs" class="button-primary" value="%s">&nbsp;<input type="button" id="itsec_dismiss_file_change_warning" class="button-secondary" value="%s"></p></div>',
176
- $itsec_globals['plugin_name'],
177
- __( 'has noticed a change to some files in your WordPress site. Please review the logs to make sure your system has not been compromised.', 'better-wp-security' ),
 
 
 
178
  __( 'View Logs', 'better-wp-security' ),
179
  __( 'Dismiss Warning', 'better-wp-security' )
180
 
@@ -863,6 +868,9 @@ class ITSEC_File_Change_Admin {
863
  * @return void
864
  */
865
  public function wp_ajax_itsec_file_change_warning_ajax() {
 
 
 
866
 
867
  if ( ! wp_verify_nonce( sanitize_text_field( $_POST['nonce'] ), 'itsec_file_change_warning' ) ) {
868
  die( __( 'Security error!', 'better-wp-security' ) );
86
 
87
  global $itsec_globals;
88
 
89
+ if ( ITSEC_Core::current_user_can_manage() ) {
90
+ wp_register_script( 'itsec_file_change_warning_js', $this->module_path . 'js/admin-file-change-warning.js', array( 'jquery' ), $itsec_globals['plugin_build'] );
91
+ wp_enqueue_script( 'itsec_file_change_warning_js' );
92
+ wp_localize_script(
93
+ 'itsec_file_change_warning_js',
94
+ 'itsec_file_change_warning',
95
+ array(
96
+ 'nonce' => wp_create_nonce( 'itsec_file_change_warning' ),
97
+ 'url' => admin_url() . 'admin.php?page=toplevel_page_itsec_logs&itsec_log_filter=file_change',
98
+ )
99
+ );
100
+ }
101
 
102
  if ( isset( get_current_screen()->id ) && ( false !== strpos( get_current_screen()->id, 'security_page_toplevel_page_itsec_settings' ) || false !== strpos( get_current_screen()->id, 'security_page_toplevel_page_itsec_logs' ) || false !== strpos( get_current_screen()->id, 'dashboard' ) ) ) {
103
 
151
 
152
  global $blog_id; //get the current blog id
153
 
154
+ if ( ! ITSEC_Core::current_user_can_manage() ) {
155
  return;
156
  }
157
 
174
  global $itsec_globals;
175
 
176
  printf(
177
+ '<div id="itsec_file_change_warning_dialog" class="error"><p>%s</p> <p><input type="button" id="itsec_go_to_logs" class="button-primary" value="%s">&nbsp;<input type="button" id="itsec_dismiss_file_change_warning" class="button-secondary" value="%s"></p></div>',
178
+ sprintf(
179
+ /* translators: 1: Plugin name */
180
+ __( '%1$s has noticed a change to some files in your WordPress site. Please review the logs to make sure your system has not been compromised.', 'better-wp-security' ),
181
+ $itsec_globals['plugin_name']
182
+ ),
183
  __( 'View Logs', 'better-wp-security' ),
184
  __( 'Dismiss Warning', 'better-wp-security' )
185
 
868
  * @return void
869
  */
870
  public function wp_ajax_itsec_file_change_warning_ajax() {
871
+ if ( ! ITSEC_Core::current_user_can_manage() ) {
872
+ die( __( 'You do not have permissions to do this!', 'better-wp-security' ) );
873
+ }
874
 
875
  if ( ! wp_verify_nonce( sanitize_text_field( $_POST['nonce'] ), 'itsec_file_change_warning' ) ) {
876
  die( __( 'Security error!', 'better-wp-security' ) );
core/modules/hide-backend/class-itsec-hide-backend-admin.php CHANGED
@@ -230,8 +230,8 @@ class ITSEC_Hide_Backend_Admin {
230
  $slug = sanitize_title( isset( $this->settings['theme_compat_slug'] ) ? $this->settings['theme_compat_slug'] : 'not_found' );
231
 
232
  $content = '<input name="itsec_hide_backend[theme_compat_slug]" id="itsec_hide_backend_strong_passwords_theme_compat_slug" value="' . $slug . '" type="text"><br />';
233
- $content .= '<label for="itsec_hide_backend_strong_passwords_theme_compat_slug">' . __( '404 Slug:', 'better-wp-security' ) . trailingslashit( get_option( 'siteurl' ) ) . '<span style="color: #4AA02C">' . $slug . '</span></label>';
234
- $content .= '<p class="description">' . __( 'The slug to redirect folks to when theme compatibility mode is enabled (just make sure it does not exist in your site).', 'better-wp-security' ) . '</p>';
235
 
236
  }
237
 
@@ -290,7 +290,7 @@ class ITSEC_Hide_Backend_Admin {
290
  }
291
 
292
  $content = '<input type="checkbox" id="itsec_hide_backend_theme_compat" name="itsec_hide_backend[theme_compat]" value="1" ' . checked( 1, $enabled, false ) . '/>';
293
- $content .= '<label for="itsec_hide_backend_theme_compat"> ' . __( 'Enable theme compatibility. If you see errors in your theme when using hide backend, in particular when going to wp-admin while not logged in, turn this on to fix them.', 'better-wp-security' ) . '</label>';
294
 
295
  }
296
 
@@ -375,7 +375,7 @@ class ITSEC_Hide_Backend_Admin {
375
 
376
  add_settings_field(
377
  'itsec_hide_backend[theme_compat]',
378
- __( 'Enable Theme Compatibility', 'better-wp-security' ),
379
  array( $this, 'hide_backend_theme_compat' ),
380
  'security_page_toplevel_page_itsec_settings',
381
  'hide_backend-settings'
@@ -383,7 +383,7 @@ class ITSEC_Hide_Backend_Admin {
383
 
384
  add_settings_field(
385
  'itsec_hide_backend[theme_compat_slug]',
386
- __( 'Theme Compatibility Slug', 'better-wp-security' ),
387
  array( $this, 'hide_backend_theme_compat_slug' ),
388
  'security_page_toplevel_page_itsec_settings',
389
  'hide_backend-settings'
230
  $slug = sanitize_title( isset( $this->settings['theme_compat_slug'] ) ? $this->settings['theme_compat_slug'] : 'not_found' );
231
 
232
  $content = '<input name="itsec_hide_backend[theme_compat_slug]" id="itsec_hide_backend_strong_passwords_theme_compat_slug" value="' . $slug . '" type="text"><br />';
233
+ $content .= '<label for="itsec_hide_backend_strong_passwords_theme_compat_slug">' . __( 'Redirect Location:', 'better-wp-security' ) . ' ' . trailingslashit( get_option( 'siteurl' ) ) . '<span style="color: #4AA02C">' . $slug . '</span></label>';
234
+ $content .= '<p class="description">' . __( 'The slug to redirect users to when they attempt to access wp-admin while not logged in.', 'better-wp-security' ) . '</p>';
235
 
236
  }
237
 
290
  }
291
 
292
  $content = '<input type="checkbox" id="itsec_hide_backend_theme_compat" name="itsec_hide_backend[theme_compat]" value="1" ' . checked( 1, $enabled, false ) . '/>';
293
+ $content .= '<label for="itsec_hide_backend_theme_compat"> ' . __( 'Redirect users to a custom location on your site, instead of throwing a 403 (forbidden) error.', 'better-wp-security' ) . '</label>';
294
 
295
  }
296
 
375
 
376
  add_settings_field(
377
  'itsec_hide_backend[theme_compat]',
378
+ __( 'Enable Redirection', 'better-wp-security' ),
379
  array( $this, 'hide_backend_theme_compat' ),
380
  'security_page_toplevel_page_itsec_settings',
381
  'hide_backend-settings'
383
 
384
  add_settings_field(
385
  'itsec_hide_backend[theme_compat_slug]',
386
+ __( 'Redirection Slug', 'better-wp-security' ),
387
  array( $this, 'hide_backend_theme_compat_slug' ),
388
  'security_page_toplevel_page_itsec_settings',
389
  'hide_backend-settings'
core/modules/hide-backend/class-itsec-hide-backend.php CHANGED
@@ -154,9 +154,10 @@ class ITSEC_Hide_Backend {
154
  wp_redirect( ITSEC_Lib::get_home_root() . sanitize_title( isset( $this->settings['theme_compat_slug'] ) ? $this->settings['theme_compat_slug'] : 'not_found' ), 302 );
155
  exit;
156
 
157
- } else { //just set the current page as a 404
158
 
159
- add_action( 'wp_loaded', array( $this, 'set_404' ) );
 
160
 
161
  }
162
 
@@ -340,17 +341,4 @@ class ITSEC_Hide_Backend {
340
 
341
  }
342
 
343
- /**
344
- * Sets 404 error at later time.
345
- *
346
- * @since 4.0.6
347
- *
348
- * @return void
349
- */
350
- public function set_404() {
351
-
352
- ITSEC_Lib::set_404();
353
-
354
- }
355
-
356
  }
154
  wp_redirect( ITSEC_Lib::get_home_root() . sanitize_title( isset( $this->settings['theme_compat_slug'] ) ? $this->settings['theme_compat_slug'] : 'not_found' ), 302 );
155
  exit;
156
 
157
+ } else {
158
 
159
+ // Throw a 403 forbidden
160
+ wp_die( __( 'This has been disabled.', 'better-wp-security' ), 403 );
161
 
162
  }
163
 
341
 
342
  }
343
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  }
core/modules/ipcheck/class-itsec-ipcheck.php CHANGED
@@ -108,7 +108,7 @@ class ITSEC_IPCheck extends ITSEC_Lockout {
108
 
109
  }
110
 
111
- if ( $this->is_ip_whitelisted( $ip ) ) {
112
  return false;
113
  }
114
 
@@ -121,7 +121,7 @@ class ITSEC_IPCheck extends ITSEC_Lockout {
121
 
122
  $action = 'check-ip';
123
 
124
- if ( ITSEC_Lib::validates_ip_address( $ip ) ) { //verify IP address is valid
125
 
126
  if ( ! isset( $this->settings['api_key'] ) || ! isset( $this->settings['api_s'] ) ) {
127
  return false; //invalid key or secret
@@ -281,11 +281,11 @@ class ITSEC_IPCheck extends ITSEC_Lockout {
281
 
282
  }
283
 
284
- if ( $this->is_ip_whitelisted( $ip ) ) {
285
  return 0;
286
  }
287
 
288
- if ( ITSEC_Lib::validates_ip_address( $ip ) ) { //verify IP address is valid
289
 
290
  if ( ! isset( $this->settings['api_key'] ) || ! isset( $this->settings['api_s'] ) ) {
291
  return -1; //invalid key or secret
108
 
109
  }
110
 
111
+ if ( ITSEC_Lib::is_ip_whitelisted( $ip ) ) {
112
  return false;
113
  }
114
 
121
 
122
  $action = 'check-ip';
123
 
124
+ if ( ITSEC_Lib_IP_Tools::validate( $ip ) ) { //verify IP address is valid
125
 
126
  if ( ! isset( $this->settings['api_key'] ) || ! isset( $this->settings['api_s'] ) ) {
127
  return false; //invalid key or secret
281
 
282
  }
283
 
284
+ if ( ITSEC_Lib::is_ip_whitelisted( $ip ) ) {
285
  return 0;
286
  }
287
 
288
+ if ( ITSEC_Lib_IP_Tools::validate( $ip ) ) { //verify IP address is valid
289
 
290
  if ( ! isset( $this->settings['api_key'] ) || ! isset( $this->settings['api_s'] ) ) {
291
  return -1; //invalid key or secret
core/modules/malware/class-itsec-malware-admin.php CHANGED
@@ -86,7 +86,7 @@ class ITSEC_Malware_Admin {
86
  $style_id = 'itsec-malware-scan-style';
87
  $style_url = plugins_url( 'css/malware.css', __FILE__ );
88
 
89
- wp_enqueue_style( $style_id, $style_url );
90
  }
91
 
92
  protected function get_scan_details() {
@@ -94,7 +94,7 @@ class ITSEC_Malware_Admin {
94
  require_once( dirname( __FILE__ ) . '/class-itsec-malware-scan-results-template.php' );
95
 
96
  $results = ITSEC_Malware_Scanner::scan();
97
- $html = ITSEC_Malware_Scan_Results_Template::get_html( $results );
98
 
99
  return $html;
100
  }
86
  $style_id = 'itsec-malware-scan-style';
87
  $style_url = plugins_url( 'css/malware.css', __FILE__ );
88
 
89
+ wp_enqueue_style( $style_id, $style_url, array(), 2 );
90
  }
91
 
92
  protected function get_scan_details() {
94
  require_once( dirname( __FILE__ ) . '/class-itsec-malware-scan-results-template.php' );
95
 
96
  $results = ITSEC_Malware_Scanner::scan();
97
+ $html = ITSEC_Malware_Scan_Results_Template::get_html( $results, true );
98
 
99
  return $html;
100
  }
core/modules/malware/class-itsec-malware-scan-results-template.php CHANGED
@@ -29,7 +29,8 @@ class ITSEC_Malware_Scan_Results_Template {
29
 
30
  if ( ! empty( $data ) ) {
31
  $details .= '<p>' . __( 'If you contact support about this error, please provide the following debug details:', 'better-wp-security' ) . "</p>\n";
32
- $details .= '<pre>' . print_r( $data, true ) . "</pre>\n";
 
33
  }
34
  }
35
 
29
 
30
  if ( ! empty( $data ) ) {
31
  $details .= '<p>' . __( 'If you contact support about this error, please provide the following debug details:', 'better-wp-security' ) . "</p>\n";
32
+
33
+ $details .= '<pre>' . esc_html( print_r( array( 'code' => $results->get_error_code(), 'data' => $data ), true ) ) . "</pre>\n";
34
  }
35
  }
36
 
core/modules/malware/class-itsec-malware-scanner.php CHANGED
@@ -87,20 +87,14 @@ final class ITSEC_Malware_Scanner {
87
  }
88
 
89
 
90
- if (
91
- ! is_array( $response ) ||
92
- ! array_key_exists( 'body', $response ) ||
93
- ! array_key_exists( 'headers', $response ) ||
94
- ! array_key_exists( 'response', $response ) ||
95
- ! isset( $response['headers']['content-type'] )
96
- ) {
97
- return new WP_Error( 'itsec-malware-scanner-malformed-wp-remote-get-response', __( 'The response from the wp_remote_get function was malformed. This could indicate an issue with WordPress.', 'better-wp-security' ) );
98
  }
99
 
100
 
101
  $body = @json_decode( $response['body'], true );
102
 
103
- if ( is_null( $body ) ) {
104
  if ( 'application/json' === $response['headers']['content-type'] ) {
105
  return new WP_Error( 'itsec-malware-scanner-invalid-json-data-in-scan-response', __( 'The scan did not complete successfully. The Sucuri server should send its response in JSON encoding. The response indicates that the encoding is JSON, but the data could not be decoded. This problem could be due to a temporary Sucuri server issue or a compatibility issue on your server. If the problem continues, please contact iThemes Security support.', 'better-wp-security' ), $response );
106
  } else {
@@ -108,10 +102,14 @@ final class ITSEC_Malware_Scanner {
108
  }
109
  } else if ( ! is_array( $body ) ) {
110
  if ( 'ERROR' === substr( $response['body'], 0, 5 ) ) {
111
- return new WP_Error( 'itsec-malware-scanner-error-received', sprintf( __( 'The scan did not complete successfully. Sucuri sent the following error: %s', 'better-wp-security' ), $response['body'] ), $response );
112
  }
113
 
114
- return new WP_Error( 'itsec-malware-scanner-unknown-scan-error', sprintf( __( 'An unknown error prevented the scan from completing successfully. The Sucuri server responded with a <code>%s</code> error code.', 'better-wp-security' ), $response['response']['code'] ), $response );
 
 
 
 
115
  }
116
 
117
 
87
  }
88
 
89
 
90
+ if ( ! is_array( $response ) || empty( $response['body'] ) ) {
91
+ return new WP_Error( 'itsec-malware-scanner-wp-remote-get-response-missing-body', __( 'The scan failed due to an unexpected technical error. The response from the wp_remote_get function does not contain a body entry. Since the body entry contains the response for the request to Sucuri\'s servers, the response cannot be processed. This could indicate a plugin/theme compatibility issue or a problem in WordPress.', 'better-wp-security' ), $response );
 
 
 
 
 
 
92
  }
93
 
94
 
95
  $body = @json_decode( $response['body'], true );
96
 
97
+ if ( is_null( $body ) && isset( $response['headers'] ) && isset( $response['headers']['content-type'] ) ) {
98
  if ( 'application/json' === $response['headers']['content-type'] ) {
99
  return new WP_Error( 'itsec-malware-scanner-invalid-json-data-in-scan-response', __( 'The scan did not complete successfully. The Sucuri server should send its response in JSON encoding. The response indicates that the encoding is JSON, but the data could not be decoded. This problem could be due to a temporary Sucuri server issue or a compatibility issue on your server. If the problem continues, please contact iThemes Security support.', 'better-wp-security' ), $response );
100
  } else {
102
  }
103
  } else if ( ! is_array( $body ) ) {
104
  if ( 'ERROR' === substr( $response['body'], 0, 5 ) ) {
105
+ return new WP_Error( 'itsec-malware-scanner-error-received', sprintf( __( 'The scan did not complete successfully. Sucuri sent the following error: %s', 'better-wp-security' ), '<code>' . $response['body'] . '</code>' ), $response );
106
  }
107
 
108
+ if ( ! empty( $response['response'] ) && ! empty( $response['response']['code'] ) ) {
109
+ return new WP_Error( 'itsec-malware-scanner-unknown-scan-error', sprintf( __( 'An unknown error prevented the scan from completing successfully. The Sucuri server responded with a <code>%s</code> error code.', 'better-wp-security' ), $response['response']['code'] ), $response );
110
+ }
111
+
112
+ return new WP_Error( 'itsec-malware-scanner-wp-remote-get-response-malformed', __( 'The scan failed due to an unexpected technical error. The response from the wp_remote_get function is missing some critical information that is needed in order to properly process the response from Sucuri\'s servers. This could indicate a plugin/theme compatibility issue or a problem in WordPress.', 'better-wp-security' ), $response );
113
  }
114
 
115
 
core/modules/malware/css/malware.css CHANGED
@@ -9,6 +9,8 @@
9
  display: none;
10
  }
11
  .itsec-malware-scan-details pre {
 
 
12
  white-space: pre-wrap;
13
  }
14
  .itsec-malware-scan-results-section {
9
  display: none;
10
  }
11
  .itsec-malware-scan-details pre {
12
+ background-color: #eaeaea;
13
+ padding: 1em;
14
  white-space: pre-wrap;
15
  }
16
  .itsec-malware-scan-results-section {
core/modules/tweaks/class-itsec-tweaks.php CHANGED
@@ -28,7 +28,7 @@ class ITSEC_Tweaks {
28
  //Disable XML-RPC
29
  if ( isset( $this->settings['disable_xmlrpc'] ) && $this->settings['disable_xmlrpc'] == 2 ) {
30
 
31
- add_filter( 'xmlrpc_enabled', array( $this, 'empty_return_function' ) );
32
  add_filter( 'bloginfo_url', array( $this, 'remove_pingback_url' ), 10, 2 );
33
 
34
  }
@@ -56,17 +56,17 @@ class ITSEC_Tweaks {
56
 
57
  //remove theme update notifications if turned on
58
  if ( ( ! isset( $itsec_globals['is_iwp_call'] ) || $itsec_globals['is_iwp_call'] === false ) && isset( $this->settings['theme_updates'] ) && $this->settings['theme_updates'] == true ) {
59
- add_action( 'plugins_loaded', array( $this, 'theme_updates' ) );
60
  }
61
 
62
  //remove plugin update notifications if turned on
63
  if ( ( ! isset( $itsec_globals['is_iwp_call'] ) || $itsec_globals['is_iwp_call'] === false ) && isset( $this->settings['plugin_updates'] ) && $this->settings['plugin_updates'] == true ) {
64
- add_action( 'plugins_loaded', array( $this, 'public_updates' ) );
65
  }
66
 
67
  //remove core update notifications if turned on
68
  if ( ( ! isset( $itsec_globals['is_iwp_call'] ) || $itsec_globals['is_iwp_call'] === false ) && isset( $this->settings['core_updates'] ) && $this->settings['core_updates'] == true ) {
69
- add_action( 'plugins_loaded', array( $this, 'core_updates' ) );
70
  }
71
 
72
  //Execute jQuery check
@@ -78,7 +78,7 @@ class ITSEC_Tweaks {
78
 
79
  //Process remove login errors
80
  if ( isset( $this->settings['login_errors'] ) && $this->settings['login_errors'] === true ) {
81
- add_filter( 'login_errors', array( $this, 'empty_return_function' ) );
82
  }
83
 
84
  //Process remove extra author archives
@@ -151,7 +151,7 @@ class ITSEC_Tweaks {
151
  if ( ! current_user_can( 'manage_options' ) ) {
152
 
153
  remove_action( 'admin_notices', 'update_nag', 3 );
154
- add_filter( 'pre_site_transient_update_core', array( $this, 'empty_return_function' ) );
155
  wp_clear_scheduled_hook( 'wp_version_check' );
156
 
157
  }
@@ -177,17 +177,6 @@ class ITSEC_Tweaks {
177
 
178
  }
179
 
180
- /**
181
- * Returns null
182
- *
183
- * @return null
184
- */
185
- public function empty_return_function() {
186
-
187
- return null;
188
-
189
- }
190
-
191
  /**
192
  * Requires a unique nicename on profile update or activate.
193
  *
@@ -197,7 +186,7 @@ class ITSEC_Tweaks {
197
  */
198
  public function force_unique_nicename( &$errors, $update, &$user ) {
199
 
200
- $display_name = isset( $user->display_name ) ? $user->display_name : ITSEC_Lib::get_random( 14 );
201
 
202
  if ( ! empty( $user->nickname ) ) {
203
 
@@ -257,7 +246,7 @@ class ITSEC_Tweaks {
257
  if ( ! current_user_can( 'manage_options' ) ) {
258
 
259
  remove_action( 'load-update-core.php', 'wp_update_plugins' );
260
- add_filter( 'pre_site_transient_update_plugins', array( $this, 'empty_return_function' ) );
261
  wp_clear_scheduled_hook( 'wp_update_plugins' );
262
 
263
  }
@@ -308,7 +297,7 @@ class ITSEC_Tweaks {
308
  if ( ! current_user_can( 'manage_options' ) ) {
309
 
310
  remove_action( 'load-update-core.php', 'wp_update_themes' );
311
- add_filter( 'pre_site_transient_update_themes', array( $this, 'empty_return_function' ) );
312
  wp_clear_scheduled_hook( 'wp_update_themes' );
313
 
314
  }
28
  //Disable XML-RPC
29
  if ( isset( $this->settings['disable_xmlrpc'] ) && $this->settings['disable_xmlrpc'] == 2 ) {
30
 
31
+ add_filter( 'xmlrpc_enabled', '__return_null' );
32
  add_filter( 'bloginfo_url', array( $this, 'remove_pingback_url' ), 10, 2 );
33
 
34
  }
56
 
57
  //remove theme update notifications if turned on
58
  if ( ( ! isset( $itsec_globals['is_iwp_call'] ) || $itsec_globals['is_iwp_call'] === false ) && isset( $this->settings['theme_updates'] ) && $this->settings['theme_updates'] == true ) {
59
+ add_action( 'init', array( $this, 'theme_updates' ) );
60
  }
61
 
62
  //remove plugin update notifications if turned on
63
  if ( ( ! isset( $itsec_globals['is_iwp_call'] ) || $itsec_globals['is_iwp_call'] === false ) && isset( $this->settings['plugin_updates'] ) && $this->settings['plugin_updates'] == true ) {
64
+ add_action( 'init', array( $this, 'public_updates' ) );
65
  }
66
 
67
  //remove core update notifications if turned on
68
  if ( ( ! isset( $itsec_globals['is_iwp_call'] ) || $itsec_globals['is_iwp_call'] === false ) && isset( $this->settings['core_updates'] ) && $this->settings['core_updates'] == true ) {
69
+ add_action( 'init', array( $this, 'core_updates' ) );
70
  }
71
 
72
  //Execute jQuery check
78
 
79
  //Process remove login errors
80
  if ( isset( $this->settings['login_errors'] ) && $this->settings['login_errors'] === true ) {
81
+ add_filter( 'login_errors', '__return_null' );
82
  }
83
 
84
  //Process remove extra author archives
151
  if ( ! current_user_can( 'manage_options' ) ) {
152
 
153
  remove_action( 'admin_notices', 'update_nag', 3 );
154
+ add_filter( 'pre_site_transient_update_core', '__return_null' );
155
  wp_clear_scheduled_hook( 'wp_version_check' );
156
 
157
  }
177
 
178
  }
179
 
 
 
 
 
 
 
 
 
 
 
 
180
  /**
181
  * Requires a unique nicename on profile update or activate.
182
  *
186
  */
187
  public function force_unique_nicename( &$errors, $update, &$user ) {
188
 
189
+ $display_name = isset( $user->display_name ) ? $user->display_name : wp_generate_password( 14, false );
190
 
191
  if ( ! empty( $user->nickname ) ) {
192
 
246
  if ( ! current_user_can( 'manage_options' ) ) {
247
 
248
  remove_action( 'load-update-core.php', 'wp_update_plugins' );
249
+ add_filter( 'pre_site_transient_update_plugins', '__return_null' );
250
  wp_clear_scheduled_hook( 'wp_update_plugins' );
251
 
252
  }
297
  if ( ! current_user_can( 'manage_options' ) ) {
298
 
299
  remove_action( 'load-update-core.php', 'wp_update_themes' );
300
+ add_filter( 'pre_site_transient_update_themes', '__return_null' );
301
  wp_clear_scheduled_hook( 'wp_update_themes' );
302
 
303
  }
history.txt CHANGED
@@ -461,3 +461,41 @@
461
  Enhancement: Added "Use MySQLi" entry to the "System Information" section of Security > Dashboard to show whether the MySQLi driver is enabled.
462
  Enhancement: Updated the "SQL Mode" entry in the "System Information" section of Security > Dashboard to show the full details if that value is set.
463
  Enhancement: Improved code that ensures that tables and options table entries created by iThemes Security are removed on uninstall only when no other iThemes Security plugin is active.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  Enhancement: Added "Use MySQLi" entry to the "System Information" section of Security > Dashboard to show whether the MySQLi driver is enabled.
462
  Enhancement: Updated the "SQL Mode" entry in the "System Information" section of Security > Dashboard to show the full details if that value is set.
463
  Enhancement: Improved code that ensures that tables and options table entries created by iThemes Security are removed on uninstall only when no other iThemes Security plugin is active.
464
+ 5.3.0 - 2016-02-17 - Chris Jean & Aaron D. Campbell
465
+ New Feature: Added support for IPv6 addresses. This includes support for IPv6 in lockouts, ban hosts, and white lists.
466
+ Bug Fix: Fixed issue that could cause username-based lockouts to fail for long usernames.
467
+ Bug Fix: Fixed issue that prevented wildcard IP ranges from being blacklisted or whitelisted.
468
+ Bug Fix: Removed warnings generated when the Away Mode module is disabled and iThemes Sync contacts the site.
469
+ Enhancement: Updated descriptions of valid IP and IP range formats for the Lockout White List and the Ban Hosts settings.
470
+ Enhancement: Updated host entries in log details to link to traceip.net rather than ip-adress.com. This is because ip-adress.com does not support IPv6 addresses.
471
+ Enhancement: Updated some translatable strings relating to blacklisting and whitelisting to allow for better translations.
472
+ Enhancement: Added details about how wildcard IP ranges are converted to CIDR format (this improves performance).
473
+ 5.3.1 - 2016-02-29 - Chris Jean & Aaron D. Campbell
474
+ Security Fix: Hardened the created backups and logs directories. Thanks to Nicolas Chatelain (SYSDREAM IT Security Services) for notifying us of this issue.
475
+ Security Fix: More secure backup and log file names. Thanks to Nicolas Chatelain (SYSDREAM IT Security Services) for notifying us of this issue.
476
+ Bug Fix: The "NGINX Conf File" setting is now properly respected, causing the generated NGINX configuration file to be stored in that location.
477
+ Enhancement: Generated database backup file names now contain a human-readable timestamp in the format of YYYYMMDD-HHMMSS.
478
+ Enhancement: Zipped database backup files no longer contain a deeply nested directory structure. Instead, they only contain the sql file.
479
+ Enhancement: When the "Force Unique Nickname" feature is enabled, the generated display name now uses an improved randomization function.
480
+ Enhancement: Improved tabbing of rules in generated nginx.conf files.
481
+ Enhancement: Removed the "See what's new button" as it has fulfilled its purpose.
482
+ 5.3.2 - 2016-03-01 - Chris Jean & Aaron D. Campbell
483
+ Bug Fix: Updated code that generates the backups and logs directories to ensure that it attempts to create the parent directory if it does not exist yet.
484
+ Bug Fix: Removed warnings that could be generated if the logs directory could not be created.
485
+ Bug Fix: Database backup files sent via email no longer have a name without an extension if zipping up the file fails.
486
+ 5.3.3 - 2016-03-03 - Chris Jean & Aaron D. Campbell
487
+ Bug Fix: Fixed temporary whitelisting by preventing a temporarily whitelisted IP from being locked out.
488
+ 5.3.4 - 2016-03-17 - Chris Jean & Aaron D. Campbell
489
+ Bug Fix: Fixed issue that could cause a fatal error after changing the content directory.
490
+ Bug Fix: Updated the link to sign up for security guide download to point to a https address. This is better security and prevents warnings when submitting from a http site in some browsers.
491
+ Bug Fix: If a cryptographically secure log file name can't be generated, queue up log file writes until we can.
492
+ 5.3.5 - 2016-03-29 - Chris Jean & Aaron D. Campbell
493
+ Security Fix: No longer using document.location to build 'Show Intro' link in admin - Thanks to David Lodge (Pen Test Partners) for notifying us of this issue.
494
+ Bug Fix: Fixed some notices when certain multisite options are used on BuddyPress
495
+ Enhancement: New itsec_white_ips filter to allow plugins that work with external services to whitelist service IPs
496
+ 5.3.6 - 2016-04-19 - Chris Jean & Aaron D. Campbell
497
+ Security Fix: Better caps checks for dismissal of changed file dialog - Thanks to Julio Potier for notifying us of this issue.
498
+ Bug Fix: Make file change warning dialog text properly translatable
499
+ Enhancement: Adding 'itsec_log_event' action for logged events
500
+ 5.3.7 - 2016-05-02 - Chris Jean & Aaron D. Campbell
501
+ Bug Fix: Throw a real 403 instead of a faked 404 for hide backend - Fixes compatability with certain plugins including WordPress SEO. Hat tip to Joost de Valk (@jdevalk) and the @Yoast team for bringing this issue to our attention.
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === iThemes Security (formerly Better WP Security) ===
2
  Contributors: ithemes, chrisjean, aaroncampbell, gerroald, mattdanner
3
- Tags: security, malware, secure, multi-site, network, mu, login, lockdown, htaccess, hack, header, cleanup, ban, restrict, access, protect, protection, disable, images, image, hotlink, admin, username, database, prefix, wp-content, rename, directory, directories, secure, SSL, iThemes, BackupBuddy, Exchange, iThemes Exchange
4
  Requires at least: 4.1
5
- Tested up to: 4.4.2
6
- Stable tag: 5.2.1
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -12,49 +12,51 @@ Released under the terms of the GNU General Public License.
12
 
13
  == Description ==
14
 
15
- = iThemes Security (formerly Better WP Security), #1 WordPress Security Plugin =
16
 
17
  iThemes Security (formerly Better WP Security) gives you over 30+ ways to secure and protect your WordPress site. On average, 30,000 new websites are hacked each day. WordPress sites can be an easy target for attacks because of plugin vulnerabilities, weak passwords and obsolete software.
18
 
19
- Most WordPress admins don't know they're vulnerable, but iThemes Security works to fix common holes, stop automated attacks and strengthen user credentials. With one-click activation for most features, as well as advanced features for experienced users, iThemes Security can help protect any WordPress site.
20
 
21
  = Maintained and Supported by iThemes =
22
 
23
- iThemes has been building and supporting WordPress tools since 2008. With our full range of WordPress <a href="http://ithemes.com/find/plugins/">plugins</a>, <a href="http://ithemes.com/find/themes/">themes</a> and <a href="http://webdesign.com">training</a>, WordPress security is the next step in providing you with everything you need to build the WordPress web.
24
 
25
- = Get Support and Pro Features =
26
 
27
  Get added peace of mind with professional support from our expert team and pro features to take your site's security to the next level with <a href="http://ithemes.com/security">iThemes Security Pro</a>.
28
 
29
  Pro Features:
30
 
31
- * User Action Logging - Track when user's edit content, login or logout.
32
  * Two-Factor Authentication - Use a mobile app such as Google Authenticator or Authy to generate a code or have a generated code emailed to you.
33
- * Import/Export Settings - Saves time setting up multiple WordPress sites.
34
  * Malware Scan Scheduling - Have your site scanned for malware automatically each day. If an issue is found, an email is sent with the details.
35
- * Password Expiration - Set a maximum password age and force users to choose a new password. You can also force all users to choose a new password immediately (if needed).
36
- * Generate Strong Passwords - Generate strong passwords right from your profile screen.
 
 
 
37
  * Dashboard Widget - Manage important tasks such as user banning and system scans right from the WordPress dashboard.
38
  * Online File Comparison - When a file change is detected it will scan the origin of the files to determine if the change was malicious or not. Currently works only in WordPress core but plugins and themes are coming.
39
  * Temporary Privilege Escalation - give a contractor or someone else temporary admin or editor access to your site that will automatically reset itself.
40
  * wp-cli Integration - Manage your site's security from the command line.
41
- * Google reCAPTCHA - Protect your site against spammers.
42
 
43
  = iThemes Sync Integration =
44
 
45
- Manage more than one site? Manage away mode, release lockouts and make sure your WordPress site is up to date with <a href="http://ithemes.com/sync/">iThemes Sync</a>.
46
 
47
- = iThemes Brute Force Protection Network =
48
 
49
- Network Brute Force Protection takes brute force protection to the next level by further banning users who have tried to break into other sites from breaking into yours. The iThemes Brute Force Protection Network will automatically report IP addresses of failed login attempts to iThemes and will block them for a length of time necessary to protect your site based on the number of sites that have seen a similar attack.
50
 
51
  = Protect =
52
 
53
- Hiding parts of your site is helpful, but won't prevent all attacks. In addition to obscuring sensitive areas of your WordPress site, iThemes Security works to protect it by blocking bad users and increasing the security of passwords and other vital information.
54
 
 
55
  * Scans your site to instantly report where vulnerabilities exist and fixes them in seconds
56
  * Bans troublesome user agents, bots and other hosts
57
- * Prevents brute force attacks by banning hosts and users with too many invalid login attempts
58
  * Strengthens server security
59
  * Enforces strong passwords for all accounts of a configurable minimum role
60
  * Forces SSL for admin pages (on supporting servers)
@@ -90,16 +92,16 @@ iThemes Security hides common WordPress security vulnerabilities, preventing att
90
 
91
  iThemes Security makes regular backups of your WordPress database, allowing you to get back online quickly in the event of an attack. Use iThemes Security to create and email database backups on a customizable schedule.
92
 
93
- For complete site backups and the ability to restore or move WordPress easily, check out <a href="http://ithemes.com/purchase/backupbuddy">BackupBuddy</a> by iThemes.
94
 
95
- = Other Benefits =
96
 
97
  * Makes it easier for users not accustomed to WordPress to remember login and admin URLs by customizing default admin URLs
98
  * Detects hidden 404 errors on your site that can affect your SEO such as bad links and missing images
99
 
100
- = Tutorials =
101
 
102
- Learn how to use iThemes Security with our series of <a href="http://ithemes.com/tutorial/category/ithemes-security/">in-depth tutorial videos</a> taught by lead developer Chris Wiegman:
103
 
104
  * <a href="http://ithemes.com/tutorials/getting-started-ithemes-security-part-1/">Getting Started</a>
105
  * <a href="http://ithemes.com/tutorials/getting-started-ithemes-security-part-2-global-settings/">Global Settings</a>
@@ -107,7 +109,7 @@ Learn how to use iThemes Security with our series of <a href="http://ithemes.com
107
  * <a href="http://ithemes.com/tutorials/getting-started-ithemes-security-part-4-away-mode/">Away Mode</a>
108
  * <a href="http://ithemes.com/tutorials/getting-started-ithemes-security-part-5-banned-users/">Banned Users</a>
109
  * <a href="http://ithemes.com/tutorials/getting-started-ithemes-security-part-6-brute-force-protection/">Brute Force Protection</a>
110
- * Many more to come!
111
 
112
  = Compatibility =
113
 
@@ -123,7 +125,7 @@ Please <a href="http://ithemes.com/contact" target="_blank">let us know</a> if y
123
 
124
  = Warning =
125
 
126
- Please read the installation instructions and FAQ before installing this plugin. iThemes Security makes significant changes to your database and other site files which can be problematic, so a backup is strongly recommended before making any changes to your site with this plugin. While problems are rare, most support requests involve the failure to make a proper backup before installation.
127
 
128
  == Installation ==
129
 
@@ -189,6 +191,52 @@ Free support may be available with the help of the community in the <a href="htt
189
 
190
  == Changelog ==
191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  = 5.2.1 =
193
  * Bug Fix: Comparisons of IPv4 addresses and ranges now include the IP's at the edge of the ranges.
194
  * Bug Fix: IPv4 tests now work as expected when deciding if a blacklisted IP or range overlaps a whitelisted IP's and ranges.
@@ -290,7 +338,7 @@ Free support may be available with the help of the community in the <a href="htt
290
 
291
  = 4.6.10 =
292
  * Bug Fix: Fixed regression that prevented adding wildcard IP's in the form of 'XXX.XXX.XXX.*' to Ban Hosts.
293
- * Bug Fix: When a file scan is run from iThemes Sync, a warning will no longer be added to the site's error log.
294
 
295
  = 4.6.8 =
296
  * Enhancement: Minor refactoring for performance and scalability.
@@ -1471,6 +1519,18 @@ This release is a complete rewrite from the ground up. Special thanks to Cory Mi
1471
 
1472
  == Upgrade Notice ==
1473
 
 
 
 
 
 
 
 
 
 
 
 
 
1474
  = 4.6.8 =
1475
  Version 4.6.8 contains minor bugfixes and enhancements and is recommended for all users.
1476
 
1
  === iThemes Security (formerly Better WP Security) ===
2
  Contributors: ithemes, chrisjean, aaroncampbell, gerroald, mattdanner
3
+ Tags: security, security plugin, malware, hack, secure, block, SSL, admin, htaccess, lockdown, login, protect, protection, anti virus, attack, injection, login security, maintenance, permissions, prevention, authentication, administration, password, brute force, ban, permissions, bots, user agents, xml rpc, security log
4
  Requires at least: 4.1
5
+ Tested up to: 4.5.1
6
+ Stable tag: 5.3.7
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
12
 
13
  == Description ==
14
 
15
+ = iThemes Security is the #1 WordPress Security Plugin =
16
 
17
  iThemes Security (formerly Better WP Security) gives you over 30+ ways to secure and protect your WordPress site. On average, 30,000 new websites are hacked each day. WordPress sites can be an easy target for attacks because of plugin vulnerabilities, weak passwords and obsolete software.
18
 
19
+ Most WordPress admins don't know they're vulnerable, but iThemes Security works to lock down Wordpress, fix common holes, stop automated attacks and strengthen user credentials. With one-click activation for most features, as well as advanced features for experienced users, our WordPress security plugin can help harden WordPress.
20
 
21
  = Maintained and Supported by iThemes =
22
 
23
+ iThemes has been building and supporting WordPress tools since 2008 like BackupBuddy, our <a href="http://ithemes.com/purchase/backupbuddy">WordPress backup plugin</a>. With our full range of WordPress <a href="http://ithemes.com/find/plugins/">plugins</a>, <a href="http://ithemes.com/find/themes/">themes</a> and <a href="http://ithemes.com/training">training</a>, WordPress security is the next step in providing you with everything you need to build the WordPress web.
24
 
25
+ = Get Plugin Support and Pro Features =
26
 
27
  Get added peace of mind with professional support from our expert team and pro features to take your site's security to the next level with <a href="http://ithemes.com/security">iThemes Security Pro</a>.
28
 
29
  Pro Features:
30
 
 
31
  * Two-Factor Authentication - Use a mobile app such as Google Authenticator or Authy to generate a code or have a generated code emailed to you.
32
+ * WordPress Salts & Security Keys - The iThemes Security plugin makes updating your WordPress keys and salts easy.
33
  * Malware Scan Scheduling - Have your site scanned for malware automatically each day. If an issue is found, an email is sent with the details.
34
+ * Password Security - Generate strong passwords right from your profile screen.
35
+ * Password Expiration - Set a maximum password age and force users to choose a new password. You can also force all users to choose a new password immediately (if needed).
36
+ * Google reCAPTCHA - Protect your site against spammers.
37
+ * User Action Logging - Track when users edit content, login or logout.
38
+ * Import/Export Settings - Saves time setting up multiple WordPress sites.
39
  * Dashboard Widget - Manage important tasks such as user banning and system scans right from the WordPress dashboard.
40
  * Online File Comparison - When a file change is detected it will scan the origin of the files to determine if the change was malicious or not. Currently works only in WordPress core but plugins and themes are coming.
41
  * Temporary Privilege Escalation - give a contractor or someone else temporary admin or editor access to your site that will automatically reset itself.
42
  * wp-cli Integration - Manage your site's security from the command line.
43
+
44
 
45
  = iThemes Sync Integration =
46
 
47
+ Manage more than one WordPress site? Manage Away Mode, release lockouts and keep your themes, plugins and WordPress core up to date from one dashboard with iThemes Sync. <a href="http://ithemes.com/sync/">Start managing 10 WordPress sites for free with iThemes Sync</a>.
48
 
49
+ = iThemes Brute Force Attack Protection Network =
50
 
51
+ iThemes Security takes brute force attack protection to the next level by banning users who have tried to break into other sites from breaking into yours. The iThemes Brute Force Attack Protection Network will automatically report IP addresses of failed login attempts and will block them for a length of time necessary to protect your site based on the number of sites that have seen a similar attack.
52
 
53
  = Protect =
54
 
55
+ iThemes Security works to protect your site by blocking bad users and increasing the security of passwords and other vital information.
56
 
57
+ * Prevents brute force attacks by banning hosts and users with too many invalid login attempts
58
  * Scans your site to instantly report where vulnerabilities exist and fixes them in seconds
59
  * Bans troublesome user agents, bots and other hosts
 
60
  * Strengthens server security
61
  * Enforces strong passwords for all accounts of a configurable minimum role
62
  * Forces SSL for admin pages (on supporting servers)
92
 
93
  iThemes Security makes regular backups of your WordPress database, allowing you to get back online quickly in the event of an attack. Use iThemes Security to create and email database backups on a customizable schedule.
94
 
95
+ For complete site backups and the ability to restore or move WordPress to a new host or domain, check out <a href="http://ithemes.com/purchase/backupbuddy">BackupBuddy</a>.
96
 
97
+ = Other WordPress Security Benefits =
98
 
99
  * Makes it easier for users not accustomed to WordPress to remember login and admin URLs by customizing default admin URLs
100
  * Detects hidden 404 errors on your site that can affect your SEO such as bad links and missing images
101
 
102
+ = WordPress Security Tutorials =
103
 
104
+ Learn how to use our WordPress security plugin with our series of <a href="http://ithemes.com/tutorial/category/ithemes-security/">in-depth tutorial videos</a>:
105
 
106
  * <a href="http://ithemes.com/tutorials/getting-started-ithemes-security-part-1/">Getting Started</a>
107
  * <a href="http://ithemes.com/tutorials/getting-started-ithemes-security-part-2-global-settings/">Global Settings</a>
109
  * <a href="http://ithemes.com/tutorials/getting-started-ithemes-security-part-4-away-mode/">Away Mode</a>
110
  * <a href="http://ithemes.com/tutorials/getting-started-ithemes-security-part-5-banned-users/">Banned Users</a>
111
  * <a href="http://ithemes.com/tutorials/getting-started-ithemes-security-part-6-brute-force-protection/">Brute Force Protection</a>
112
+
113
 
114
  = Compatibility =
115
 
125
 
126
  = Warning =
127
 
128
+ Please read the installation instructions and FAQ before installing this WordPress security plugin. iThemes Security makes significant changes to your database and other site files which can be problematic, so a backup is strongly recommended before making any changes to your site with this plugin. While problems are rare, most support requests involve the failure to make a proper backup before installation.
129
 
130
  == Installation ==
131
 
191
 
192
  == Changelog ==
193
 
194
+ = 5.3.7 =
195
+ * Bug Fix: Throw a real 403 instead of a faked 404 for hide backend - Fixes compatability with certain plugins including WordPress SEO. Hat tip to Joost de Valk (@jdevalk) and the @Yoast team for bringing this issue to our attention.
196
+
197
+ = 5.3.6 =
198
+ * Security Fix: Better caps checks for dismissal of changed file dialog - Thanks to Julio Potier for notifying us of this issue.
199
+ * Bug Fix: Make file change warning dialog text properly translatable
200
+ * Enhancement: Adding 'itsec_log_event' action for logged events
201
+
202
+ = 5.3.5 =
203
+ * Security Fix: No longer using document.location to build 'Show Intro' link in admin - Thanks to David Lodge (Pen Test Partners) for notifying us of this issue.
204
+ * Bug Fix: Fixed some notices when certain multisite options are used on BuddyPress
205
+ * Enhancement: New itsec_white_ips filter to allow plugins that work with external services to whitelist service IPs
206
+
207
+ = 5.3.4 =
208
+ * Bug Fix: Fixed issue that could cause a fatal error after changing the content directory.
209
+ * Bug Fix: Updated the link to sign up for security guide download to point to a https address. This is better security and prevents warnings when submitting from a http site in some browsers.
210
+ * Bug Fix: If a cryptographically secure log file name can't be generated, queue up log file writes until we can.
211
+
212
+ = 5.3.3 =
213
+ * Bug Fix: Fixed temporary whitelisting by preventing a temporarily whitelisted IP from being locked out.
214
+
215
+ = 5.3.2 =
216
+ * Bug Fix: Updated code that generates the backups and logs directories to ensure that it attempts to create the parent directory if it does not exist yet.
217
+ * Bug Fix: Removed warnings that could be generated if the logs directory could not be created.
218
+ * Bug Fix: Database backup files sent via email no longer have a name without an extension if zipping up the file fails.
219
+
220
+ = 5.3.1 =
221
+ * Security Fix: Hardened the created backups and logs directories. Thanks to Nicolas Chatelain (SYSDREAM IT Security Services) for notifying us of this issue.
222
+ * Security Fix: More secure backup and log file names. Thanks to Nicolas Chatelain (SYSDREAM IT Security Services) for notifying us of this issue.
223
+ * Bug Fix: The "NGINX Conf File" setting is now properly respected, causing the generated NGINX configuration file to be stored in that location.
224
+ * Enhancement: Generated database backup file names now contain a human-readable timestamp in the format of YYYYMMDD-HHMMSS.
225
+ * Enhancement: Zipped database backup files no longer contain a deeply nested directory structure. Instead, they only contain the sql file.
226
+ * Enhancement: When the "Force Unique Nickname" feature is enabled, the generated display name now uses an improved randomization function.
227
+ * Enhancement: Improved tabbing of rules in generated nginx.conf files.
228
+ * Enhancement: Removed the "See what's new button" as it has fulfilled its purpose.
229
+
230
+ = 5.3.0 =
231
+ * New Feature: Added support for IPv6 addresses. This includes support for IPv6 in lockouts, ban hosts, and white lists.
232
+ * Bug Fix: Fixed issue that could cause username-based lockouts to fail for long usernames.
233
+ * Bug Fix: Fixed issue that prevented wildcard IP ranges from being blacklisted or whitelisted.
234
+ * Bug Fix: Removed warnings generated when the Away Mode module is disabled and iThemes Sync contacts the site.
235
+ * Enhancement: Updated descriptions of valid IP and IP range formats for the Lockout White List and the Ban Hosts settings.
236
+ * Enhancement: Updated host entries in log details to link to traceip.net rather than ip-adress.com. This is because ip-adress.com does not support IPv6 addresses.
237
+ * Enhancement: Updated some translatable strings relating to blacklisting and whitelisting to allow for better translations.
238
+ * Enhancement: Added details about how wildcard IP ranges are converted to CIDR format (this improves performance).
239
+
240
  = 5.2.1 =
241
  * Bug Fix: Comparisons of IPv4 addresses and ranges now include the IP's at the edge of the ranges.
242
  * Bug Fix: IPv4 tests now work as expected when deciding if a blacklisted IP or range overlaps a whitelisted IP's and ranges.
338
 
339
  = 4.6.10 =
340
  * Bug Fix: Fixed regression that prevented adding wildcard IP's in the form of 'XXX.XXX.XXX.*' to Ban Hosts.
341
+ * Bug Fix: When a file scan is run from iThemes Sync, a warning will no longer be added to the site's error log.
342
 
343
  = 4.6.8 =
344
  * Enhancement: Minor refactoring for performance and scalability.
1519
 
1520
  == Upgrade Notice ==
1521
 
1522
+ = 5.3.7 =
1523
+ Version 5.3.6 contains a bugfix that fixes compatability with WordPress SEO and is recommended for all users.
1524
+
1525
+ = 5.3.6 =
1526
+ Version 5.3.6 contains minor bugfixes and a small security fix and is recommended for all users.
1527
+
1528
+ = 5.3.5 =
1529
+ Version 5.3.5 contains minor bugfixes and enhancements and is recommended for all users.
1530
+
1531
+ = 5.3.4 =
1532
+ Version 5.3.4 contains minor bugfixes and enhancements and is recommended for all users.
1533
+
1534
  = 4.6.8 =
1535
  Version 4.6.8 contains minor bugfixes and enhancements and is recommended for all users.
1536