CVE-2026-24379

Job Portal <= 2.4.3 - Authenticated (Subscriber+) Insecure Direct Object Reference

mediumAuthorization Bypass Through User-Controlled Key
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
2.4.4
Patched in
5d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.4.3
PublishedJanuary 24, 2026
Last updatedJanuary 28, 2026
Affected pluginwp-job-portal

What Changed in the Fix

Changes introduced in v2.4.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 endpoint wp-admin/admin-ajax.php.
  • Action: wpjobportaltask (handled via action parameter).
  • Task: deleteresumefile (inferred from the attachment management logic in resumeviewlayout.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

  1. Entry Point: The plugin listens for the action=wpjobportaltask request, often via the init or wp_loaded hooks, or through a registered AJAX action wp_ajax_wpjobportaltask.
  2. Routing: The request is routed based on the wpjobportalme parameter (e.g., resume) and the task parameter (e.g., deleteresumefile).
  3. Object Identification: The plugin extracts wpjobportalid from the $_REQUEST array.
  4. Vulnerable Logic: The handler for the specific task performs a database operation (like DELETE or UPDATE) using the provided wpjobportalid.
  5. Sink: The code fails to verify if the object identified by wpjobportalid actually belongs to the get_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:

  1. The task handler for modification/deletion completely omits the wp_verify_nonce check.
  2. Or, the nonce check is present but the ownership check (user_id validation) is missing.

To obtain a nonce (if required):

  1. Identify the Jobseeker Control Panel URL: /wp-job-portal-jobseeker-controlpanel/.
  2. Use browser_navigate to access the attacker's own dashboard.
  3. If the plugin localizes nonces, use browser_eval to find them.
    • Target Variable: Check window for objects like wpjobportal_params or similar.
  4. 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

  1. Users:
    • attacker_user (Subscriber/
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-job-portal/2.4.3/includes/activation.php /home/deploy/wp-safety.org/data/plugin-versions/wp-job-portal/2.4.4/includes/activation.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-job-portal/2.4.3/includes/activation.php	2025-12-30 14:07:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-job-portal/2.4.4/includes/activation.php	2026-01-06 12:15:08.000000000 +0000
@@ -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')
               ;
               ";
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-job-portal/2.4.3/includes/classes/resumeviewlayout.php /home/deploy/wp-safety.org/data/plugin-versions/wp-job-portal/2.4.4/includes/classes/resumeviewlayout.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-job-portal/2.4.3/includes/classes/resumeviewlayout.php	2025-12-30 14:07:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-job-portal/2.4.4/includes/classes/resumeviewlayout.php	2026-01-06 12:15:08.000000000 +0000
@@ -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.