CVE-2025-69318

JobWP <= 2.4.5 - Unauthenticated Stored Cross-Site Scripting

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
2.4.6
Patched in
7d
Time to patch

Description

The JobWP plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.4.5 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.4.5
PublishedJanuary 21, 2026
Last updatedJanuary 27, 2026
Affected pluginjobwp

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the technical details and steps required to reproduce and verify the Unauthenticated Stored Cross-Site Scripting (XSS) vulnerability in the **JobWP** plugin (CVE-2025-69318). --- ### 1. Vulnerability Summary * **Vulnerability:** Unauthenticated Stored Cross-Site Scrip…

Show full research plan

This research plan outlines the technical details and steps required to reproduce and verify the Unauthenticated Stored Cross-Site Scripting (XSS) vulnerability in the JobWP plugin (CVE-2025-69318).


1. Vulnerability Summary

  • Vulnerability: Unauthenticated Stored Cross-Site Scripting (XSS).
  • Location: Job submission frontend form and subsequent storage in the database (Custom Post Type: jobwp_job).
  • Cause: The plugin fails to sanitize user-provided metadata during the unauthenticated job submission process and subsequently fails to escape this data when it is rendered in the WordPress admin dashboard (Job Listings table) or on the frontend single job page.
  • Impact: An unauthenticated attacker can inject malicious scripts that execute in the context of an administrative user viewing the submitted job listing, potentially leading to session hijacking or full site takeover.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php
  • Action: jobwp_submit_job (inferred) or jobwp_save_form (inferred).
  • Vulnerable Parameters: Metadata fields such as job_location, company_name, company_website, or custom application URLs.
  • Authentication: None (Unauthenticated).
  • Preconditions: The plugin must have the "Job Submission" feature enabled, or a page must exist containing the job submission shortcode.

3. Code Flow (Inferred)

  1. Entry Point: An unauthenticated user accesses a page containing the [jobwp_submit_job_form] (inferred) shortcode.
  2. Request: The user submits the form, triggering an AJAX request to admin-ajax.php with an action registered via wp_ajax_nopriv_jobwp_submit_job (inferred).
  3. Processing: The handler function (e.g., jobwp_handle_job_submission) retrieves data from $_POST.
  4. Storage (Sink): The plugin uses wp_insert_post() to create a jobwp_job post type and update_post_meta() to store fields like _company_name or _job_location.
  5. Vulnerability: The data is stored without being passed through sanitize_text_field() or wp_kses().
  6. Execution: An administrator logs in and navigates to JobWP > All Jobs. The admin table displays the _company_name meta value using echo without esc_html() or esc_attr().

4. Nonce Acquisition Strategy

To exploit unauthenticated AJAX handlers in WordPress, a valid nonce is often required. Based on standard JobWP patterns:

  1. Identify Shortcode: The job submission form is typically rendered via a shortcode. We will create a page with [jobwp_submit_job] (inferred).
  2. Setup Test Page:
    wp post create --post_type=page --post_title="Submit Job" --post_status=publish --post_content='[jobwp_submit_job]'
    
  3. Extract Nonce: The plugin likely localizes a script with a nonce. We will navigate to the newly created page and extract the nonce using browser_eval.
    • Script Handle: jobwp-script (inferred)
    • JS Variable: window.jobwp_vars (inferred)
    • Nonce Key: nonce or job_nonce (inferred)

Extraction Command:
browser_eval("window.jobwp_params?.nonce || window.jobwp_vars?.nonce")

5. Exploitation Strategy

  1. Target URL: http://<target-ip>/wp-admin/admin-ajax.php
  2. Payload: <script>alert(document.domain)</script>
  3. HTTP Request (Example):
    • Method: POST
    • Content-Type: application/x-www-form-urlencoded
    • Parameters:
      • action: jobwp_submit_job (verify via source)
      • nonce: [EXTRACTED_NONCE]
      • job_title: Security Researcher
      • company_name: Acme Corp <img src=x onerror=alert(1)>
      • job_location: Remote
      • job_description: Testing XSS
      • submit_job: 1

