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 | includes/class-wpconfig-security.php +84 -29 2.9.72.11.8 View file →
@@ -96,17 +96,22 @@
96 96 *
97 97 * @return bool|WP_Error
98 98 */
99 99 public function apply_security_constants() {
100 + // Safety check 0: on a network this file belongs to the main site
101 + if ( ! Vigilante_Settings::can_write_shared_files() ) {
102 + return new WP_Error( 'network_not_owner', Vigilante_Settings::get_shared_files_notice() );
103 + }
104 +
100 105 // Safety check 1: File must exist and be writable
101 106 if ( ! $this->is_wpconfig_writable() ) {
102 107 return new WP_Error( 'not_writable', __( 'wp-config.php is not writable', 'vigilante' ) );
103 108 }
104 109
105 - // Safety check 2: Create backup BEFORE any modification
106 - $backup_result = $this->create_backup();
107 - if ( is_wp_error( $backup_result ) ) {
108 - return $backup_result;
110 + // Safety check 2: the file must look whole BEFORE any modification
111 + $check_result = $this->check_before_write();
112 + if ( is_wp_error( $check_result ) ) {
113 + return $check_result;
109 114 }
110 115
111 116 // First clean up old plugin constants
112 117 $this->remove_old_constants();
@@ -127,8 +132,14 @@
127 132
128 133 // Regenerate critical file baseline so the integrity scan does not
129 134 // flag our own modifications as unauthorized changes.
130 135 if ( true === $result ) {
136 + // Record the block as Vigilant's own first: the baseline refreshed
137 + // below leaves out of its hash only the blocks recorded this way.
138 + if ( class_exists( 'Vigilante_File_Integrity' ) ) {
139 + Vigilante_File_Integrity::remember_owned_block( 'wp-config.php', self::MARKER_START, rtrim( $constants, "\n" ) );
140 + }
141 +
131 142 /**
132 143 * Fires after Vigilante successfully writes to wp-config.php.
133 144 * Used by the file integrity module to update the baseline hash.
134 145 */
@@ -138,13 +149,18 @@
138 149 return $result;
139 150 }
140 151
141 152 /**
142 - * Create a backup of wp-config.php before modification
153 + * Check wp-config.php before modifying it
143 154 *
155 + * Until 2.11.6 this also stored the whole file in vigilante_wpconfig_backup,
156 + * and with it the database password and the authentication keys and salts.
157 + * Nothing ever read that copy back to restore anything: the checks are what
158 + * protected the file, and the copy only put its secrets in the options table.
159 + *
144 160 * @return bool|WP_Error
145 161 */
146 - private function create_backup() {
162 + private function check_before_write() {
147 163 if ( ! file_exists( $this->wpconfig_path ) ) {
148 164 return new WP_Error( 'no_config', __( 'wp-config.php does not exist', 'vigilante' ) );
149 165 }
150 166
@@ -158,25 +174,10 @@
158 174 if ( ! $this->validate_wpconfig_content( $content ) ) {
159 175 return new WP_Error( 'invalid_config', __( 'wp-config.php does not appear to be a valid WordPress configuration file', 'vigilante' ) );
160 176 }
161 177
162 - // Store the backup in a private database option, never as a file under
163 - // the web root. wp-config.php holds DB credentials and salts; a file in
164 - // wp-content could be served by a misconfigured server. The option is
165 - // not reachable over HTTP and is not autoloaded.
166 - $stored = update_option(
167 - 'vigilante_wpconfig_backup',
168 - array(
169 - 'content' => $content,
170 - 'time' => time(),
171 - ),
172 - false
173 - );
174 -
175 - // update_option() returns false both on failure and when the value is
176 - // unchanged; only treat it as an error if the content was not stored.
177 - if ( false === $stored && $content !== $this->get_wpconfig_backup_content() ) {
178 - return new WP_Error( 'backup_failed', __( 'Could not create wp-config.php backup', 'vigilante' ) );
178 + if ( ! self::constants_blocks_are_whole( $content ) ) {
179 + return new WP_Error( 'block_incomplete', __( 'A Vigilant block in wp-config.php is missing one of its markers, so the file was left as it is.', 'vigilante' ) );
179 180 }
180 181
181 182 return true;
182 183 }
@@ -181,15 +182,43 @@
181 182 return true;
182 183 }
183 184
184 185 /**
185 - * Get the stored wp-config.php backup content, or '' if none.
186 + * Whether no constants block in wp-config.php is left without its END
186 187 *
187 - * @return string
188 + * A BEGIN with no END after it, or a second BEGIN before the END, means the
189 + * block cannot be found whole. Removing it would still rewrite the file and
190 + * uncomment the original constants around it while the block keeps defining
191 + * them, and writing a new one would leave the broken block in place for the
192 + * next removal to pair with the new END, taking everything in between. So
193 + * the file is left as it is, and the block keeps working until someone
194 + * removes it by hand. An END with no BEGIN before it is left out of the
195 + * question: each BEGIN is paired with the next END and a stray END is left
196 + * alone.
197 + *
198 + * Until 2.11.6 only deactivation asked this, from its own copy of the check.
199 + *
200 + * @since 2.11.6
201 + *
202 + * @param string $content wp-config.php content.
203 + * @return bool
188 204 */
189 - private function get_wpconfig_backup_content() {
190 - $backup = get_option( 'vigilante_wpconfig_backup' );
191 - return ( is_array( $backup ) && isset( $backup['content'] ) ) ? (string) $backup['content'] : '';
205 + private static function constants_blocks_are_whole( $content ) {
206 + preg_match_all( '/' . preg_quote( self::MARKER_START, '/' ) . '|' . preg_quote( self::MARKER_END, '/' ) . '/', $content, $markers );
207 +
208 + $inside = false;
209 + foreach ( $markers[0] as $marker ) {
210 + if ( self::MARKER_START === $marker ) {
211 + if ( $inside ) {
212 + return false;
213 + }
214 + $inside = true;
215 + } else {
216 + $inside = false;
217 + }
218 + }
219 +
220 + return ! $inside;
192 221 }
193 222
194 223 /**
195 224 * Read file directly without WP_Filesystem (more reliable)
@@ -361,13 +390,30 @@
361 390 return true;
362 391 }
363 392
364 393 /**
394 + * Drop the integrity scan's record of the constants block once it is gone
395 + *
396 + * @since 2.11.5
397 + */
398 + private function forget_owned_block() {
399 + if ( class_exists( 'Vigilante_File_Integrity' ) ) {
400 + Vigilante_File_Integrity::forget_owned_blocks( 'wp-config.php', self::MARKER_START );
401 + }
402 + }
403 +
404 + /**
365 405 * Remove our security constants from wp-config.php and restore originals
366 406 *
367 - * @return bool
407 + * @return bool|WP_Error
368 408 */
369 409 public function remove_constants() {
410 + // On a network, a subsite deactivating the plugin must not strip the
411 + // constants the main site put there for everyone.
412 + if ( ! Vigilante_Settings::can_write_shared_files() ) {
413 + return new WP_Error( 'network_not_owner', Vigilante_Settings::get_shared_files_notice() );
414 + }
415 +
370 416 if ( ! file_exists( $this->wpconfig_path ) ) {
371 417 return true;
372 418 }
373 419
@@ -376,10 +422,17 @@
376 422 if ( false === $content ) {
377 423 return false;
378 424 }
379 425
426 + // A block that lost a marker cannot come out without cutting or
427 + // duplicating what surrounds it, so the file is left as it is.
428 + if ( ! self::constants_blocks_are_whole( $content ) ) {
429 + return new WP_Error( 'block_incomplete', __( 'A Vigilant block in wp-config.php is missing one of its markers, so the file was left as it is.', 'vigilante' ) );
430 + }
431 +
380 432 // If our markers don't exist, just try to uncomment originals
381 433 if ( strpos( $content, self::MARKER_START ) === false ) {
434 + $this->forget_owned_block();
382 435 return $this->uncomment_original_constants();
383 436 }
384 437
385 438 // Validate before modification
@@ -403,8 +456,10 @@
403 456 // Write the file without our block
404 457 if ( ! $this->write_file_directly( $this->wpconfig_path, $new_content ) ) {
405 458 return false;
406 459 }
460 +
461 + $this->forget_owned_block();
407 462
408 463 // Now uncomment the original constants
409 464 return $this->uncomment_original_constants();
410 465 }