JobWP <= 2.4.5 - Unauthenticated Stored Cross-Site Scripting
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:NTechnical Details
Source Code
WordPress.org SVNThis 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) orjobwp_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)
- Entry Point: An unauthenticated user accesses a page containing the
[jobwp_submit_job_form](inferred) shortcode. - Request: The user submits the form, triggering an AJAX request to
admin-ajax.phpwith an action registered viawp_ajax_nopriv_jobwp_submit_job(inferred). - Processing: The handler function (e.g.,
jobwp_handle_job_submission) retrieves data from$_POST. - Storage (Sink): The plugin uses
wp_insert_post()to create ajobwp_jobpost type andupdate_post_meta()to store fields like_company_nameor_job_location. - Vulnerability: The data is stored without being passed through
sanitize_text_field()orwp_kses(). - Execution: An administrator logs in and navigates to JobWP > All Jobs. The admin table displays the
_company_namemeta value usingechowithoutesc_html()oresc_attr().
4. Nonce Acquisition Strategy
To exploit unauthenticated AJAX handlers in WordPress, a valid nonce is often required. Based on standard JobWP patterns:
- Identify Shortcode: The job submission form is typically rendered via a shortcode. We will create a page with
[jobwp_submit_job](inferred). - Setup Test Page:
wp post create --post_type=page --post_title="Submit Job" --post_status=publish --post_content='[jobwp_submit_job]' - 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:
nonceorjob_nonce(inferred)
- Script Handle:
Extraction Command:browser_eval("window.jobwp_params?.nonce || window.jobwp_vars?.nonce")
5. Exploitation Strategy
- Target URL:
http://<target-ip>/wp-admin/admin-ajax.php - Payload:
<script>alert(document.domain)</script> - 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 Researchercompany_name:Acme Corp <img src=x onerror=alert(1)>job_location:Remotejob_description:Testing XSSsubmit_job:1
6. Test Data Setup
- Install and activate the
jobwpplugin (version <= 2.4.5). - 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]' - Ensure that guest job submission is enabled in the plugin settings (usually
jobwp_settingsoption).
7. Expected Results
- The AJAX request should return a success message or a redirection URL.
- A new post of type
jobwp_jobshould be created in thewp_poststable. - When an administrator navigates to the JobWP > All Jobs page in the dashboard, the JavaScript payload (
alert(1)) should execute automatically.
8. Verification Steps
- Check Database for Payload:
wp post list --post_type=jobwp_job # Identify the Post ID, then: wp post meta list [POST_ID] - Verify Unescaped Output:
Usebrowser_navigateto 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_nameis sanitized, attempt exploitation viajob_location,application_email, orcompany_website(usingjavascript:protocol ifesc_urlis 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.
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
@@ -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'])); } @@ -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.