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 +72 -28 2.11.22.11.8 View file →
@@ -106,12 +106,12 @@
106 106 if ( ! $this->is_wpconfig_writable() ) {
107 107 return new WP_Error( 'not_writable', __( 'wp-config.php is not writable', 'vigilante' ) );
108 108 }
109 109
110 - // Safety check 2: Create backup BEFORE any modification
111 - $backup_result = $this->create_backup();
112 - if ( is_wp_error( $backup_result ) ) {
113 - 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;
114 114 }
115 115
116 116 // First clean up old plugin constants
117 117 $this->remove_old_constants();
@@ -132,8 +132,14 @@
132 132
133 133 // Regenerate critical file baseline so the integrity scan does not
134 134 // flag our own modifications as unauthorized changes.
135 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 +
136 142 /**
137 143 * Fires after Vigilante successfully writes to wp-config.php.
138 144 * Used by the file integrity module to update the baseline hash.
139 145 */
@@ -143,13 +149,18 @@
143 149 return $result;
144 150 }
145 151
146 152 /**
147 - * Create a backup of wp-config.php before modification
153 + * Check wp-config.php before modifying it
148 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 + *
149 160 * @return bool|WP_Error
150 161 */
151 - private function create_backup() {
162 + private function check_before_write() {
152 163 if ( ! file_exists( $this->wpconfig_path ) ) {
153 164 return new WP_Error( 'no_config', __( 'wp-config.php does not exist', 'vigilante' ) );
154 165 }
155 166
@@ -163,25 +174,10 @@
163 174 if ( ! $this->validate_wpconfig_content( $content ) ) {
164 175 return new WP_Error( 'invalid_config', __( 'wp-config.php does not appear to be a valid WordPress configuration file', 'vigilante' ) );
165 176 }
166 177
167 - // Store the backup in a private database option, never as a file under
168 - // the web root. wp-config.php holds DB credentials and salts; a file in
169 - // wp-content could be served by a misconfigured server. The option is
170 - // not reachable over HTTP and is not autoloaded.
171 - $stored = update_option(
172 - 'vigilante_wpconfig_backup',
173 - array(
174 - 'content' => $content,
175 - 'time' => time(),
176 - ),
177 - false
178 - );
179 -
180 - // update_option() returns false both on failure and when the value is
181 - // unchanged; only treat it as an error if the content was not stored.
182 - if ( false === $stored && $content !== $this->get_wpconfig_backup_content() ) {
183 - 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' ) );
184 180 }
185 181
186 182 return true;
187 183 }
@@ -186,15 +182,43 @@
186 182 return true;
187 183 }
188 184
189 185 /**
190 - * 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
191 187 *
192 - * @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
193 204 */
194 - private function get_wpconfig_backup_content() {
195 - $backup = get_option( 'vigilante_wpconfig_backup' );
196 - 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;
197 221 }
198 222
199 223 /**
200 224 * Read file directly without WP_Filesystem (more reliable)
@@ -366,8 +390,19 @@
366 390 return true;
367 391 }
368 392
369 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 + /**
370 405 * Remove our security constants from wp-config.php and restore originals
371 406 *
372 407 * @return bool|WP_Error
373 408 */
@@ -387,10 +422,17 @@
387 422 if ( false === $content ) {
388 423 return false;
389 424 }
390 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 +
391 432 // If our markers don't exist, just try to uncomment originals
392 433 if ( strpos( $content, self::MARKER_START ) === false ) {
434 + $this->forget_owned_block();
393 435 return $this->uncomment_original_constants();
394 436 }
395 437
396 438 // Validate before modification
@@ -414,8 +456,10 @@
414 456 // Write the file without our block
415 457 if ( ! $this->write_file_directly( $this->wpconfig_path, $new_content ) ) {
416 458 return false;
417 459 }
460 +
461 + $this->forget_owned_block();
418 462
419 463 // Now uncomment the original constants
420 464 return $this->uncomment_original_constants();
421 465 }