PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 2.11.8
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v2.11.8
3.0.0 2.11.12 2.11.11 2.11.10 2.11.9 2.11.7 2.11.8 2.11.6 2.11.5 2.11.4 2.11.3 2.11.1 2.11.2 2.11.0 2.10.5 2.10.4 2.10.3 2.10.2 2.10.1 2.10.0 2.9.9 2.9.8 2.9.6 2.9.7 2.9.5 All 88 releases
← All changes | admin/class-admin.php +148 -8 2.11.72.11.8 View file →
@@ -1545,8 +1545,11 @@
1545 1545 'reviewChanges' => __( 'Review changes', 'vigilante' ),
1546 1546 'hideChanges' => __( 'Hide changes', 'vigilante' ),
1547 1547 'changes' => __( 'Changes', 'vigilante' ),
1548 1548 'diffUnavailable' => __( 'Diff not available for this file (baseline was created before diff tracking was added). Approve to enable diff on future changes.', 'vigilante' ),
1549 + 'diffNetwork' => __( 'This file belongs to the whole network, so its line changes are only shown to network administrators, on the main site.', 'vigilante' ),
1550 + 'diffRescan' => __( 'Run a new scan to see the line changes of this file.', 'vigilante' ),
1551 + 'diffRedaction' => __( 'The line changes of this file are not shown because a value in it could not be hidden safely. The change itself is still detected.', 'vigilante' ),
1549 1552 'diffEmpty' => __( 'No line-level changes detected (may be whitespace or reordering).', 'vigilante' ),
1550 1553 'diffLines' => __( 'lines', 'vigilante' ),
1551 1554 // Under Attack mode strings
1552 1555 'underAttackConfirmActivate' => __( 'Activate Under Attack mode? All visitors will see a verification page for the next 4 hours.', 'vigilante' ),
@@ -2115,10 +2118,46 @@
2115 2118 *
2116 2119 * @since 2.10.4
2117 2120 * @return bool
2118 2121 */
2122 + private function forwarded_chain_readings() {
2123 + // Shown, not decided on: the firewall resolves the address elsewhere.
2124 + $chain = Vigilante_IP_Utils::trusted_forwarded_for();
2125 +
2126 + if ( '' === $chain ) {
2127 + return array();
2128 + }
2129 +
2130 + $public = array();
2131 +
2132 + foreach ( explode( ',', $chain ) as $entry ) {
2133 + $address = Vigilante_IP_Utils::unmap_ipv4( trim( $entry ) );
2134 +
2135 + if ( filter_var( $address, FILTER_VALIDATE_IP ) && ! Vigilante_IP_Utils::is_own_network( $address ) ) {
2136 + $public[] = $address;
2137 + }
2138 + }
2139 +
2140 + if ( count( $public ) < 2 ) {
2141 + return array();
2142 + }
2143 +
2144 + return array(
2145 + 'now' => Vigilante_IP_Utils::client_from_chain( $chain ),
2146 + 'before' => $public[0],
2147 + );
2148 + }
2149 +
2150 + /**
2151 + * Whether the user tools of this screen are out of reach for this user
2152 + *
2153 + * @return bool
2154 + */
2119 2155 private function user_actions_locked() {
2120 - return is_multisite() && ! current_user_can( 'manage_network_users' );
2156 + // On a single site edit_user maps to edit_users, which a custom role with
2157 + // manage_options may lack: since 2.11.8 approving and rejecting a pending
2158 + // registration ask for it, so the buttons have to say so there too.
2159 + return is_multisite() ? ! current_user_can( 'manage_network_users' ) : ! current_user_can( 'edit_users' );
2121 2160 }
2122 2161
2123 2162 /**
2124 2163 * Print the notice for user tools that cannot be used from this site
@@ -2130,9 +2169,13 @@
2130 2169 return;
2131 2170 }
2132 2171 ?>
2133 2172 <div class="notice notice-info inline" style="margin:10px 0 16px;padding:8px 12px;">
2173 + <?php if ( is_multisite() ) : ?>
2134 2174 <p style="margin:0;"><?php esc_html_e( 'These tools act on user accounts, which on a network belong to the whole network rather than to one site. WordPress reserves that to network administrators, so they are managed from the network admin.', 'vigilante' ); ?></p>
2175 + <?php else : ?>
2176 + <p style="margin:0;"><?php esc_html_e( 'These tools act on other user accounts, and your role cannot edit users, so they are not available to you.', 'vigilante' ); ?></p>
2177 + <?php endif; ?>
2135 2178 </div>
2136 2179 <?php
2137 2180 }
2138 2181
@@ -3307,8 +3350,29 @@
3307 3350 </table>
3308 3351 </div>
3309 3352 <?php endif; ?>
3310 3353
3354 + <?php
3355 + // Since 2.11.8 X-Forwarded-For is read from its end, where the proxy
3356 + // writes. The administrator's own request shows whether that end is
3357 + // a CDN or a balancer for everybody here. Cross review of 2.11.8.
3358 + $xff_readings = $this->forwarded_chain_readings();
3359 + if ( $xff_readings ) :
3360 + ?>
3361 + <div id="vigilante-xff-chain-notice" class="notice notice-warning inline" style="margin:10px 0 16px;padding:8px 12px;">
3362 + <p style="margin:0;">
3363 + <?php
3364 + printf(
3365 + /* translators: 1: address Vigilant reads now, 2: address earlier versions read */
3366 + esc_html__( 'Your own request reaches the site with more than one public address in X-Forwarded-For. Vigilant reads the last one, %1$s, which is the one your proxy added; up to version 2.11.7 it read the first one, %2$s, which a visitor can write. If %1$s belongs to a CDN or a load balancer rather than to you, every visitor shares it for rate limiting, login lockouts and the IP lists: choose the header of that CDN in Visitor IP detection, such as CF-Connecting-IP for Cloudflare.', 'vigilante' ),
3367 + esc_html( $xff_readings['now'] ),
3368 + esc_html( $xff_readings['before'] )
3369 + );
3370 + ?>
3371 + </p>
3372 + </div>
3373 + <?php endif; ?>
3374 +
3311 3375 <h3><?php esc_html_e( 'IP Lists', 'vigilante' ); ?></h3>
3312 3376 <p class="description">
3313 3377 <?php
3314 3378 printf(
@@ -5062,8 +5126,9 @@
5062 5126 <span class="dashicons dashicons-yes-alt"></span>
5063 5127 <p><?php esc_html_e( 'No pending registrations.', 'vigilante' ); ?></p>
5064 5128 </div>
5065 5129 <?php else : ?>
5130 + <?php $this->render_user_actions_notice(); ?>
5066 5131 <table class="wp-list-table widefat fixed striped vigilante-pending-users-table">
5067 5132 <thead>
5068 5133 <tr>
5069 5134 <th><?php esc_html_e( 'User', 'vigilante' ); ?></th>
@@ -5092,12 +5157,12 @@
5092 5157 }
5093 5158 ?>
5094 5159 </td>
5095 5160 <td>
5096 - <button type="button" class="button button-small vigilante-approve-user" data-user-id="<?php echo esc_attr( $pending_user->ID ); ?>">
5161 + <button type="button" class="button button-small vigilante-approve-user" data-user-id="<?php echo esc_attr( $pending_user->ID ); ?>" <?php disabled( $this->user_actions_locked() ); ?>>
5097 5162 <?php esc_html_e( 'Approve', 'vigilante' ); ?>
5098 5163 </button>
5099 - <button type="button" class="button button-small vigilante-reject-user" data-user-id="<?php echo esc_attr( $pending_user->ID ); ?>" style="color: #d63638;">
5164 + <button type="button" class="button button-small vigilante-reject-user" data-user-id="<?php echo esc_attr( $pending_user->ID ); ?>" style="color: #d63638;" <?php disabled( $this->user_actions_locked() ); ?>>
5100 5165 <?php esc_html_e( 'Reject', 'vigilante' ); ?>
5101 5166 </button>
5102 5167 </td>
5103 5168 </tr>
@@ -5991,8 +6056,13 @@
5991 6056 */
5992 6057 private function render_tab_file_integrity() {
5993 6058 $is_disabled = $this->render_module_disabled_notice( 'file_integrity' );
5994 6059 $options = $this->settings->get_section( 'file_integrity' );
6060 + // On the main site of a network the critical-file scan is the network's
6061 + // canary for a change to wp-config.php or the root .htaccess, so a
6062 + // main-site admin without network rights cannot turn it off. Since
6063 + // 2.11.8; see Vigilante_Settings::get_main_site_file_settings().
6064 + $vg_main_locked = $this->main_site_files_locked();
5995 6065 $last_scan = get_option( 'vigilante_last_integrity_scan' );
5996 6066 $last_results = get_option( 'vigilante_last_integrity_results' );
5997 6067 $ignored_files = get_option( 'vigilante_ignored_files', array() );
5998 6068
@@ -6118,10 +6188,13 @@
6118 6188 <?php esc_html_e( 'Uploads directory (detect PHP files, double extensions, .htaccess)', 'vigilante' ); ?>
6119 6189 </label>
6120 6190 <br>
6121 6191 <label>
6122 - <input type="checkbox" name="file_integrity[scan_critical_config]" value="1" <?php checked( $options['scan_critical_config'] ?? true ); ?>>
6192 + <input type="checkbox" name="file_integrity[scan_critical_config]" value="1" <?php disabled( $vg_main_locked ); ?> <?php checked( $options['scan_critical_config'] ?? true ); ?>>
6123 6193 <?php esc_html_e( 'Critical config files (wp-config.php, .htaccess baseline monitoring)', 'vigilante' ); ?>
6194 + <?php if ( $vg_main_locked ) : ?>
6195 + <span class="description" style="display:block;margin-left:24px;"><?php echo esc_html( Vigilante_Settings::get_shared_files_notice() ); ?></span>
6196 + <?php endif; ?>
6124 6197 </label>
6125 6198 <br>
6126 6199 <label>
6127 6200 <input type="checkbox" name="file_integrity[check_closed_plugins]" value="1" <?php checked( $options['check_closed_plugins'] ?? true ); ?>>
@@ -6400,9 +6473,15 @@
6400 6473 $crit_diff = $crit_item['diff'] ?? array();
6401 6474 $crit_id = sanitize_html_class( $crit_file );
6402 6475 $added_count = is_array( $crit_diff ) ? count( $crit_diff['added'] ?? array() ) : 0;
6403 6476 $removed_count = is_array( $crit_diff ) ? count( $crit_diff['removed'] ?? array() ) : 0;
6404 - $diff_unavailable = is_array( $crit_diff ) && ! empty( $crit_diff['unavailable'] );
6477 + // The lines of a shared file are for whoever approves it. Results
6478 + // stored before 2.11.8 on the main site still carry them, so the
6479 + // screen asks too, not only the scan that wrote them.
6480 + $diff_network = ( is_array( $crit_diff ) && ! empty( $crit_diff['network'] ) ) || $this->critical_approval_locked();
6481 + $diff_rescan = is_array( $crit_diff ) && ! empty( $crit_diff['rescan'] );
6482 + $diff_redaction = is_array( $crit_diff ) && ! empty( $crit_diff['redaction'] );
6483 + $diff_unavailable = $diff_network || ( is_array( $crit_diff ) && ! empty( $crit_diff['unavailable'] ) );
6405 6484 ?>
6406 6485 <tr>
6407 6486 <td><code style="color: #e36210;"><?php echo esc_html( $crit_file ); ?></code></td>
6408 6487 <td>
@@ -6439,10 +6518,22 @@
6439 6518 </tr>
6440 6519 <tr id="vigilante-critical-content-<?php echo esc_attr( $crit_id ); ?>" class="vigilante-critical-content-row" style="display:none;">
6441 6520 <td colspan="3" style="padding: 0;">
6442 6521 <div class="vigilante-critical-content" style="max-height: 400px; overflow: auto; background: #fff; padding: 10px; font-size: 12px; line-height: 1.5; font-family: Consolas, Monaco, monospace; border-top: 1px solid #c3c4c7;">
6443 - <?php if ( $diff_unavailable ) : ?>
6522 + <?php if ( $diff_network ) : ?>
6444 6523 <p style="color: #50575e; font-style: italic; margin: 0;">
6524 + <?php esc_html_e( 'This file belongs to the whole network, so its line changes are only shown to network administrators, on the main site.', 'vigilante' ); ?>
6525 + </p>
6526 + <?php elseif ( $diff_rescan ) : ?>
6527 + <p style="color: #50575e; font-style: italic; margin: 0;">
6528 + <?php esc_html_e( 'Run a new scan to see the line changes of this file.', 'vigilante' ); ?>
6529 + </p>
6530 + <?php elseif ( $diff_redaction ) : ?>
6531 + <p style="color: #50575e; font-style: italic; margin: 0;">
6532 + <?php esc_html_e( 'The line changes of this file are not shown because a value in it could not be hidden safely. The change itself is still detected.', 'vigilante' ); ?>
6533 + </p>
6534 + <?php elseif ( $diff_unavailable ) : ?>
6535 + <p style="color: #50575e; font-style: italic; margin: 0;">
6445 6536 <?php esc_html_e( 'Diff not available for this file (baseline was created before diff tracking was added). Approve to enable diff on future changes.', 'vigilante' ); ?>
6446 6537 </p>
6447 6538 <?php elseif ( empty( $crit_diff['added'] ) && empty( $crit_diff['removed'] ) ) : ?>
6448 6539 <p style="color: #50575e; font-style: italic; margin: 0;">
@@ -7594,8 +7685,19 @@
7594 7685 // Save new results
7595 7686 update_option( 'vigilante_last_integrity_scan', time() );
7596 7687 update_option( 'vigilante_last_integrity_results', $results );
7597 7688
7689 + // On the main site the scan does compute the lines of wp-config.php and
7690 + // .htaccess, for the network administrator. Somebody without network
7691 + // rights gets the change and its sizes, not the lines.
7692 + if ( $this->critical_approval_locked() && ! empty( $results['modified'] ) && is_array( $results['modified'] ) ) {
7693 + foreach ( $results['modified'] as $index => $item ) {
7694 + if ( is_array( $item ) && 'critical_config' === ( $item['type'] ?? '' ) ) {
7695 + $results['modified'][ $index ]['diff'] = Vigilante_File_Integrity::network_only_diff();
7696 + }
7697 + }
7698 + }
7699 +
7598 7700 wp_send_json_success( array(
7599 7701 'message' => __( 'Scan completed.', 'vigilante' ),
7600 7702 'results' => $results,
7601 7703 'ignored_count' => count( get_option( 'vigilante_ignored_files', array() ) ),
@@ -7629,11 +7731,41 @@
7629 7731 if ( ! current_user_can( 'manage_options' ) ) {
7630 7732 wp_send_json_error( __( 'Permission denied.', 'vigilante' ) );
7631 7733 }
7632 7734
7735 + $results = get_option( 'vigilante_last_integrity_results' );
7736 + $scanned_at = get_option( 'vigilante_last_integrity_scan' );
7737 +
7633 7738 delete_option( 'vigilante_last_integrity_results' );
7634 7739 delete_option( 'vigilante_last_integrity_scan' );
7635 7740
7741 + /*
7742 + * A pending change to wp-config.php or the root .htaccess is closed by
7743 + * approving it, which takes the network. Clearing the results was one
7744 + * more way to close it without, until the next scan: the ignore list was
7745 + * shut in 2.11.8 and this button was left open, found by the cross
7746 + * review of 2.11.8. So for somebody who cannot approve, those entries
7747 + * stay and everything else goes.
7748 + */
7749 + if ( $this->critical_approval_locked() && is_array( $results ) && ! empty( $results['modified'] ) && is_array( $results['modified'] ) ) {
7750 + $critical = array_values(
7751 + array_filter(
7752 + $results['modified'],
7753 + function ( $item ) {
7754 + return is_array( $item ) && 'critical_config' === ( $item['type'] ?? '' );
7755 + }
7756 + )
7757 + );
7758 +
7759 + if ( $critical ) {
7760 + $results['modified'] = $critical;
7761 + $results['suspicious'] = array();
7762 + $results['extra'] = array();
7763 + update_option( 'vigilante_last_integrity_results', $results );
7764 + update_option( 'vigilante_last_integrity_scan', $scanned_at ? $scanned_at : time() );
7765 + }
7766 + }
7767 +
7636 7768 if ( $this->database ) {
7637 7769 $this->database->clear_file_hashes();
7638 7770 }
7639 7771
@@ -7659,8 +7791,14 @@
7659 7791 if ( empty( $file ) ) {
7660 7792 wp_send_json_error( __( 'No file specified.', 'vigilante' ) );
7661 7793 }
7662 7794
7795 + // A change to a shared file is closed by approving it, and approving it
7796 + // takes the network. Ignoring it would close the same warning without.
7797 + if ( $this->critical_approval_locked() && in_array( $file, array( 'wp-config.php', '.htaccess' ), true ) ) {
7798 + wp_send_json_error( $this->critical_approval_notice() );
7799 + }
7800 +
7663 7801 $file_integrity = new Vigilante_File_Integrity( $this->settings, $this->database );
7664 7802 $file_integrity->ignore_file( $file );
7665 7803
7666 7804 // Also remove the file from stored scan results so UI updates
@@ -7724,12 +7862,14 @@
7724 7862 if ( ! is_array( $raw_files ) ) {
7725 7863 wp_send_json_error( __( 'Invalid request.', 'vigilante' ) );
7726 7864 }
7727 7865
7728 - $files = array();
7866 + $files = array();
7867 + $shared = $this->critical_approval_locked() ? array( 'wp-config.php', '.htaccess' ) : array();
7729 7868 foreach ( $raw_files as $f ) {
7730 7869 $clean = sanitize_text_field( $f );
7731 - if ( '' !== $clean ) {
7870 + // Same rule as ajax_ignore_file() for the two shared files.
7871 + if ( '' !== $clean && ! in_array( $clean, $shared, true ) ) {
7732 7872 $files[] = $clean;
7733 7873 }
7734 7874 }
7735 7875