PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 3.0.0
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v3.0.0
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-ajax.php +67 -101 2.11.23.0.0 View file →
@@ -22,95 +22,18 @@
22 22 * AJAX: Apply preset
23 23 */
24 24 // ajax_apply_preset() is defined in class-admin.php directly (not in this trait)
25 25
26 - /**
27 - * AJAX: Clear lockouts
26 + /*
27 + * ajax_clear_lockouts(), ajax_clear_logs(), ajax_run_scan() and
28 + * ajax_test_headers() live in class-admin.php. Until 2.11.8 this trait
29 + * carried older copies of the four, and PHP runs the method of the class,
30 + * so the copies never ran: a fix written into one of them would have looked
31 + * applied and changed nothing. Removed after the audit of the admin surface
32 + * for 2.11.8 found them.
28 33 */
29 - public function ajax_clear_lockouts() {
30 - check_ajax_referer( 'vigilante_admin_nonce', 'nonce' );
31 34
32 - if ( ! current_user_can( 'manage_options' ) ) {
33 - wp_send_json_error( __( 'Permission denied.', 'vigilante' ) );
34 - }
35 -
36 - $ip = isset( $_POST['ip'] ) ? sanitize_text_field( wp_unslash( $_POST['ip'] ) ) : '';
37 -
38 - if ( ! empty( $ip ) ) {
39 - // Clear specific IP
40 - $result = $this->database->clear_lockout( $ip );
41 - } else {
42 - // Clear all
43 - $result = $this->database->clear_all_lockouts();
44 - }
45 -
46 - if ( $result ) {
47 - wp_send_json_success( __( 'Lockouts cleared.', 'vigilante' ) );
48 - } else {
49 - wp_send_json_error( __( 'Failed to clear lockouts.', 'vigilante' ) );
50 - }
51 - }
52 -
53 35 /**
54 - * AJAX: Clear logs
55 - */
56 - public function ajax_clear_logs() {
57 - check_ajax_referer( 'vigilante_admin_nonce', 'nonce' );
58 -
59 - if ( ! current_user_can( 'manage_options' ) ) {
60 - wp_send_json_error( __( 'Permission denied.', 'vigilante' ) );
61 - }
62 -
63 - $result = $this->activity_log->clear_all_logs();
64 -
65 - if ( $result ) {
66 - wp_send_json_success( __( 'Logs cleared.', 'vigilante' ) );
67 - } else {
68 - wp_send_json_error( __( 'Failed to clear logs.', 'vigilante' ) );
69 - }
70 - }
71 -
72 - /**
73 - * AJAX: Run file integrity scan
74 - */
75 - public function ajax_run_scan() {
76 - check_ajax_referer( 'vigilante_admin_nonce', 'nonce' );
77 -
78 - if ( ! current_user_can( 'manage_options' ) ) {
79 - wp_send_json_error( __( 'Permission denied.', 'vigilante' ) );
80 - }
81 -
82 - try {
83 - if ( ! class_exists( 'Vigilante_File_Integrity' ) ) {
84 - require_once VIGILANTE_PLUGIN_DIR . 'includes/class-file-integrity.php';
85 - }
86 -
87 - if ( ! $this->settings ) {
88 - wp_send_json_error( 'Settings not initialized' );
89 - }
90 -
91 - $activity_log = isset( $this->activity_log ) ? $this->activity_log : null;
92 - $database = isset( $this->database ) ? $this->database : null;
93 -
94 - $file_integrity = new Vigilante_File_Integrity( $this->settings, $database, $activity_log );
95 - $results = $file_integrity->run_scan();
96 -
97 - // Save results for display
98 - update_option( 'vigilante_last_integrity_scan', time() );
99 - update_option( 'vigilante_last_integrity_results', $results );
100 -
101 - wp_send_json_success( array(
102 - 'message' => __( 'Scan completed.', 'vigilante' ),
103 - 'results' => $results,
104 - ) );
105 - } catch ( Exception $e ) {
106 - wp_send_json_error( 'Exception: ' . $e->getMessage() );
107 - } catch ( Error $e ) {
108 - wp_send_json_error( 'PHP Error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine() );
109 - }
110 - }
111 -
112 - /**
113 36 * AJAX: Approve a critical config file modification
114 37 *
115 38 * Updates the baseline hash for a single critical file (wp-config.php
116 39 * or .htaccess), accepting the current content as legitimate.
@@ -117,9 +40,23 @@
117 40 */
118 41 public function ajax_approve_critical_file() {
119 42 check_ajax_referer( 'vigilante_admin_nonce', 'nonce' );
120 43
121 - if ( ! current_user_can( 'manage_options' ) ) {
44 + // Both approvable files, wp-config.php and the root .htaccess, belong
45 + // to the whole network, and since 2.11.3 so does the baseline that
46 + // records them. Approving a change to them is a network action, so on
47 + // a network it takes a network administrator: manage_options is held
48 + // by the administrator of every subsite.
49 + // Written with both calls in plain sight, following the recipe in
50 + // native-aeo-pack/trunk/includes/class-robots-txt.php:650, so the
51 + // surface inventory can read the capability. With the name in a
52 + // variable it can only say "check by hand", and an alert that says
53 + // that forever is an alert nobody reads.
54 + $allowed = is_multisite()
55 + ? current_user_can( 'manage_network_options' )
56 + : current_user_can( 'manage_options' );
57 +
58 + if ( ! $allowed ) {
122 59 wp_send_json_error( __( 'Permission denied.', 'vigilante' ) );
123 60 }
124 61
125 62 // The request carries an opaque key instead of the file name: hosting
@@ -244,8 +181,16 @@
244 181 $log->is_ip_blacklisted = ( '' !== $ip_val && in_array( $ip_val, $ip_blacklist, true ) );
245 182 $log->is_ua_whitelisted = ( '' !== $ua_val && in_array( $ua_val, $ua_whitelist, true ) );
246 183 $log->is_ua_blacklisted = ( '' !== $ua_val && in_array( $ua_val, $ua_blacklist, true ) );
247 184 $log->request_uri = Vigilante_Activity_Log::extract_request_uri( $log->extra_data ?? '' );
185 + // Same explanation as the first page load: without this, an entry
186 + // reached by filtering or paginating would open a popup with less
187 + // in it than the same entry opened from the first page.
188 + $log->self_guidance = Vigilante_Self_Integrity_Guidance::for_log_event(
189 + (string) ( $log->event_action ?? '' ),
190 + $log->extra_data ?? '',
191 + (string) ( $log->severity ?? 'info' )
192 + );
248 193 }
249 194
250 195 wp_send_json_success( array(
251 196 'logs' => $logs,
@@ -326,8 +271,18 @@
326 271 $removed_from_opposite = true;
327 272 }
328 273 }
329 274
275 + // On the main site of a network the whitelists also build the .htaccess
276 + // rules every site shares, so a user without network rights cannot put
277 + // an entry in them or take one out (2.11.6).
278 + $locked = Vigilante_Settings::get_locked_file_settings();
279 + $locked_firewall = ( isset( $locked['firewall'] ) && is_array( $locked['firewall'] ) ) ? $locked['firewall'] : array();
280 +
281 + if ( in_array( $option_key, $locked_firewall, true ) || ( $removed_from_opposite && in_array( $opposite_key, $locked_firewall, true ) ) ) {
282 + wp_send_json_error( Vigilante_Settings::get_shared_files_notice() );
283 + }
284 +
330 285 wp_cache_delete( Vigilante_Settings::OPTION_NAME, 'options' );
331 286 update_option( Vigilante_Settings::OPTION_NAME, $all_options );
332 287 $this->settings->clear_cache();
333 288
@@ -376,24 +331,8 @@
376 331 wp_send_json_success( $message );
377 332 }
378 333
379 334 /**
380 - * AJAX: Test security headers
381 - */
382 - public function ajax_test_headers() {
383 - check_ajax_referer( 'vigilante_admin_nonce', 'nonce' );
384 -
385 - if ( ! current_user_can( 'manage_options' ) ) {
386 - wp_send_json_error( __( 'Permission denied.', 'vigilante' ) );
387 - }
388 -
389 - $security_headers = new Vigilante_Security_Headers( $this->settings );
390 - $results = $security_headers->test_headers();
391 -
392 - wp_send_json_success( $results );
393 - }
394 -
395 - /**
396 335 * Sanitize activity log data
397 336 *
398 337 * @param array $data Data to sanitize.
399 338 * @return array
@@ -1115,8 +1054,29 @@
1115 1054 if ( ! $user_id ) {
1116 1055 wp_send_json_error( __( 'Invalid user ID.', 'vigilante' ) );
1117 1056 }
1118 1057
1058 + /*
1059 + * Permission over that account, which on a network only a network
1060 + * administrator has (wp-includes/capabilities.php:75). Same rule the other
1061 + * account tools got in 2.10.3, kept here in 2.11.8.
1062 + *
1063 + * The reason written here until 2.11.10 was that the pending flag is one
1064 + * user meta shared by the whole network, and that stopped being true in
1065 + * this very release: the flag is per site now and approving clears only
1066 + * this site's. The check stays all the same, and deliberately. Approving
1067 + * is what lets somebody into a network whose session cookie is valid on
1068 + * every site of it, and the queue is shown to a site administrator so they
1069 + * can see who is waiting, with the button locked and explained, which is
1070 + * how it has behaved since 2.11.8 and what matriz-red-limpieza-2114.sh
1071 + * checks. Loosening it is a decision about who may let people into a
1072 + * network, not a tidy-up, so it belongs with the rest of the network
1073 + * permissions work in 3.1.0 and not in a security release.
1074 + */
1075 + if ( ! current_user_can( 'edit_user', $user_id ) ) {
1076 + wp_send_json_error( __( 'Permission denied.', 'vigilante' ) );
1077 + }
1078 +
1119 1079 $user_security = new Vigilante_User_Security( $this->settings, $this->activity_log );
1120 1080 $result = $user_security->approve_user( $user_id, get_current_user_id() );
1121 1081
1122 1082 if ( $result ) {
@@ -1147,8 +1107,14 @@
1147 1107 $reason = isset( $_POST['reason'] ) ? sanitize_text_field( wp_unslash( $_POST['reason'] ) ) : '';
1148 1108
1149 1109 if ( ! $user_id ) {
1150 1110 wp_send_json_error( __( 'Invalid user ID.', 'vigilante' ) );
1111 + }
1112 +
1113 + // See ajax_approve_user(): the account and its pending flag belong to the
1114 + // whole network (2.11.8).
1115 + if ( ! current_user_can( 'edit_user', $user_id ) ) {
1116 + wp_send_json_error( __( 'Permission denied.', 'vigilante' ) );
1151 1117 }
1152 1118
1153 1119 $user = get_userdata( $user_id );
1154 1120 $username = $user ? $user->user_login : $user_id;