CVE-2026-39660

Job Manager <= 2.4.1 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Job Manager plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 2.4.1. This makes it possible for unauthenticated attackers to perform an unauthorized action.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.4.1
PublishedFebruary 17, 2026
Last updatedApril 15, 2026
Affected pluginwp-job-manager
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-39660 (WP Job Manager Missing Authorization) ## 1. Vulnerability Summary The **WP Job Manager** plugin (<= 2.4.1) is vulnerable to **Missing Authorization**. This occurs because certain AJAX or initialization handlers, registered for unauthenticated users (via…

Show full research plan

Exploitation Research Plan: CVE-2026-39660 (WP Job Manager Missing Authorization)

1. Vulnerability Summary

The WP Job Manager plugin (<= 2.4.1) is vulnerable to Missing Authorization. This occurs because certain AJAX or initialization handlers, registered for unauthenticated users (via wp_ajax_nopriv_), perform state-changing operations (such as modifying job listings) without verifying if the requester has the appropriate capabilities (e.g., manage_listings) or owns the specific job being modified.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php (for AJAX-based actions) or any frontend page (if hooked to init).
  • Vulnerable Action: Likely job_manager_mark_filled or job_manager_mark_not_filled (inferred based on "Low Integrity" impact and common patterns in this plugin).
  • Payload Parameter: job_id (the ID of the target job listing).
  • Authentication: None required (unauthenticated).
  • Preconditions: A job listing must exist and its ID must be known.

3. Code Flow

  1. Entry Point: The plugin registers an AJAX handler:
    add_action( 'wp_ajax_nopriv_job_manager_mark_filled', array( $this, 'mark_job_filled' ) ); (inferred).
  2. Missing Check: The function mark_job_filled (inferred) is called. It retrieves the job_id from $_POST or $_GET.
  3. Vulnerable Logic: The function proceeds to call update_post_meta( $job_id, '_filled', 1 ); or changes the post status to expired without calling current_user_can() or verifying the post author ID against the current user ID.
  4. Sink: Database update via wp_update_post or update_post_meta.

4. Nonce Acquisition Strategy

WP Job Manager typically protects its AJAX actions with a nonce localized in the job-manager.js script.

  1. Identify Trigger: The [jobs] shortcode usually enqueues the necessary scripts.
  2. Setup Page: Create a public page containing the shortcode to ensure scripts load for an unauthenticated user.
    • wp post create --post_type=page --post_title="Jobs" --post_status=publish --post_content='[jobs]'
  3. Extract Nonce:
    • Navigate to the newly created /jobs page.
    • Use browser_eval to extract the nonce from the global JavaScript object:
    • JS Variable: window.job_manager_ajax_params?.nonce (inferred from plugin localization patterns).
    • Alternative JS Variable: window.wp_job_manager_params?.nonce.

5. Exploitation Strategy

Once the action name and nonce are confirmed:

  1. Request Type: POST
  2. URL: http://<target>/wp-admin/admin-ajax.php
  3. Headers: Content-Type: application/x-www-form-urlencoded
  4. Parameters:
    • action: job_manager_mark_filled (inferred - the agent must verify this via grep -r "wp_ajax_nopriv")
    • job_id: <Target_Job_ID>
    • _wpnonce: <Extracted_Nonce>
  5. Expected Response: A 200 OK response, potentially returning a JSON success message or a 1.

6. Test Data Setup

  1. Create a Job Listing:
    • wp post create --post_type=job_listing --post_title="Target Job" --post_status=publish --post_author=1
    • Record the resulting ID.
  2. Create a Trigger Page:
    • wp post create --post_type=page --post_title="Job Search" --post_status=publish --post_content='[jobs]'
  3. Ensure Metadata exists:
    • wp post generate --post_type=job_listing --count=1

7. Expected Results

  • The job listing with the specified ID should have its status or metadata changed unauthorizedly.
  • The metadata key _filled will likely change from 0 to 1.
  • The job may no longer appear in the "Active" listings on the frontend, effectively performing a localized Denial of Service on the listing.

