Headline
CVE-2023-3132: Changeset 2923512 for mainwp-child – WordPress Plugin Repository
The MainWP Child plugin for WordPress is vulnerable to Sensitive Information Exposure in versions up to, and including, 4.4.1.1 due to insufficient controls on the storage of back-up files. This makes it possible for unauthenticated attackers to extract sensitive data including the entire installations database if a backup occurs and the deletion of the back-up files fail.
mainwp-child/trunk/class/class-mainwp-backup.php
r2868300
r2923512
284
284
} else {
285
285
// When not an archive.
286
$backupFile = 'dbBackup-' . $fileNameUID . '-\*.sql';
286
$backupFile = 'dbBackup-' . $fileNameUID . '-\*.sql.php';
287
287
$dirs = MainWP\_Helper::get\_mainwp\_dir( 'backup' );
288
288
$backupdir = $dirs\[0\];
…
…
582
582
while ( ( $file = readdir( $dh ) ) !== false ) {
583
583
if ( '.' !== $file && '..' !== $file && ( preg\_match( '/dbBackup-(.\*).sql(\\.zip|\\.tar|\\.tar\\.gz|\\.tar\\.bz2|\\.tmp)?$/', $file ) ) ) {
584
unlink( $dir . $file );
585
}
586
if ( '.' !== $file && '..' !== $file && ( preg\_match( '/dbBackup-(.\*).sql.php(\\.zip|\\.tar|\\.tar\\.gz|\\.tar\\.bz2|\\.tmp)?$/', $file ) ) ) {
584
587
unlink( $dir . $file );
585
588
}
…
…
1263
1266
$table = $curr\_table\[0\];
1264
1267
1265
$currentfile = $filepath\_prefix . '-' . MainWP\_Helper::sanitize\_filename( $table ) . '.sql';
1268
$currentfile = $filepath\_prefix . '-' . MainWP\_Helper::sanitize\_filename( $table ) . '.sql.php';
1266
1269
$db\_files\[\] = $currentfile;
1267
1270
if ( file\_exists( $currentfile ) ) {
…
…
1270
1273
$fh = fopen( $currentfile . '.tmp', 'w' );
1271
1274
1275
$protect\_content\_string = '<?php exit(); ?>';
1276
1277
fwrite( $fh, $protect\_content\_string );
1272
1278
fwrite( $fh, "\\n\\n" . 'DROP TABLE IF EXISTS ' . $table . ';' );
1273
1279
$table\_create = $wpdb->get\_row( 'SHOW CREATE TABLE ' . $table, ARRAY\_N ); // phpcs:ignore -- required to achieve desired results. Pull requests appreciated.
mainwp-child/trunk/class/class-mainwp-child-actions.php
r2890510
r2923512
608
608
\*/
609
609
public function callback\_upgrader\_pre\_install() {
610
$this->current\_plugins\_info = $this->get\_plugins();
610
if ( empty( $this->current\_plugins\_info ) ) {
611
$this->current\_plugins\_info = $this->get\_plugins();
612
}
611
613
}
612
614
mainwp-child/trunk/class/class-mainwp-child-cache-purge.php
r2890510
r2923512
148
148
'flying-press/flying-press.php' => 'FlyingPress',
149
149
'wp-super-cache/wp-cache.php' => 'WP Super Cache',
150
'comet-cache/comet-cache.php' => 'Comet Cache',
150
151
'wp-optimize/wp-optimize.php' => 'WP Optimize',
151
'comet-cache/comet-cache.php' => 'Comet Cache',
152
'seraphinite-accelerator/plugin\_root.php' => 'Seraphinite Accelerator',
152
153
);
153
154
…
…
155
156
foreach ( $supported\_cache\_plugins as $plugin => $name ) {
156
157
if ( is\_plugin\_active( $plugin ) ) {
157
$cache\_plugin\_solution = $name;
158
$this->is\_plugin\_installed = true;
158
159
// Check if WP Optimize is active and page cache is enabled or disabled. If disabled, continue to next plugin as if it is not installed.
160
if ( 'wp-optimize/wp-optimize.php' == $plugin ) {
161
if ( class\_exists( '\\WP\_Optimize' ) ) {
162
$cache = WP\_Optimize()->get\_page\_cache();
163
if ( $cache->is\_enabled() === false ) {
164
continue;
165
} elseif ( $cache->is\_enabled() === true ) {
166
{
167
$cache\_plugin\_solution = 'WP Optimize';
168
}
169
}
170
}
171
} else {
172
$cache\_plugin\_solution = $name;
173
}
159
174
}
160
175
}
…
…
238
253
case 'CDN Cache Plugin':
239
254
$information = $this->cdn\_cache\_plugin\_auto\_purge\_cache();
255
break;
256
case 'Seraphinite Accelerator':
257
$information = $this->seraphinite\_auto\_purge\_cache();
240
258
break;
241
259
default:
…
…
301
319
302
320
/\*\*
321
\* Purge Seraphinite Accelerator plugin cache.
322
\*
323
\* @return array Purge results array.
324
\*/
325
public function seraphinite\_auto\_purge\_cache() {
326
327
$success\_message = 'Seraphinite Accelerator => Cache auto cleared on: (' . current\_time( 'mysql' ) . ')';
328
$error\_message = 'Seraphinite Accelerator => There was an issue purging your cache.';
329
330
if ( class\_exists( '\\seraph\_accel\\API' ) ) {
331
332
\\seraph\_accel\\API::OperateCache( \\seraph\_accel\\API::CACHE\_OP\_DEL );
333
334
// record results.
335
update\_option( 'mainwp\_cache\_control\_last\_purged', time() );
336
337
return $this->purge\_result( $success\_message, 'SUCCESS' );
338
339
} else {
340
return $this->purge\_result( $error\_message, 'ERROR' );
341
}
342
}
343
344
/\*\*
303
345
\* Purge CDN Cache Plugin cache.
304
346
\*
…
…
393
435
394
436
/\*\*
437
\* Check if WP Optimize is installed and cache is enabled.
438
\*/
439
public function wp\_optimize\_activated\_check() {
440
if ( class\_exists( '\\WP\_Optimize' ) ) {
441
$cache = WP\_Optimize()->get\_page\_cache();
442
if ( ! $cache->is\_enabled() ) {
443
return false;
444
}
445
}
446
}
447
448
/\*\*
395
449
\* Preload WP Optimize cache after purge.
396
450
\*
…
…
734
788
$cust\_domain = trim( str\_replace( array( 'http://', 'https://', 'www.' ), '', get\_option( 'siteurl' ) ), '/' );
735
789
790
// Check if we have all the required data.
736
791
if ( '' == $cust\_email || '' == $cust\_xauth || '' == $cust\_domain ) {
737
792
return;
738
793
}
794
795
// Strip subdomains. Cloudflare doesn't like them.
796
$cust\_domain = $this->strip\_subdomains( $cust\_domain );
739
797
740
798
// Get the Zone-ID from Cloudflare since they don't provide that in the Backend.
…
…
751
809
if ( 'resource' === gettype( $ch\_query ) ) {
752
810
curl\_close( $ch\_query ); // phpcs:ignore -- use core function.
753
}
754
755
// If the Zone-ID is not found, return status no-id but still return "SUCCESS" action because it did not fail.
756
// Explanation: When no Child Site is found on CF account, this will stop execution of this function and return
757
// back to auto\_purge\_cache() function for further processing.
758
if ( ! isset( $qresult\['result'\]\[0\]\['id'\] ) ) {
759
return array(
760
'status' => 'no-id',
761
'action' => 'SUCCESS',
762
);
763
811
}
764
812
…
…
784
832
curl\_close( $ch\_purge ); // phpcs:ignore -- use core function.
785
833
}
786
787
$success\_message = 'Cloudflair => Cache auto cleared on: (' . current\_time( 'mysql' ) . ')';
788
$error\_message = 'Cloudflare => There was an issue purging your cache.' . json\_encode( $result ); // phpcs:ignore -- ok.
834
$success\_message = 'Cloudflare => Cache auto cleared on: (' . current\_time( 'mysql' ) . ')';
835
$error\_message = 'Cloudflare => There was an issue purging the cache. ' . json\_encode( $qresult\['errors'\]\[0\], JSON\_UNESCAPED\_SLASHES ) . "-" . json\_encode( $result\['errors'\]\[0\], JSON\_UNESCAPED\_SLASHES ); // phpcs:ignore -- ok.
789
836
790
837
// Save last purge time to database on success.
791
838
if ( 1 == $result\['success'\] ) {
839
792
840
// record results.
793
841
update\_option( 'mainwp\_cache\_control\_last\_purged', time() );
…
…
795
843
// Return success message.
796
844
return $this->purge\_result( $success\_message, 'SUCCESS' );
797
} else {
845
} elseif ( ( 1 != $qresult\['success'\] ) || ( 1 != $result\['success'\] ) ) {
798
846
// Return error message.
799
847
return $this->purge\_result( $error\_message, 'ERROR' );
…
…
918
966
update\_option( 'mainwp\_cache\_control\_log', wp\_json\_encode( $information ) );
919
967
}
968
969
/\*\*
970
\* Strip subdomains from a url.
971
\*
972
\* @param string $url string The url to strip subdomains from.
973
\*
974
\* @return string The url without subdomains (if any).
975
\*/
976
public function strip\_subdomains( $url ) {
977
978
// credits to gavingmiller for maintaining this list.
979
$second\_level\_domains = file\_get\_contents( 'https://raw.githubusercontent.com/gavingmiller/second-level-domains/master/SLDs.csv' ); //phpcs:ignore -- to do.
980
981
// presume sld first ...
982
$possible\_sld = implode( '.', array\_slice( explode( '.', $url ), -2 ) );
983
984
// and then verify it.
985
if ( strpos( $second\_level\_domains, $possible\_sld ) ) {
986
return implode( '.', array\_slice( explode( '.', $url ), -3 ) );
987
} else {
988
return implode( '.', array\_slice( explode( '.', $url ), -2 ) );
989
}
990
}
920
991
}
mainwp-child/trunk/class/class-mainwp-child-callable.php
r2881395
r2923512
1061
1061
MainWP\_Helper::write( $information );
1062
1062
}
1063
1064
1063
}
mainwp-child/trunk/class/class-mainwp-child.php
r2910248
r2923512
34
34
\* @var string MainWP Child plugin version.
35
35
\*/
36
public static $version = '4.4.1.1';
36
public static $version = '4.4.1.2';
37
37
38
38
/\*\*
mainwp-child/trunk/class/class-mainwp-client-report-base.php
r2877395
r2923512
763
763
$tok\_value = MainWP\_Helper::format\_time( MainWP\_Helper::get\_timestamp( strtotime( $record->created ) ) );
764
764
break;
765
case 'utime':
766
$tok\_value = $record->created;
767
break;
768
case 'slug':
769
$tok\_value = $this->get\_stream\_meta\_data( $record, $data );
770
break;
765
771
case 'area':
766
772
$data = 'sidebar\_name';
mainwp-child/trunk/class/class-mainwp-clone-install.php
r2900843
r2923512
9
9
10
10
namespace MainWP\Child;
11
12
use Stringable;
11
13
12
14
// phpcs:disable WordPress.WP.AlternativeFunctions, Generic.Metrics.CyclomaticComplexity – Required to achieve desired results, pull request solutions appreciated.
…
…
238
240
\*/
239
241
public function clean() {
240
$files = glob( WP\_CONTENT\_DIR . '/dbBackup\*.sql' );
242
$files = glob( WP\_CONTENT\_DIR . '/dbBackup\*.sql.php' );
241
243
foreach ( $files as $file ) {
242
244
unlink( $file );
245
}
246
if ( file\_exists( WP\_CONTENT\_DIR . '/dbBackup.sql' ) ) {
247
unlink( WP\_CONTENT\_DIR . '/dbBackup.sql' );
243
248
}
244
249
if ( file\_exists( ABSPATH . 'clone/config.txt' ) ) {
…
…
341
346
$wpdb->query( 'SET foreign\_key\_checks = 0' );
342
347
343
$files = glob( WP\_CONTENT\_DIR . '/dbBackup\*.sql' );
348
$protect\_content\_string = '<?php exit(); ?>';
349
350
$files = glob( WP\_CONTENT\_DIR . '/dbBackup\*.sql.php' );
344
351
foreach ( $files as $file ) {
345
352
$handle = fopen( $file, 'r' );
…
…
347
354
$lastRun = 0;
348
355
if ( $handle ) {
349
$readline = '';
356
$readline = '';
357
$remove\_protect = true;
350
358
while ( ( $line = fgets( $handle, 81920 ) ) !== false ) {
351
359
if ( time() - $lastRun > 20 ) {
352
360
set\_time\_limit( 0 ); // reset timer..
353
361
$lastRun = time();
362
}
363
364
if ( $remove\_protect ) {
365
if ( false !== stristr( $line, $protect\_content\_string ) ) {
366
$line = str\_replace( $protect\_content\_string, '', $line );
367
$remove\_protect = false;
368
}
354
369
}
355
370
mainwp-child/trunk/class/class-mainwp-clone.php
r2868300
r2923512
290
290
private function create\_clone\_backup() { // phpcs:ignore -- Current complexity is the only way to achieve desired results, pull request solutions appreciated.
291
291
MainWP\_Helper::end\_session();
292
$files = glob( WP\_CONTENT\_DIR . '/dbBackup\*.sql' );
292
$files = glob( WP\_CONTENT\_DIR . '/dbBackup\*.sql.php' );
293
293
foreach ( $files as $file ) {
294
294
unlink( $file );
295
}
296
if ( file\_exists( WP\_CONTENT\_DIR . '/dbBackup.sql' ) ) {
297
unlink( WP\_CONTENT\_DIR . '/dbBackup.sql' );
295
298
}
296
299
if ( file\_exists( ABSPATH . 'clone/config.txt' ) ) {
mainwp-child/trunk/class/class-mainwp-helper.php
r2900843
r2923512
106
106
\*
107
107
\* @return array Return directory and directory URL.
108
\* @throws Exception Error Message.
108
109
\*/
109
110
public static function get\_mainwp\_dir( $what = null, $die\_on\_error = true ) {
111
112
if ( ! self::fs\_is\_connected() ) {
113
throw new \\Exception( esc\_html\_\_( 'Unable to connect to the filesystem.', 'mainwp-child' ) );
114
}
110
115
111
116
/\*\*
…
…
116
121
global $wp\_filesystem;
117
122
118
self::get\_wp\_filesystem();
119
120
123
$upload\_dir = wp\_upload\_dir();
121
124
$dir = $upload\_dir\['basedir'\] . DIRECTORY\_SEPARATOR . 'mainwp' . DIRECTORY\_SEPARATOR;
…
…
144
147
return array( $dir, $url );
145
148
}
149
150
151
/\*\*
152
\* Method fs\_is\_connected()
153
\*
154
\* Check if WP FileSystem is connected.
155
\*/
156
public static function fs\_is\_connected() {
157
self::get\_wp\_filesystem();
158
global $wp\_filesystem;
159
if ( ! empty( $wp\_filesystem ) && $wp\_filesystem->connect() ) {
160
return true;
161
}
162
return false;
163
}
164
146
165
147
166
/\*\*
mainwp-child/trunk/mainwp-child.php
r2910248
r2923512
13
13
* Author URI: https://mainwp.com
14
14
* Text Domain: mainwp-child
15
* Version: 4.4.1.1
15
* Version: 4.4.1.2
16
16
* Requires at least: 5.4
17
17
* Requires PHP: 7.4
mainwp-child/trunk/readme.txt
r2910248
r2923512
8
8
Tested up to: 6.2
9
9
Requires PHP: 7.0
10
Stable tag: 4.4.1.1
10
Stable tag: 4.4.1.2
11
11
License: GPLv3 or later
12
12
License URI: https://www.gnu.org/licenses/gpl-3.0.html
…
…
148
148
== Changelog ==
149
149
150
= 4.4.1.2 - 6-8-2023 =
151
* Fixed: Potential issues caused by incorrect FileSystem settings
152
* Updated: Logging system related to the Cache Control extension
153
* Updated: Database export process related to the Cloning feature
154
150
155
= 4.4.1.1 - 5-9-2023 =
151
156
* Fixed: Potential conflict with the Oxygen Builder 4.6