WordPress Frontend File Manager plugin fixed multiple critical vulnerabilities.

The WordPress Frontend File Manager plugin (2,000+ active installations) fixed multiple critical vulnerabilities affecting version 17.1 and, to some extend, version 18.2 (see Timeline below for more details).

Privilege Escalation

Throughout its code, the plugin relies on the wpfm_get_current_user function from the “nmedia-user-file-uploader/inc/helpers.php” script to get the user ID:

// This function will return a current user object
// if logged in then current otherwise will see public user settings
function wpfm_get_current_user() {

    $current_user_id = null;
    if( is_user_logged_in() ) {
        $current_user_id = get_current_user_id();
    } elseif ( wpfm_is_guest_upload_allow() ) {

        $current_user_id = get_option('wpfm_guest_user_id');
    if( isset($_GET['file_owner']) && $_GET['file_owner'] != '' ) {
        $current_user_id = $_GET['file_owner'];
    $current_user   = get_userdata( $current_user_id );
    return apply_filters('wpfm_get_current_user', $current_user);

It retrieves the user ID from the WordPress get_current_user_id function if the user is authenticated, or from the plugin’s wpfm_guest_user_id option if the user is not logged-in. However, the user, authenticated or not, can assign any ID to the $_GET['file_owner'] variable in order to override $current_user_id L318, which could lead to privilege escalation.

Unauthenticated Content Injection and Stored XSS

The wpfm_edit_file_title_desc AJAX action (unauthenticated) loads the wpfm_edit_file_title_desc function from the “nmedia-user-file-uploader/inc/callback-functions.php” script. It is used to edit a post:

 * Edit file title and description
function wpfm_edit_file_title_desc(){
   if (isset($_REQUEST)) {
      $file = array(
         'ID'           => esc_attr($_REQUEST['file_id']),
         'post_title'   => esc_attr($_REQUEST['file_title']),
         'post_content' => esc_attr($_REQUEST['file_content']),
      $post_id = wp_update_post( $file, true );
      // var_dump($post_id);
      // Update the post into the database
      if( $post_id != 0 ) {

         update_post_meta( $post_id,'wpfm_title', $_REQUEST['file_title']);
         _e('File updated', 'wpfm');
         _e('Error while updating file, try again', 'wpfm');

It doesn’t check if users are editing their own post and lacks a security nonce.
An unauthenticated user can change the content and title of every page and post on the blog:

In addition, if the post type is wpfm-files, it is possible to inject JavaScript code in the post title because the plugin relies only on the WordPress esc_attr function to sanitise the $_REQUEST['file_title'] variable, which will be echoed outside HTML attributes in the backend section. The JS code will be executed when an admin user visits the plugin’s settings pages:

An unauthenticated user could inject JavaScript code in order to create an admin user account.

Authenticated Settings Change and Arbitrary File Upload

The wpfm_save_settings function from the “nmedia-user-file-uploader/inc/admin.php” script is loaded by the wpfm_save_settings AJAX action (authenticated). It is used to save the plugin’s settings:

  9 function wpfm_save_settings() {
 11    $wpfm_settigns = array( 
 12       "wpfm_files_view"         => isset($_REQUEST['wpfm_files_view'] ) && $_REQUEST['wpfm_files_view'] != '' ? $_REQUEST['wpfm_files_view']  : 'grid',
 13       "wpfm_thumb_size"         => isset($_REQUEST['wpfm_thumb_size']) ? sanitize_text_field($_REQUEST['wpfm_thumb_size']) : '',
 14       "wpfm_button_title"       => isset($_REQUEST['wpfm_button_title']) ? sanitize_text_field($_REQUEST['wpfm_button_title']) : '',
 15       "wpfm_upload_title"       => isset($_REQUEST['wpfm_upload_title']) ? sanitize_text_field($_REQUEST['wpfm_upload_title']) : '',
 21       "wpfm_file_types"         => isset($_REQUEST['wpfm_file_types']) ? sanitize_text_field($_REQUEST['wpfm_file_types']) : '',
 94       //table view
 95       "wpfm_enable_table"       => isset($_REQUEST['wpfm_enable_table']) ? sanitize_text_field($_REQUEST['wpfm_enable_table']) : '',
 96    );
 97    update_option ( WPFM_SHORT_NAME . '_settings', $wpfm_settigns );
 98    _e( 'All options are updated', 'wpfm' );
 99    die ( 0 );
100 }

There’s no capability check or security nonce.
An authenticated user can modify the plugin’s settings, for instance by adding php to the list of allowed filetypes:

Using the wpfm_upload_file AJAX action, the attacker could then upload a PHP script that would be saved and accessible as http://example.com/wp-content/uploads/user_uploads/<username>/<file>.php, which would lead to remote code execution:

Unauthenticated Arbitrary Post Deletion

The wpfm_delete_file AJAX action (unauthenticated) loads the wpfm_delete_file function from the “nmedia-user-file-uploader/inc/files.php” script. It takes an ID, $_REQUEST['file_id'], and deletes the corresponding post L708:

function wpfm_delete_file() {
   //check if it has attachment
   $file_id = intval($_REQUEST['file_id']);
   $curent_user = wpfm_get_current_user();
   $file = new WPFM_File($file_id);
   $response = array();
     if( $file->delete_file() ){
      $message = sprintf(__('%s Successfully Removed', 'wpfm'), $file->title);
      update_user_meta( get_current_user_id(), 'wpfm_total_filesize_used', wpfm_get_user_files_size(get_current_user_id()) );
      // Now deleting post
        wp_delete_post($file_id, true);

The plugin doesn’t verify that the user is allowed to delete the corresponding post and it lacks a security nonce. There’s only a call to the unsafe wpfm_get_current_user function but the result, $curent_user, is not even checked in the code.
An unauthenticated user can delete every page and post on the blog.

Unauthenticated Post Meta Change and Arbitrary File Download

The wpfm_file_meta_update AJAX action (unauthenticated) loads the wpfm_file_meta_update function from the “nmedia-user-file-uploader/inc/files.php” script. It is used to modify post meta data:

function wpfm_file_meta_update() {

   $file_id = isset($_REQUEST['file_id']) ? $_REQUEST['file_id'] : '' ;
   // we have meta fiels array with action and field_id
   // we remove file_id and action key form meta array

   // now we have pure meta fields array
   $meta_fields = $_REQUEST;

   if ($file_id != '') {
      foreach ($meta_fields as $meta_key => $meta_value) {
         update_post_meta( $file_id, $meta_key, $meta_value );
      _e("File Meta save successfully", "wpfm");
         _e("File Meta not save successfully", "wpfm");

There’s no capability check or nonce, and the data is not validated or sanitised.
Users can change any post meta data, which could lead for instance to arbitrary file download among other issues: after uploading a file, a user can change its post meta data by assigning wpfm_dir_path to $meta_key and wp-config.php to $meta_value and then download the wp-config.php script instead of the uploaded file.

Unauthenticated HTML Injection

The wpfm_send_file_in_email AJAX action (unauthenticated) loads the wpfm_send_file_in_email function from the “nmedia-user-file-uploader/inc/callback-functions.php” script. It allows a user to send an email to a recipient in order to share the link to the file uploaded by that user:

// sending file in email
function wpfm_send_file_in_email() {
   $file_id = isset($_REQUEST['file_id']) ? $_REQUEST['file_id'] : '';
   $file = new WPFM_File($file_id);
   if( empty($_POST['subject']) || empty($_POST['emailaddress']) ) {
      $response = array('message'=>__('Email or Subject not given.','wpfm'),'status'=>'error');
      wp_send_json( $response );
   $message       = '';
   $message      .= $_POST['message'];
   $message      .= '<br><hr>';
    $file_hash = $file->add_file_hash();
    $download_url   = add_query_arg('file_hash',$file_hash, $file->download_url);
   $message      .= sprintf(__('<a href="%s">Download %s</a>','wpfm'), esc_url($download_url), $file->title );
   $context = 'send-file';
   $email = new WPFM_Email($file_id, $context);
   $email->to      = $_POST['emailaddress'];
   $email->subject = $_POST['subject'];
   $email->message = $message;
   // send
   $response = array('message'=>__('File is shared successfully','wpfm'),'status'=>'success');

The user can customise the email subject, the recipient’s email address and also the message body. Because it is sent in HTML format and it isn’t sanitized, it is possible to inject HTML code (text formatting, CSS, images etc) in order to fully customise the email. Additionally, even if $_REQUEST['file_id'] is empty or invalid, the message will be sent anyway.
An unauthenticated user can use the blog as a spam relay.


Update immediately if you have version 18.2 or below installed. If you are using our web application firewall for WordPress, NinjaFirewall WP Edition (free) and NinjaFirewall WP+ Edition (premium), you are protected against this vulnerability.


The issue was reported on May 20, 2021 and version 18.0 was released on June 07, 2021. However, because not all issues were fixed in that version, a new version 18.3 was released on June 26, 2021.

Stay informed about the latest vulnerabilities