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 | includes/class-wpconfig-security.php +52 -28 2.11.53.0.0 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();
@@ -149,13 +149,18 @@
149 149 return $result;
150 150 }
151 151
152 152 /**
153 - * Create a backup of wp-config.php before modification
153 + * Check wp-config.php before modifying it
154 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 + *
155 160 * @return bool|WP_Error
156 161 */
157 - private function create_backup() {
162 + private function check_before_write() {
158 163 if ( ! file_exists( $this->wpconfig_path ) ) {
159 164 return new WP_Error( 'no_config', __( 'wp-config.php does not exist', 'vigilante' ) );
160 165 }
161 166
@@ -169,25 +174,10 @@
169 174 if ( ! $this->validate_wpconfig_content( $content ) ) {
170 175 return new WP_Error( 'invalid_config', __( 'wp-config.php does not appear to be a valid WordPress configuration file', 'vigilante' ) );
171 176 }
172 177
173 - // Store the backup in a private database option, never as a file under
174 - // the web root. wp-config.php holds DB credentials and salts; a file in
175 - // wp-content could be served by a misconfigured server. The option is
176 - // not reachable over HTTP and is not autoloaded.
177 - $stored = update_option(
178 - 'vigilante_wpconfig_backup',
179 - array(
180 - 'content' => $content,
181 - 'time' => time(),
182 - ),
183 - false
184 - );
185 -
186 - // update_option() returns false both on failure and when the value is
187 - // unchanged; only treat it as an error if the content was not stored.
188 - if ( false === $stored && $content !== $this->get_wpconfig_backup_content() ) {
189 - 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' ) );
190 180 }
191 181
192 182 return true;
193 183 }
@@ -192,15 +182,43 @@
192 182 return true;
193 183 }
194 184
195 185 /**
196 - * 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
197 187 *
198 - * @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
199 204 */
200 - private function get_wpconfig_backup_content() {
201 - $backup = get_option( 'vigilante_wpconfig_backup' );
202 - 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;
203 221 }
204 222
205 223 /**
206 224 * Read file directly without WP_Filesystem (more reliable)
@@ -402,8 +420,14 @@
402 420 $content = $this->read_file_directly( $this->wpconfig_path );
403 421
404 422 if ( false === $content ) {
405 423 return false;
424 + }
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' ) );
406 430 }
407 431
408 432 // If our markers don't exist, just try to uncomment originals
409 433 if ( strpos( $content, self::MARKER_START ) === false ) {