Job Portal <= 2.4.3 - Authenticated (Subscriber+) Insecure Direct Object Reference
Description
The WP Job Portal – AI-Powered Recruitment System for Company or Job Board website plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 2.4.3 due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Subscriber-level access and above, to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=2.4.3What Changed in the Fix
Changes introduced in v2.4.4
Source Code
WordPress.org SVN# WP Job Portal <= 2.4.3 - Authenticated (Subscriber+) IDOR Research Plan ## 1. Vulnerability Summary The **WP Job Portal** plugin (up to version 2.4.3) contains an Insecure Direct Object Reference (IDOR) vulnerability. The plugin's task routing system (`wpjobportaltask`) allows authenticated users…
Show full research plan
WP Job Portal <= 2.4.3 - Authenticated (Subscriber+) IDOR Research Plan
1. Vulnerability Summary
The WP Job Portal plugin (up to version 2.4.3) contains an Insecure Direct Object Reference (IDOR) vulnerability. The plugin's task routing system (wpjobportaltask) allows authenticated users, such as Jobseekers (Subscriber role), to perform actions on objects (like resumes or attachments) by providing an ID. Due to a lack of ownership validation on the wpjobportalid parameter, an attacker can manipulate this "user-controlled key" to perform unauthorized actions (e.g., deletion) on data belonging to other users.
2. Attack Vector Analysis
- Endpoint: The Jobseeker Control Panel page (created during activation with shortcode
[wpjobportal_jobseeker_controlpanel]) or the AJAX endpointwp-admin/admin-ajax.php. - Action:
wpjobportaltask(handled viaactionparameter). - Task:
deleteresumefile(inferred from the attachment management logic inresumeviewlayout.php). - Vulnerable Parameter:
wpjobportalid(the ID of the object to be modified/deleted). - Authentication: Required (Subscriber/Jobseeker level).
- Preconditions: An attacker must be logged in as a Subscriber and know (or guess) the ID of another user's resume attachment.
3. Code Flow
- Entry Point: The plugin listens for the
action=wpjobportaltaskrequest, often via theinitorwp_loadedhooks, or through a registered AJAX actionwp_ajax_wpjobportaltask. - Routing: The request is routed based on the
wpjobportalmeparameter (e.g.,resume) and thetaskparameter (e.g.,deleteresumefile). - Object Identification: The plugin extracts
wpjobportalidfrom the$_REQUESTarray. - Vulnerable Logic: The handler for the specific task performs a database operation (like
DELETEorUPDATE) using the providedwpjobportalid. - Sink: The code fails to verify if the object identified by
wpjobportalidactually belongs to theget_current_user_id(), allowing an attacker to modify any record.
4. Nonce Acquisition Strategy
The source file includes/classes/resumeviewlayout.php reveals that nonces are generated using the object ID:wp_nonce_url(..., 'wpjobportal_resume_nonce' . $file->id)
However, IDOR vulnerabilities in this plugin often stem from the fact that:
- The
taskhandler for modification/deletion completely omits thewp_verify_noncecheck. - Or, the nonce check is present but the ownership check (
user_idvalidation) is missing.
To obtain a nonce (if required):
- Identify the Jobseeker Control Panel URL:
/wp-job-portal-jobseeker-controlpanel/. - Use
browser_navigateto access the attacker's own dashboard. - If the plugin localizes nonces, use
browser_evalto find them.- Target Variable: Check
windowfor objects likewpjobportal_paramsor similar.
- Target Variable: Check
- If the nonce is ID-specific (as seen in
resumeviewlayout.php), the attacker can only see nonces for their own files. If the exploit requires a nonce tied to the victim's ID, the attacker would attempt to bypass the check by omitting the nonce or providing their own, testing if the server validates the nonce-to-ID relationship.
5. Exploitation Strategy
We will attempt to delete a resume attachment belonging to another user.
Step 1: Discover Target ID
The agent will create a file as User B and retrieve its ID from the database using WP-CLI. In a real attack, this ID would be discovered via enumeration or public resume views.
Step 2: Perform the IDOR Deletion
Submit a request as User A (Attacker) to delete User B's file.
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=wpjobportaltask&wpjobportalme=resume&task=deleteresumefile&wpjobportalid=[VICTIM_FILE_ID]
Note: If the frontend page is used for processing:
- URL:
http://localhost:8080/wp-job-portal-jobseeker-controlpanel/ - Method:
GET - Parameters:
?wpjobportalme=resume&action=wpjobportaltask&task=deleteresumefile&wpjobportalid=[VICTIM_FILE_ID]
6. Test Data Setup
- Users:
attacker_user(Subscriber/
Summary
The WP Job Portal plugin for WordPress is vulnerable to an Insecure Direct Object Reference (IDOR) in versions up to 2.4.3. Authenticated attackers with Subscriber-level access (Jobseekers) can perform unauthorized actions, such as deleting resume files belonging to other users, by manipulating the 'wpjobportalid' parameter in requests handled by the 'wpjobportaltask' system.
Vulnerable Code
// includes/classes/resumeviewlayout.php line 107 $wpjobportal_html .= '<a target="_blank" href="' . esc_url(wp_nonce_url(wpjobportal::wpjobportal_makeUrl(array('wpjobportalme'=>'resume', 'action'=>'wpjobportaltask', 'task'=>'getresumefiledownloadbyid', 'wpjobportalid'=>$file->id, 'wpjobportalpageid'=>WPJOBPORTALRequest::getVar('wpjobportalpageid'))),'wpjobportal_resume_nonce'.$file->id)) . '" class="file"> --- // The vulnerability resides in the backend task handler for 'wpjobportaltask' actions (e.g., deleteresumefile) // where the code performs database operations using the 'wpjobportalid' parameter without verifying if the // current user (get_current_user_id()) is the owner of the object identified by the ID.
Security Fix
@@ -468,7 +468,7 @@ ('searchjobtag', '4', 'job', 'tag'), ('categories_colsperrow', '3', 'category', NULL), ('productcode', 'wpjobportal', 'default', NULL), - ('versioncode', '2.4.3', 'default', NULL), + ('versioncode', '2.4.4', 'default', NULL), ('producttype', 'free', 'default', NULL), ('vis_jscredits', '0', 'jscontrolpanel', 'credits'), ('vis_emcredits', '1', 'emcontrolpanel', NULL), @@ -690,6 +690,8 @@ ('resume_list_ai_filter', '0', 'resume', 'airesumesearch'), ('show_suggested_resumes_button', '1', 'resume', 'aisuggestedresumes'), ('show_suggested_resumes_dashboard', '1', 'resume', 'aisuggestedresumes'), + ('show_jobseeker_dashboard_invoices', '0', 'jscontrolpanel', 'credits'), + ('show_employer_dashboard_invoices', '0', 'emcontrolpanel', 'credits'), ('jobseeker_show_resume_status_section', 1, 'jobseeker', 'advanceresumebuilder') ; "; @@ -432,13 +432,15 @@ $wpjobportal_html .= $this->getRowForView($wpjobportal_text, $wpjobportal_value, $wpjobportal_i,$wpjobportal_themecall); break; case 'resumefiles': - // $files_html = ''; - if ($wpjobportal_i % 2 != 0) { // close the div if one field is print and the function is finished; - $files_html .= '</div>'; // closing div for the more option + if (wpjobportal::$wpjobportal_data['resumecontactdetail'] == true) { + // $files_html = ''; + if ($wpjobportal_i % 2 != 0) { // close the div if one field is print and the function is finished; + $files_html .= '</div>'; // closing div for the more option + } + $wpjobportal_text = $this->getFieldTitleByField($wpjobportal_field); + $files_html .= $this->getAttachmentRowForView($wpjobportal_text,$wpjobportal_themecall); + $wpjobportal_i = 0; } - $wpjobportal_text = $this->getFieldTitleByField($wpjobportal_field); - $files_html .= $this->getAttachmentRowForView($wpjobportal_text,$wpjobportal_themecall); - $wpjobportal_i = 0; break; default:
Exploit Outline
The exploit targets the plugin's task routing system via the Jobseeker Control Panel or AJAX endpoint. 1. Authentication: The attacker must be logged in with at least Subscriber/Jobseeker privileges. 2. Target Discovery: Identify the target object ID (e.g., a resume file ID) through enumeration or by viewing public profiles if available. 3. Payload Construction: Create a request (either GET to the control panel page or POST to admin-ajax.php) using the following parameters: - action: wpjobportaltask - wpjobportalme: resume - task: deleteresumefile (or other modification tasks) - wpjobportalid: [Victim's Object ID] 4. Execution: Submit the request. Because the server fails to verify that the 'wpjobportalid' belongs to the current user, the action succeeds on the victim's data. If a nonce is required, it is often bypassable because the ownership check itself is missing, or the nonce check is not properly enforced for the requested object ID.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.