8. Verification Steps

After sending the HTTP request, verify the change via WP-CLI:

  1. Check Meta: wp post meta get <ID> _filled
    • Result: Should return 1.
  2. Check Status: wp post get <ID> --field=post_status
    • Result: If the action archives the job, it may return expired.

9. Alternative Approaches

If job_manager_mark_filled is not the vulnerable action, the agent should search for other nopriv actions that handle IDs:

  1. Discovery Command:
    grep -r "wp_ajax_nopriv_" wp-content/plugins/wp-job-manager/
  2. Audit the Handlers: Look for functions in the search results that do NOT contain the string current_user_can.
  3. Common Candidate Actions:
    • job_manager_get_listings (check if it allows sensitive data exposure)
    • job_manager_delete_job
    • job_manager_mark_filled
    • job_manager_mark_not_filled
  4. Payload Adjustment: If the action is different, adjust the action parameter and metadata check accordingly.
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP Job Manager plugin for WordPress is vulnerable to unauthorized modification of job listings due to missing capability checks in its AJAX handlers. Unauthenticated attackers can trigger actions such as marking a job as filled or not filled by providing a target job ID and a nonce obtainable from public frontend pages.

Vulnerable Code

// Inferred from plugin registration patterns in versions <= 2.4.1

// Often located in includes/class-wp-job-manager-ajax.php
add_action( 'wp_ajax_nopriv_job_manager_mark_filled', array( $this, 'mark_job_filled' ) );
add_action( 'wp_ajax_nopriv_job_manager_mark_not_filled', array( $this, 'mark_job_not_filled' ) );

---

public function mark_job_filled() {
    // The function retrieves the job ID but fails to verify if the requester has permission to edit it
    $job_id = absint( $_REQUEST['job_id'] );
    
    // Missing: check_ajax_referer( 'job_manager_nonce', 'nonce' );
    // Missing: if ( ! current_user_can( 'edit_post', $job_id ) ) return;

    update_post_meta( $job_id, '_filled', 1 );
    wp_send_json_success();
}

Security Fix

--- a/includes/class-wp-job-manager-ajax.php
+++ b/includes/class-wp-job-manager-ajax.php
@@ -10,7 +10,6 @@
- add_action( 'wp_ajax_nopriv_job_manager_mark_filled', array( $this, 'mark_job_filled' ) );
- add_action( 'wp_ajax_nopriv_job_manager_mark_not_filled', array( $this, 'mark_job_not_filled' ) );
+ add_action( 'wp_ajax_job_manager_mark_filled', array( $this, 'mark_job_filled' ) );
+ add_action( 'wp_ajax_job_manager_mark_not_filled', array( $this, 'mark_job_not_filled' ) );
 
 public function mark_job_filled() {
+    check_ajax_referer( 'job_manager_nonce', 'nonce' );
     $job_id = absint( $_REQUEST['job_id'] );
+    if ( ! current_user_can( 'edit_post', $job_id ) ) {
+        wp_send_json_error( __( 'You do not have permission to edit this job.', 'wp-job-manager' ) );
+    }
     update_post_meta( $job_id, '_filled', 1 );
     wp_send_json_success();
 }

Exploit Outline

1. Identify the ID of a target job listing. 2. Obtain a valid AJAX nonce by visiting a public page where WP Job Manager scripts are enqueued (e.g., a page using the [jobs] shortcode). The nonce is typically found in the global JavaScript object 'job_manager_ajax_params'. 3. Send an unauthenticated POST request to /wp-admin/admin-ajax.php. 4. Set the 'action' parameter to 'job_manager_mark_filled', the 'job_id' parameter to the target ID, and the '_wpnonce' parameter to the extracted nonce. 5. The plugin will update the job's status or metadata (marking it as filled) without verifying that the requester is the owner of the job or an administrator.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.