6. Test Data Setup

  1. Install and activate the jobwp plugin (version <= 2.4.5).
  2. Create a public-facing page for job submissions:
    wp post create --post_type=page --post_title="Submit a Job" --post_status=publish --post_content='[jobwp_submit_job]'
    
  3. Ensure that guest job submission is enabled in the plugin settings (usually jobwp_settings option).

7. Expected Results

  • The AJAX request should return a success message or a redirection URL.
  • A new post of type jobwp_job should be created in the wp_posts table.
  • When an administrator navigates to the JobWP > All Jobs page in the dashboard, the JavaScript payload (alert(1)) should execute automatically.

8. Verification Steps

  1. Check Database for Payload:
    wp post list --post_type=jobwp_job
    # Identify the Post ID, then:
    wp post meta list [POST_ID]
    
  2. Verify Unescaped Output:
    Use browser_navigate to go to the admin job listing page and check for the presence of the unescaped HTML:
    // In browser_eval
    document.body.innerHTML.includes('<img src=x onerror=alert(1)>')
    

9. Alternative Approaches

  • Metadata Targets: If company_name is sanitized, attempt exploitation via job_location, application_email, or company_website (using javascript: protocol if esc_url is missing).
  • Frontend XSS: If the payload doesn't fire in the admin dashboard, check the frontend "Job Listing" page or the "Single Job" view.
  • Direct Post Creation: If the AJAX handler is different, check if the plugin uses a custom REST API endpoint (/wp-json/jobwp/v1/...) for submissions.
Research Findings
Static analysis — not yet PoC-verified

Summary

The JobWP plugin for WordPress allows unauthenticated attackers to submit job listings through a frontend form that fails to sanitize user input. Malicious scripts injected into fields such as the company name are stored in the database and executed when an administrator views the job listing in the WordPress dashboard.

Vulnerable Code

// includes/class-jobwp-form-handler.php (Hypothetical)
public function jobwp_handle_job_submission() {
    $post_id = wp_insert_post(array(
        'post_title'   => $_POST['job_title'],
        'post_type'    => 'jobwp_job',
        'post_status'  => 'pending'
    ));

    // Vulnerable storage: No sanitization before storing in post meta
    update_post_meta($post_id, '_company_name', $_POST['company_name']);
    update_post_meta($post_id, '_job_location', $_POST['job_location']);
}

---

// includes/admin/class-jobwp-admin-display.php (Hypothetical)
public function display_job_columns($column, $post_id) {
    if ($column == 'company') {
        $company = get_post_meta($post_id, '_company_name', true);
        // Vulnerable output: Direct echo without escaping
        echo $company;
    }
}

Security Fix

--- a/includes/class-jobwp-form-handler.php
+++ b/includes/class-jobwp-form-handler.php
@@ -10,8 +10,8 @@
-    update_post_meta($post_id, '_company_name', $_POST['company_name']);
-    update_post_meta($post_id, '_job_location', $_POST['job_location']);
+    update_post_meta($post_id, '_company_name', sanitize_text_field($_POST['company_name']));
+    update_post_meta($post_id, '_job_location', sanitize_text_field($_POST['job_location']));
 }

--- a/includes/admin/class-jobwp-admin-display.php
+++ b/includes/admin/class-jobwp-admin-display.php
@@ -5,5 +5,5 @@
     if ($column == 'company') {
         $company = get_post_meta($post_id, '_company_name', true);
-        echo $company;
+        echo esc_html($company);
     }
 }

Exploit Outline

1. Identify the public-facing page containing the job submission form (usually implemented via the [jobwp_submit_job] shortcode). 2. Extract the required AJAX security nonce from the page source, typically found within localized JavaScript objects like 'jobwp_vars' or 'jobwp_params'. 3. Send an unauthenticated POST request to /wp-admin/admin-ajax.php with the action parameter corresponding to job submission (e.g., jobwp_submit_job). 4. Include an XSS payload (e.g., <script>alert(1)</script>) in metadata fields such as 'company_name', 'job_location', or 'company_website'. 5. Wait for an administrative user to navigate to the 'JobWP > All Jobs' dashboard page, at which point the stored payload will execute in their session context.

Check if your site is affected.

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