
WP Job Portal Plugin
SQL Injection
This blog post is about an unauthenticated arbitrary file download and an SQL injection vulnerability in the WP Job Portal plugin. If you're a WP Job Portal plugin user, please update the plugin to version 2.3.3.
The vulnerabilities mentioned here were discovered and reported by Patchstack Alliance community member LVT-tholv2k.
✌️ Our users are protected from this vulnerability. Are yours?
Identify vulnerabilities in your plugins and get recommendations for fixes.
Request auditProtect your users, improve server health and earn additional revenue.
Patchstack for hostsAbout WP Job Portal plugin
The plugin WP Job Portal, which has over 8,000 active installations, allows site owners to deliver a complete recruitment system without having to code. It helps make the hiring process easier, faster, and more accurate.

Unauthenticated SQL Injection
In versions 2.3.2 and below, the plugin is vulnerable to an SQL injection, which allows any unauthenticated attacker to inject SQL queries into the database. The vulnerability has been patched in version 2.3.3 and is tracked with CVE-2025-48274.
The root cause of the issue lies in the validateFormData function:
function validateFormData(&$data) {
$category = WPJOBPORTALrequest::getVar('parentid');
$inquery = ' ';
if ($category) {
$inquery .=" WHERE parentid = $category ";
}
$canupdate = false;
if ($data['id'] == '') {
$result = $this->isCategoryExist($data['cat_title']);
if ($result == true) {
return WPJOBPORTAL_ALREADY_EXIST;
} else {
$query = "SELECT max(ordering)+1 AS maxordering FROM " . wpjobportal::$_db->prefix . "wj_portal_categories " . $inquery;
$data['ordering'] = wpjobportaldb::get_var($query);
if ($data['ordering'] == null)
$data['ordering'] = 1;
}
//TRIMMED
}
return $canupdate;
}
The function validateFormData is taking the $category
value from the user-input and concatenating it with the SQL statement variable $inquery
. The variable is then concatenated to $query
and executed as an SQL statement, which leads to SQLi.
The function validateFormData is called by another function storeCategory
, which again is called by the function savecategory which can be executed as a task by a user.
Unauthenticated Arbitrary File Download
In versions 2.3.2 and below, the plugin is vulnerable to an arbitrary file upload, which allows any unauthenticated attacker to download any file from the server. The vulnerability has been patched in version 2.3.3 and is tracked with CVE-2025-48273.
The root cause of the issue lies in the downloadCustomUploadedFile function:
function downloadCustomUploadedFile($upload_for,$file_name,$entity_id){
$filename = wpjobportalphplib::wpJP_str_replace(' ', '_', $file_name);
$maindir = wp_upload_dir();
$basedir = $maindir['basedir'];
$datadirectory = wpjobportal::$_config->getConfigurationByConfigName('data_directory');
$path = $basedir . '/' . $datadirectory. '/data';
//TRIMMED
$file = $path .'/'.$file_name;
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . wpjobportalphplib::wpJP_basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
//ob_clean();
flush();
if ( ! function_exists( 'WP_Filesystem' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
global $wp_filesystem;
if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base') ) {
$creds = request_filesystem_credentials( site_url() );
wp_filesystem( $creds );
}
// test
echo $wp_filesystem->get_contents($file);
exit();
}
The function is taking the parameters and directly concatenating the $file_name
variable to $file
and retrieving and outputting the file. Tracing back the downloadCustomUploadedFile function, we come across the downloadcustomfile
function.
function downloadcustomfile(){
$nonce = WPJOBPORTALrequest::getVar('_wpnonce');
if (! wp_verify_nonce( $nonce, 'wpjobportal_field_nonce') ) {
die( 'Security check Failed' );
}
$upload_for = WPJOBPORTALrequest::getVar('upload_for');// to handle different entities(company, job, resume)
$entity_id = WPJOBPORTALrequest::getVar('entity_id');// to create path for enitity directory where the file is located
$file_name = WPJOBPORTALrequest::getVar('file_name');// to access the file and download it
$result = WPJOBPORTALincluder::getJSModel('customfield')->downloadCustomUploadedFile($upload_for,$file_name,$entity_id);
$msg = WPJOBPORTALMessages::getMessage($result, 'customfield');
WPJOBPORTALMessages::setLayoutMessage($msg['message'], $msg['status'],$this->_msgkey);
$url = esc_url_raw(admin_url("admin.php?page=wpjobportal_customfield&ff=".esc_attr($ff)));
if ($pagenum)
$url .= "&pagenum=" . $pagenum;
wp_redirect($url);
die();
}
The function downloadcustomfile
, which is also another task that can be executed by a user, takes $file_name from the user and passes it to downloadCustomUploadedFile. As mentioned above, the value is directly concatenated and retrieved, meaning that it is possible to access any file with path traversal.
Note: Both actions require a nonce to be executed , which is accessible through a page with the shortcode [wpjobportal_my_resumes].
The patch
In version 2.3.3, the vendor patched the SQL injection by ensuring that $category
is numeric and added additional esc_sql()
check on the variable.

For arbitrary file upload vulnerability, they patched the issue by adding a filename check with wpJP_clean_file_path
function.


Removing all occurrences of ./
and ..
essentially prevents an attacker from doing path traversal to access an arbitrary file.
Conclusion
It is necessary to ensure that all the SQL queries are properly used with prepared statements when processing with user-inputs. Whenever it comes to downloading or retrieving files, there should be proper filename validation to ensure there is no path traversal leading to arbitrary file access.
Want to learn more about finding and fixing vulnerabilities?
Explore our Academy to master the art of finding and patching vulnerabilities within the WordPress ecosystem. Dive deep into detailed guides on various vulnerability types, from discovery tactics for researchers to robust fixes for developers. Join us and contribute to our growing knowledge base.
Timeline
🤝 You can help us make the Internet a safer place
Streamline your disclosure process to fix vulnerabilities faster and comply with CRA.
Get started for freeProtect your users too! Improve server health and earn added revenue with proactive security.
Patchstack for hostsReport vulnerabilities to our gamified bug bounty program to earn monthly cash rewards.
Learn more